mirror of
https://github.com/henrygd/beszel.git
synced 2026-04-06 13:01:49 +02:00
Compare commits
65 Commits
main
...
l10n_main_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ca8589568 | ||
|
|
8e4d6fa225 | ||
|
|
7eabff5c22 | ||
|
|
f54b7b9b94 | ||
|
|
15a75d1f80 | ||
|
|
8ba085e368 | ||
|
|
bff8be5f6a | ||
|
|
9020bb28ba | ||
|
|
05802df059 | ||
|
|
f74cb6eced | ||
|
|
f89ab9acf4 | ||
|
|
7ada1a1948 | ||
|
|
aa8612e39e | ||
|
|
e3aa33b124 | ||
|
|
fb8d21538a | ||
|
|
c6fbcb6c1d | ||
|
|
78d3fe7a7d | ||
|
|
c449719e07 | ||
|
|
3f5c4b6782 | ||
|
|
67e5d17d4a | ||
|
|
55c5352369 | ||
|
|
868380a62d | ||
|
|
8ce395041b | ||
|
|
6e49278bac | ||
|
|
11ee9b83e4 | ||
|
|
85f5407b42 | ||
|
|
27ac3f66b4 | ||
|
|
8418094b69 | ||
|
|
b2937a5c26 | ||
|
|
9229e102ab | ||
|
|
16ebf60d4d | ||
|
|
b197f56d2e | ||
|
|
af3ab321ee | ||
|
|
c9bf4a51f2 | ||
|
|
c0835eb9b2 | ||
|
|
ca91cd243c | ||
|
|
e969a4a288 | ||
|
|
1bf17a772f | ||
|
|
d97f5c55dc | ||
|
|
f9332cae1e | ||
|
|
1076e6040c | ||
|
|
0ee9ad9435 | ||
|
|
f40a7febe1 | ||
|
|
9547cff277 | ||
|
|
55ee0f6c5a | ||
|
|
adbe538463 | ||
|
|
0ac6d26307 | ||
|
|
ed203d4e38 | ||
|
|
abc3fd2167 | ||
|
|
478d67e12c | ||
|
|
d36c26303b | ||
|
|
a40709c747 | ||
|
|
6301cdbc37 | ||
|
|
64caa76183 | ||
|
|
2f882a4fd3 | ||
|
|
929c7a395c | ||
|
|
59f4105f42 | ||
|
|
50976ff369 | ||
|
|
b3d75e19bb | ||
|
|
081e747ac3 | ||
|
|
21e9c11ab1 | ||
|
|
eea02950e9 | ||
|
|
fd36b0184f | ||
|
|
93868a4965 | ||
|
|
75fd364350 |
@@ -19,8 +19,6 @@ import (
|
|||||||
gossh "golang.org/x/crypto/ssh"
|
gossh "golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultDataCacheTimeMs uint16 = 60_000
|
|
||||||
|
|
||||||
type Agent struct {
|
type Agent struct {
|
||||||
sync.Mutex // Used to lock agent while collecting data
|
sync.Mutex // Used to lock agent while collecting data
|
||||||
debug bool // true if LOG_LEVEL is set to debug
|
debug bool // true if LOG_LEVEL is set to debug
|
||||||
@@ -38,7 +36,6 @@ type Agent struct {
|
|||||||
sensorConfig *SensorConfig // Sensors config
|
sensorConfig *SensorConfig // Sensors config
|
||||||
systemInfo system.Info // Host system info (dynamic)
|
systemInfo system.Info // Host system info (dynamic)
|
||||||
systemDetails system.Details // Host system details (static, once-per-connection)
|
systemDetails system.Details // Host system details (static, once-per-connection)
|
||||||
detailsDirty bool // Whether system details have changed and need to be resent
|
|
||||||
gpuManager *GPUManager // Manages GPU data
|
gpuManager *GPUManager // Manages GPU data
|
||||||
cache *systemDataCache // Cache for system stats based on cache time
|
cache *systemDataCache // Cache for system stats based on cache time
|
||||||
connectionManager *ConnectionManager // Channel to signal connection events
|
connectionManager *ConnectionManager // Channel to signal connection events
|
||||||
@@ -100,7 +97,7 @@ func NewAgent(dataDir ...string) (agent *Agent, err error) {
|
|||||||
slog.Debug(beszel.Version)
|
slog.Debug(beszel.Version)
|
||||||
|
|
||||||
// initialize docker manager
|
// initialize docker manager
|
||||||
agent.dockerManager = newDockerManager(agent)
|
agent.dockerManager = newDockerManager()
|
||||||
|
|
||||||
// initialize system info
|
// initialize system info
|
||||||
agent.refreshSystemDetails()
|
agent.refreshSystemDetails()
|
||||||
@@ -145,7 +142,7 @@ func NewAgent(dataDir ...string) (agent *Agent, err error) {
|
|||||||
|
|
||||||
// if debugging, print stats
|
// if debugging, print stats
|
||||||
if agent.debug {
|
if agent.debug {
|
||||||
slog.Debug("Stats", "data", agent.gatherStats(common.DataRequestOptions{CacheTimeMs: defaultDataCacheTimeMs, IncludeDetails: true}))
|
slog.Debug("Stats", "data", agent.gatherStats(common.DataRequestOptions{CacheTimeMs: 60_000, IncludeDetails: true}))
|
||||||
}
|
}
|
||||||
|
|
||||||
return agent, nil
|
return agent, nil
|
||||||
@@ -167,6 +164,11 @@ func (a *Agent) gatherStats(options common.DataRequestOptions) *system.CombinedD
|
|||||||
Info: a.systemInfo,
|
Info: a.systemInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Include static system details only when requested
|
||||||
|
if options.IncludeDetails {
|
||||||
|
data.Details = &a.systemDetails
|
||||||
|
}
|
||||||
|
|
||||||
// slog.Info("System data", "data", data, "cacheTimeMs", cacheTimeMs)
|
// slog.Info("System data", "data", data, "cacheTimeMs", cacheTimeMs)
|
||||||
|
|
||||||
if a.dockerManager != nil {
|
if a.dockerManager != nil {
|
||||||
@@ -179,7 +181,7 @@ func (a *Agent) gatherStats(options common.DataRequestOptions) *system.CombinedD
|
|||||||
}
|
}
|
||||||
|
|
||||||
// skip updating systemd services if cache time is not the default 60sec interval
|
// skip updating systemd services if cache time is not the default 60sec interval
|
||||||
if a.systemdManager != nil && cacheTimeMs == defaultDataCacheTimeMs {
|
if a.systemdManager != nil && cacheTimeMs == 60_000 {
|
||||||
totalCount := uint16(a.systemdManager.getServiceStatsCount())
|
totalCount := uint16(a.systemdManager.getServiceStatsCount())
|
||||||
if totalCount > 0 {
|
if totalCount > 0 {
|
||||||
numFailed := a.systemdManager.getFailedServiceCount()
|
numFailed := a.systemdManager.getFailedServiceCount()
|
||||||
@@ -210,8 +212,7 @@ func (a *Agent) gatherStats(options common.DataRequestOptions) *system.CombinedD
|
|||||||
slog.Debug("Extra FS", "data", data.Stats.ExtraFs)
|
slog.Debug("Extra FS", "data", data.Stats.ExtraFs)
|
||||||
|
|
||||||
a.cache.Set(data, cacheTimeMs)
|
a.cache.Set(data, cacheTimeMs)
|
||||||
|
return data
|
||||||
return a.attachSystemDetails(data, cacheTimeMs, options.IncludeDetails)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start initializes and starts the agent with optional WebSocket connection
|
// Start initializes and starts the agent with optional WebSocket connection
|
||||||
|
|||||||
@@ -1,11 +1,84 @@
|
|||||||
// Package battery provides functions to check if the system has a battery and return the charge state and percentage.
|
//go:build !freebsd
|
||||||
|
|
||||||
|
// Package battery provides functions to check if the system has a battery and to get the battery stats.
|
||||||
package battery
|
package battery
|
||||||
|
|
||||||
const (
|
import (
|
||||||
stateUnknown uint8 = iota
|
"errors"
|
||||||
stateEmpty
|
"log/slog"
|
||||||
stateFull
|
"math"
|
||||||
stateCharging
|
|
||||||
stateDischarging
|
"github.com/distatus/battery"
|
||||||
stateIdle
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
systemHasBattery = false
|
||||||
|
haveCheckedBattery = false
|
||||||
|
)
|
||||||
|
|
||||||
|
// HasReadableBattery checks if the system has a battery and returns true if it does.
|
||||||
|
func HasReadableBattery() bool {
|
||||||
|
if haveCheckedBattery {
|
||||||
|
return systemHasBattery
|
||||||
|
}
|
||||||
|
haveCheckedBattery = true
|
||||||
|
batteries, err := battery.GetAll()
|
||||||
|
for _, bat := range batteries {
|
||||||
|
if bat != nil && (bat.Full > 0 || bat.Design > 0) {
|
||||||
|
systemHasBattery = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !systemHasBattery {
|
||||||
|
slog.Debug("No battery found", "err", err)
|
||||||
|
}
|
||||||
|
return systemHasBattery
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBatteryStats returns the current battery percent and charge state
|
||||||
|
// percent = (current charge of all batteries) / (sum of designed/full capacity of all batteries)
|
||||||
|
func GetBatteryStats() (batteryPercent uint8, batteryState uint8, err error) {
|
||||||
|
if !HasReadableBattery() {
|
||||||
|
return batteryPercent, batteryState, errors.ErrUnsupported
|
||||||
|
}
|
||||||
|
batteries, err := battery.GetAll()
|
||||||
|
// we'll handle errors later by skipping batteries with errors, rather
|
||||||
|
// than skipping everything because of the presence of some errors.
|
||||||
|
if len(batteries) == 0 {
|
||||||
|
return batteryPercent, batteryState, errors.New("no batteries")
|
||||||
|
}
|
||||||
|
|
||||||
|
totalCapacity := float64(0)
|
||||||
|
totalCharge := float64(0)
|
||||||
|
errs, partialErrs := err.(battery.Errors)
|
||||||
|
|
||||||
|
batteryState = math.MaxUint8
|
||||||
|
|
||||||
|
for i, bat := range batteries {
|
||||||
|
if partialErrs && errs[i] != nil {
|
||||||
|
// if there were some errors, like missing data, skip it
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if bat == nil || bat.Full == 0 {
|
||||||
|
// 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.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
totalCapacity += bat.Full
|
||||||
|
totalCharge += min(bat.Current, bat.Full)
|
||||||
|
if bat.State.Raw >= 0 {
|
||||||
|
batteryState = uint8(bat.State.Raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if totalCapacity == 0 || batteryState == math.MaxUint8 {
|
||||||
|
// for macs there's sometimes a ghost battery with 0 capacity
|
||||||
|
// https://github.com/distatus/battery/issues/34
|
||||||
|
// Instead of skipping over those batteries, we'll check for total 0 capacity
|
||||||
|
// and return an error. This also prevents a divide by zero.
|
||||||
|
return batteryPercent, batteryState, errors.New("no battery capacity")
|
||||||
|
}
|
||||||
|
|
||||||
|
batteryPercent = uint8(totalCharge / totalCapacity * 100)
|
||||||
|
return batteryPercent, batteryState, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
//go:build darwin
|
|
||||||
|
|
||||||
package battery
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"log/slog"
|
|
||||||
"math"
|
|
||||||
"os/exec"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"howett.net/plist"
|
|
||||||
)
|
|
||||||
|
|
||||||
type macBattery struct {
|
|
||||||
CurrentCapacity int `plist:"CurrentCapacity"`
|
|
||||||
MaxCapacity int `plist:"MaxCapacity"`
|
|
||||||
FullyCharged bool `plist:"FullyCharged"`
|
|
||||||
IsCharging bool `plist:"IsCharging"`
|
|
||||||
ExternalConnected bool `plist:"ExternalConnected"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func readMacBatteries() ([]macBattery, error) {
|
|
||||||
out, err := exec.Command("ioreg", "-n", "AppleSmartBattery", "-r", "-a").Output()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(out) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
var batteries []macBattery
|
|
||||||
if _, err := plist.Unmarshal(out, &batteries); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return batteries, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasReadableBattery checks if the system has a battery and returns true if it does.
|
|
||||||
var HasReadableBattery = sync.OnceValue(func() bool {
|
|
||||||
systemHasBattery := false
|
|
||||||
batteries, err := readMacBatteries()
|
|
||||||
slog.Debug("Batteries", "batteries", batteries, "err", err)
|
|
||||||
for _, bat := range batteries {
|
|
||||||
if bat.MaxCapacity > 0 {
|
|
||||||
systemHasBattery = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return systemHasBattery
|
|
||||||
})
|
|
||||||
|
|
||||||
// GetBatteryStats returns the current battery percent and charge state.
|
|
||||||
// Uses CurrentCapacity/MaxCapacity to match the value macOS displays.
|
|
||||||
func GetBatteryStats() (batteryPercent uint8, batteryState uint8, err error) {
|
|
||||||
if !HasReadableBattery() {
|
|
||||||
return batteryPercent, batteryState, errors.ErrUnsupported
|
|
||||||
}
|
|
||||||
batteries, err := readMacBatteries()
|
|
||||||
if len(batteries) == 0 {
|
|
||||||
return batteryPercent, batteryState, errors.New("no batteries")
|
|
||||||
}
|
|
||||||
|
|
||||||
totalCapacity := 0
|
|
||||||
totalCharge := 0
|
|
||||||
batteryState = math.MaxUint8
|
|
||||||
|
|
||||||
for _, bat := range batteries {
|
|
||||||
if bat.MaxCapacity == 0 {
|
|
||||||
// skip ghost batteries with 0 capacity
|
|
||||||
// https://github.com/distatus/battery/issues/34
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
totalCapacity += bat.MaxCapacity
|
|
||||||
totalCharge += min(bat.CurrentCapacity, bat.MaxCapacity)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case !bat.ExternalConnected:
|
|
||||||
batteryState = stateDischarging
|
|
||||||
case bat.IsCharging:
|
|
||||||
batteryState = stateCharging
|
|
||||||
case bat.CurrentCapacity == 0:
|
|
||||||
batteryState = stateEmpty
|
|
||||||
case !bat.FullyCharged:
|
|
||||||
batteryState = stateIdle
|
|
||||||
default:
|
|
||||||
batteryState = stateFull
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if totalCapacity == 0 || batteryState == math.MaxUint8 {
|
|
||||||
return batteryPercent, batteryState, errors.New("no battery capacity")
|
|
||||||
}
|
|
||||||
|
|
||||||
batteryPercent = uint8(float64(totalCharge) / float64(totalCapacity) * 100)
|
|
||||||
return batteryPercent, batteryState, nil
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !darwin && !linux && !windows
|
//go:build freebsd
|
||||||
|
|
||||||
package battery
|
package battery
|
||||||
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
//go:build linux
|
|
||||||
|
|
||||||
package battery
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"log/slog"
|
|
||||||
"math"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/henrygd/beszel/agent/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
// getBatteryPaths returns the paths of all batteries in /sys/class/power_supply
|
|
||||||
var getBatteryPaths func() ([]string, error)
|
|
||||||
|
|
||||||
// HasReadableBattery checks if the system has a battery and returns true if it does.
|
|
||||||
var HasReadableBattery func() bool
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
resetBatteryState("/sys/class/power_supply")
|
|
||||||
}
|
|
||||||
|
|
||||||
// resetBatteryState resets the sync.Once functions to a fresh state.
|
|
||||||
// Tests call this after swapping sysfsPowerSupply so the new path is picked up.
|
|
||||||
func resetBatteryState(sysfsPowerSupplyPath string) {
|
|
||||||
getBatteryPaths = sync.OnceValues(func() ([]string, error) {
|
|
||||||
entries, err := os.ReadDir(sysfsPowerSupplyPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var paths []string
|
|
||||||
for _, e := range entries {
|
|
||||||
path := filepath.Join(sysfsPowerSupplyPath, e.Name())
|
|
||||||
if utils.ReadStringFile(filepath.Join(path, "type")) == "Battery" {
|
|
||||||
paths = append(paths, path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return paths, nil
|
|
||||||
})
|
|
||||||
HasReadableBattery = sync.OnceValue(func() bool {
|
|
||||||
systemHasBattery := false
|
|
||||||
paths, err := getBatteryPaths()
|
|
||||||
for _, path := range paths {
|
|
||||||
if _, ok := utils.ReadStringFileOK(filepath.Join(path, "capacity")); ok {
|
|
||||||
systemHasBattery = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !systemHasBattery {
|
|
||||||
slog.Debug("No battery found", "err", err)
|
|
||||||
}
|
|
||||||
return systemHasBattery
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSysfsState(status string) uint8 {
|
|
||||||
switch status {
|
|
||||||
case "Empty":
|
|
||||||
return stateEmpty
|
|
||||||
case "Full":
|
|
||||||
return stateFull
|
|
||||||
case "Charging":
|
|
||||||
return stateCharging
|
|
||||||
case "Discharging":
|
|
||||||
return stateDischarging
|
|
||||||
case "Not charging":
|
|
||||||
return stateIdle
|
|
||||||
default:
|
|
||||||
return stateUnknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBatteryStats returns the current battery percent and charge state.
|
|
||||||
// Reads /sys/class/power_supply/*/capacity directly so the kernel-reported
|
|
||||||
// value is used, which is always 0-100 and matches what the OS displays.
|
|
||||||
func GetBatteryStats() (batteryPercent uint8, batteryState uint8, err error) {
|
|
||||||
if !HasReadableBattery() {
|
|
||||||
return batteryPercent, batteryState, errors.ErrUnsupported
|
|
||||||
}
|
|
||||||
paths, err := getBatteryPaths()
|
|
||||||
if len(paths) == 0 {
|
|
||||||
return batteryPercent, batteryState, errors.New("no batteries")
|
|
||||||
}
|
|
||||||
|
|
||||||
batteryState = math.MaxUint8
|
|
||||||
totalPercent := 0
|
|
||||||
count := 0
|
|
||||||
|
|
||||||
for _, path := range paths {
|
|
||||||
capStr, ok := utils.ReadStringFileOK(filepath.Join(path, "capacity"))
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
cap, parseErr := strconv.Atoi(capStr)
|
|
||||||
if parseErr != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
totalPercent += cap
|
|
||||||
count++
|
|
||||||
|
|
||||||
state := parseSysfsState(utils.ReadStringFile(filepath.Join(path, "status")))
|
|
||||||
if state != stateUnknown {
|
|
||||||
batteryState = state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if count == 0 || batteryState == math.MaxUint8 {
|
|
||||||
return batteryPercent, batteryState, errors.New("no battery capacity")
|
|
||||||
}
|
|
||||||
|
|
||||||
batteryPercent = uint8(totalPercent / count)
|
|
||||||
return batteryPercent, batteryState, nil
|
|
||||||
}
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
//go:build testing && linux
|
|
||||||
|
|
||||||
package battery
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
// setupFakeSysfs creates a temporary sysfs-like tree under t.TempDir(),
|
|
||||||
// swaps sysfsPowerSupply, resets the sync.Once caches, and restores
|
|
||||||
// everything on cleanup. Returns a helper to create battery directories.
|
|
||||||
func setupFakeSysfs(t *testing.T) (tmpDir string, addBattery func(name, capacity, status string)) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
tmp := t.TempDir()
|
|
||||||
resetBatteryState(tmp)
|
|
||||||
|
|
||||||
write := func(path, content string) {
|
|
||||||
t.Helper()
|
|
||||||
dir := filepath.Dir(path)
|
|
||||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addBattery = func(name, capacity, status string) {
|
|
||||||
t.Helper()
|
|
||||||
batDir := filepath.Join(tmp, name)
|
|
||||||
write(filepath.Join(batDir, "type"), "Battery")
|
|
||||||
write(filepath.Join(batDir, "capacity"), capacity)
|
|
||||||
write(filepath.Join(batDir, "status"), status)
|
|
||||||
}
|
|
||||||
|
|
||||||
return tmp, addBattery
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseSysfsState(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
input string
|
|
||||||
want uint8
|
|
||||||
}{
|
|
||||||
{"Empty", stateEmpty},
|
|
||||||
{"Full", stateFull},
|
|
||||||
{"Charging", stateCharging},
|
|
||||||
{"Discharging", stateDischarging},
|
|
||||||
{"Not charging", stateIdle},
|
|
||||||
{"", stateUnknown},
|
|
||||||
{"SomethingElse", stateUnknown},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
assert.Equal(t, tt.want, parseSysfsState(tt.input), "parseSysfsState(%q)", tt.input)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetBatteryStats_SingleBattery(t *testing.T) {
|
|
||||||
_, addBattery := setupFakeSysfs(t)
|
|
||||||
addBattery("BAT0", "72", "Discharging")
|
|
||||||
|
|
||||||
pct, state, err := GetBatteryStats()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, uint8(72), pct)
|
|
||||||
assert.Equal(t, stateDischarging, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetBatteryStats_MultipleBatteries(t *testing.T) {
|
|
||||||
_, addBattery := setupFakeSysfs(t)
|
|
||||||
addBattery("BAT0", "80", "Charging")
|
|
||||||
addBattery("BAT1", "40", "Charging")
|
|
||||||
|
|
||||||
pct, state, err := GetBatteryStats()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
// average of 80 and 40 = 60
|
|
||||||
assert.EqualValues(t, 60, pct)
|
|
||||||
assert.Equal(t, stateCharging, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetBatteryStats_FullBattery(t *testing.T) {
|
|
||||||
_, addBattery := setupFakeSysfs(t)
|
|
||||||
addBattery("BAT0", "100", "Full")
|
|
||||||
|
|
||||||
pct, state, err := GetBatteryStats()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, uint8(100), pct)
|
|
||||||
assert.Equal(t, stateFull, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetBatteryStats_EmptyBattery(t *testing.T) {
|
|
||||||
_, addBattery := setupFakeSysfs(t)
|
|
||||||
addBattery("BAT0", "0", "Empty")
|
|
||||||
|
|
||||||
pct, state, err := GetBatteryStats()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, uint8(0), pct)
|
|
||||||
assert.Equal(t, stateEmpty, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetBatteryStats_NotCharging(t *testing.T) {
|
|
||||||
_, addBattery := setupFakeSysfs(t)
|
|
||||||
addBattery("BAT0", "80", "Not charging")
|
|
||||||
|
|
||||||
pct, state, err := GetBatteryStats()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, uint8(80), pct)
|
|
||||||
assert.Equal(t, stateIdle, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetBatteryStats_NoBatteries(t *testing.T) {
|
|
||||||
setupFakeSysfs(t) // empty directory, no batteries
|
|
||||||
|
|
||||||
_, _, err := GetBatteryStats()
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetBatteryStats_NonBatterySupplyIgnored(t *testing.T) {
|
|
||||||
tmp, addBattery := setupFakeSysfs(t)
|
|
||||||
|
|
||||||
// Add a real battery
|
|
||||||
addBattery("BAT0", "55", "Charging")
|
|
||||||
|
|
||||||
// Add an AC adapter (type != Battery) - should be ignored
|
|
||||||
acDir := filepath.Join(tmp, "AC0")
|
|
||||||
if err := os.MkdirAll(acDir, 0o755); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := os.WriteFile(filepath.Join(acDir, "type"), []byte("Mains"), 0o644); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pct, state, err := GetBatteryStats()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, uint8(55), pct)
|
|
||||||
assert.Equal(t, stateCharging, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetBatteryStats_InvalidCapacitySkipped(t *testing.T) {
|
|
||||||
tmp, addBattery := setupFakeSysfs(t)
|
|
||||||
|
|
||||||
// One battery with valid capacity
|
|
||||||
addBattery("BAT0", "90", "Discharging")
|
|
||||||
|
|
||||||
// Another with invalid capacity text
|
|
||||||
badDir := filepath.Join(tmp, "BAT1")
|
|
||||||
if err := os.MkdirAll(badDir, 0o755); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := os.WriteFile(filepath.Join(badDir, "type"), []byte("Battery"), 0o644); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := os.WriteFile(filepath.Join(badDir, "capacity"), []byte("not-a-number"), 0o644); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := os.WriteFile(filepath.Join(badDir, "status"), []byte("Discharging"), 0o644); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pct, _, err := GetBatteryStats()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
// Only BAT0 counted
|
|
||||||
assert.Equal(t, uint8(90), pct)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetBatteryStats_UnknownStatusOnly(t *testing.T) {
|
|
||||||
_, addBattery := setupFakeSysfs(t)
|
|
||||||
addBattery("BAT0", "50", "SomethingWeird")
|
|
||||||
|
|
||||||
_, _, err := GetBatteryStats()
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHasReadableBattery_True(t *testing.T) {
|
|
||||||
_, addBattery := setupFakeSysfs(t)
|
|
||||||
addBattery("BAT0", "50", "Charging")
|
|
||||||
|
|
||||||
assert.True(t, HasReadableBattery())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHasReadableBattery_False(t *testing.T) {
|
|
||||||
setupFakeSysfs(t) // no batteries
|
|
||||||
|
|
||||||
assert.False(t, HasReadableBattery())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHasReadableBattery_NoCapacityFile(t *testing.T) {
|
|
||||||
tmp, _ := setupFakeSysfs(t)
|
|
||||||
|
|
||||||
// Battery dir with type file but no capacity file
|
|
||||||
batDir := filepath.Join(tmp, "BAT0")
|
|
||||||
err := os.MkdirAll(batDir, 0o755)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
err = os.WriteFile(filepath.Join(batDir, "type"), []byte("Battery"), 0o644)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.False(t, HasReadableBattery())
|
|
||||||
}
|
|
||||||
@@ -1,298 +0,0 @@
|
|||||||
//go:build windows
|
|
||||||
|
|
||||||
// Most of the Windows battery code is based on
|
|
||||||
// distatus/battery by Karol 'Kenji Takahashi' Woźniak
|
|
||||||
|
|
||||||
package battery
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"log/slog"
|
|
||||||
"math"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
type batteryQueryInformation struct {
|
|
||||||
BatteryTag uint32
|
|
||||||
InformationLevel int32
|
|
||||||
AtRate int32
|
|
||||||
}
|
|
||||||
|
|
||||||
type batteryInformation struct {
|
|
||||||
Capabilities uint32
|
|
||||||
Technology uint8
|
|
||||||
Reserved [3]uint8
|
|
||||||
Chemistry [4]uint8
|
|
||||||
DesignedCapacity uint32
|
|
||||||
FullChargedCapacity uint32
|
|
||||||
DefaultAlert1 uint32
|
|
||||||
DefaultAlert2 uint32
|
|
||||||
CriticalBias uint32
|
|
||||||
CycleCount uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
type batteryWaitStatus struct {
|
|
||||||
BatteryTag uint32
|
|
||||||
Timeout uint32
|
|
||||||
PowerState uint32
|
|
||||||
LowCapacity uint32
|
|
||||||
HighCapacity uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
type batteryStatus struct {
|
|
||||||
PowerState uint32
|
|
||||||
Capacity uint32
|
|
||||||
Voltage uint32
|
|
||||||
Rate int32
|
|
||||||
}
|
|
||||||
|
|
||||||
type winGUID struct {
|
|
||||||
Data1 uint32
|
|
||||||
Data2 uint16
|
|
||||||
Data3 uint16
|
|
||||||
Data4 [8]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type spDeviceInterfaceData struct {
|
|
||||||
cbSize uint32
|
|
||||||
InterfaceClassGuid winGUID
|
|
||||||
Flags uint32
|
|
||||||
Reserved uint
|
|
||||||
}
|
|
||||||
|
|
||||||
var guidDeviceBattery = winGUID{
|
|
||||||
0x72631e54,
|
|
||||||
0x78A4,
|
|
||||||
0x11d0,
|
|
||||||
[8]byte{0xbc, 0xf7, 0x00, 0xaa, 0x00, 0xb7, 0xb3, 0x2a},
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
setupapi = &windows.LazyDLL{Name: "setupapi.dll", System: true}
|
|
||||||
setupDiGetClassDevsW = setupapi.NewProc("SetupDiGetClassDevsW")
|
|
||||||
setupDiEnumDeviceInterfaces = setupapi.NewProc("SetupDiEnumDeviceInterfaces")
|
|
||||||
setupDiGetDeviceInterfaceDetailW = setupapi.NewProc("SetupDiGetDeviceInterfaceDetailW")
|
|
||||||
setupDiDestroyDeviceInfoList = setupapi.NewProc("SetupDiDestroyDeviceInfoList")
|
|
||||||
)
|
|
||||||
|
|
||||||
// winBatteryGet reads one battery by index. Returns (fullCapacity, currentCapacity, state, error).
|
|
||||||
// Returns error == errNotFound when there are no more batteries.
|
|
||||||
var errNotFound = errors.New("no more batteries")
|
|
||||||
|
|
||||||
func setupDiSetup(proc *windows.LazyProc, nargs, a1, a2, a3, a4, a5, a6 uintptr) (uintptr, error) {
|
|
||||||
_ = nargs
|
|
||||||
r1, _, errno := syscall.SyscallN(proc.Addr(), a1, a2, a3, a4, a5, a6)
|
|
||||||
if windows.Handle(r1) == windows.InvalidHandle {
|
|
||||||
if errno != 0 {
|
|
||||||
return 0, error(errno)
|
|
||||||
}
|
|
||||||
return 0, syscall.EINVAL
|
|
||||||
}
|
|
||||||
return r1, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupDiCall(proc *windows.LazyProc, nargs, a1, a2, a3, a4, a5, a6 uintptr) syscall.Errno {
|
|
||||||
_ = nargs
|
|
||||||
r1, _, errno := syscall.SyscallN(proc.Addr(), a1, a2, a3, a4, a5, a6)
|
|
||||||
if r1 == 0 {
|
|
||||||
if errno != 0 {
|
|
||||||
return errno
|
|
||||||
}
|
|
||||||
return syscall.EINVAL
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func readWinBatteryState(powerState uint32) uint8 {
|
|
||||||
switch {
|
|
||||||
case powerState&0x00000004 != 0:
|
|
||||||
return stateCharging
|
|
||||||
case powerState&0x00000008 != 0:
|
|
||||||
return stateEmpty
|
|
||||||
case powerState&0x00000002 != 0:
|
|
||||||
return stateDischarging
|
|
||||||
case powerState&0x00000001 != 0:
|
|
||||||
return stateFull
|
|
||||||
default:
|
|
||||||
return stateUnknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func winBatteryGet(idx int) (full, current uint32, state uint8, err error) {
|
|
||||||
hdev, err := setupDiSetup(
|
|
||||||
setupDiGetClassDevsW,
|
|
||||||
4,
|
|
||||||
uintptr(unsafe.Pointer(&guidDeviceBattery)),
|
|
||||||
0, 0,
|
|
||||||
2|16, // DIGCF_PRESENT|DIGCF_DEVICEINTERFACE
|
|
||||||
0, 0,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, stateUnknown, err
|
|
||||||
}
|
|
||||||
defer syscall.SyscallN(setupDiDestroyDeviceInfoList.Addr(), hdev)
|
|
||||||
|
|
||||||
var did spDeviceInterfaceData
|
|
||||||
did.cbSize = uint32(unsafe.Sizeof(did))
|
|
||||||
errno := setupDiCall(
|
|
||||||
setupDiEnumDeviceInterfaces,
|
|
||||||
5,
|
|
||||||
hdev, 0,
|
|
||||||
uintptr(unsafe.Pointer(&guidDeviceBattery)),
|
|
||||||
uintptr(idx),
|
|
||||||
uintptr(unsafe.Pointer(&did)),
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
if errno == 259 { // ERROR_NO_MORE_ITEMS
|
|
||||||
return 0, 0, stateUnknown, errNotFound
|
|
||||||
}
|
|
||||||
if errno != 0 {
|
|
||||||
return 0, 0, stateUnknown, errno
|
|
||||||
}
|
|
||||||
|
|
||||||
var cbRequired uint32
|
|
||||||
errno = setupDiCall(
|
|
||||||
setupDiGetDeviceInterfaceDetailW,
|
|
||||||
6,
|
|
||||||
hdev,
|
|
||||||
uintptr(unsafe.Pointer(&did)),
|
|
||||||
0, 0,
|
|
||||||
uintptr(unsafe.Pointer(&cbRequired)),
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
if errno != 0 && errno != 122 { // ERROR_INSUFFICIENT_BUFFER
|
|
||||||
return 0, 0, stateUnknown, errno
|
|
||||||
}
|
|
||||||
didd := make([]uint16, cbRequired/2)
|
|
||||||
cbSize := (*uint32)(unsafe.Pointer(&didd[0]))
|
|
||||||
if unsafe.Sizeof(uint(0)) == 8 {
|
|
||||||
*cbSize = 8
|
|
||||||
} else {
|
|
||||||
*cbSize = 6
|
|
||||||
}
|
|
||||||
errno = setupDiCall(
|
|
||||||
setupDiGetDeviceInterfaceDetailW,
|
|
||||||
6,
|
|
||||||
hdev,
|
|
||||||
uintptr(unsafe.Pointer(&did)),
|
|
||||||
uintptr(unsafe.Pointer(&didd[0])),
|
|
||||||
uintptr(cbRequired),
|
|
||||||
uintptr(unsafe.Pointer(&cbRequired)),
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
if errno != 0 {
|
|
||||||
return 0, 0, stateUnknown, errno
|
|
||||||
}
|
|
||||||
devicePath := &didd[2:][0]
|
|
||||||
|
|
||||||
handle, err := windows.CreateFile(
|
|
||||||
devicePath,
|
|
||||||
windows.GENERIC_READ|windows.GENERIC_WRITE,
|
|
||||||
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE,
|
|
||||||
nil,
|
|
||||||
windows.OPEN_EXISTING,
|
|
||||||
windows.FILE_ATTRIBUTE_NORMAL,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, stateUnknown, err
|
|
||||||
}
|
|
||||||
defer windows.CloseHandle(handle)
|
|
||||||
|
|
||||||
var dwOut uint32
|
|
||||||
var dwWait uint32
|
|
||||||
var bqi batteryQueryInformation
|
|
||||||
err = windows.DeviceIoControl(
|
|
||||||
handle,
|
|
||||||
2703424, // IOCTL_BATTERY_QUERY_TAG
|
|
||||||
(*byte)(unsafe.Pointer(&dwWait)),
|
|
||||||
uint32(unsafe.Sizeof(dwWait)),
|
|
||||||
(*byte)(unsafe.Pointer(&bqi.BatteryTag)),
|
|
||||||
uint32(unsafe.Sizeof(bqi.BatteryTag)),
|
|
||||||
&dwOut, nil,
|
|
||||||
)
|
|
||||||
if err != nil || bqi.BatteryTag == 0 {
|
|
||||||
return 0, 0, stateUnknown, errors.New("battery tag not returned")
|
|
||||||
}
|
|
||||||
|
|
||||||
var bi batteryInformation
|
|
||||||
if err = windows.DeviceIoControl(
|
|
||||||
handle,
|
|
||||||
2703428, // IOCTL_BATTERY_QUERY_INFORMATION
|
|
||||||
(*byte)(unsafe.Pointer(&bqi)),
|
|
||||||
uint32(unsafe.Sizeof(bqi)),
|
|
||||||
(*byte)(unsafe.Pointer(&bi)),
|
|
||||||
uint32(unsafe.Sizeof(bi)),
|
|
||||||
&dwOut, nil,
|
|
||||||
); err != nil {
|
|
||||||
return 0, 0, stateUnknown, err
|
|
||||||
}
|
|
||||||
|
|
||||||
bws := batteryWaitStatus{BatteryTag: bqi.BatteryTag}
|
|
||||||
var bs batteryStatus
|
|
||||||
if err = windows.DeviceIoControl(
|
|
||||||
handle,
|
|
||||||
2703436, // IOCTL_BATTERY_QUERY_STATUS
|
|
||||||
(*byte)(unsafe.Pointer(&bws)),
|
|
||||||
uint32(unsafe.Sizeof(bws)),
|
|
||||||
(*byte)(unsafe.Pointer(&bs)),
|
|
||||||
uint32(unsafe.Sizeof(bs)),
|
|
||||||
&dwOut, nil,
|
|
||||||
); err != nil {
|
|
||||||
return 0, 0, stateUnknown, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if bs.Capacity == 0xffffffff { // BATTERY_UNKNOWN_CAPACITY
|
|
||||||
return 0, 0, stateUnknown, errors.New("battery capacity unknown")
|
|
||||||
}
|
|
||||||
|
|
||||||
return bi.FullChargedCapacity, bs.Capacity, readWinBatteryState(bs.PowerState), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasReadableBattery checks if the system has a battery and returns true if it does.
|
|
||||||
var HasReadableBattery = sync.OnceValue(func() bool {
|
|
||||||
systemHasBattery := false
|
|
||||||
full, _, _, err := winBatteryGet(0)
|
|
||||||
if err == nil && full > 0 {
|
|
||||||
systemHasBattery = true
|
|
||||||
}
|
|
||||||
if !systemHasBattery {
|
|
||||||
slog.Debug("No battery found", "err", err)
|
|
||||||
}
|
|
||||||
return systemHasBattery
|
|
||||||
})
|
|
||||||
|
|
||||||
// GetBatteryStats returns the current battery percent and charge state.
|
|
||||||
func GetBatteryStats() (batteryPercent uint8, batteryState uint8, err error) {
|
|
||||||
if !HasReadableBattery() {
|
|
||||||
return batteryPercent, batteryState, errors.ErrUnsupported
|
|
||||||
}
|
|
||||||
|
|
||||||
totalFull := uint32(0)
|
|
||||||
totalCurrent := uint32(0)
|
|
||||||
batteryState = math.MaxUint8
|
|
||||||
|
|
||||||
for i := 0; ; i++ {
|
|
||||||
full, current, state, bErr := winBatteryGet(i)
|
|
||||||
if errors.Is(bErr, errNotFound) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if bErr != nil || full == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
totalFull += full
|
|
||||||
totalCurrent += min(current, full)
|
|
||||||
batteryState = state
|
|
||||||
}
|
|
||||||
|
|
||||||
if totalFull == 0 || batteryState == math.MaxUint8 {
|
|
||||||
return batteryPercent, batteryState, errors.New("no battery capacity")
|
|
||||||
}
|
|
||||||
|
|
||||||
batteryPercent = uint8(float64(totalCurrent) / float64(totalFull) * 100)
|
|
||||||
return batteryPercent, batteryState, nil
|
|
||||||
}
|
|
||||||
111
agent/disk.go
111
agent/disk.go
@@ -1,7 +1,6 @@
|
|||||||
package agent
|
package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -34,34 +33,6 @@ type diskDiscovery struct {
|
|||||||
ctx fsRegistrationContext
|
ctx fsRegistrationContext
|
||||||
}
|
}
|
||||||
|
|
||||||
// prevDisk stores previous per-device disk counters for a given cache interval
|
|
||||||
type prevDisk struct {
|
|
||||||
readBytes uint64
|
|
||||||
writeBytes uint64
|
|
||||||
readTime uint64 // cumulative ms spent on reads (from ReadTime)
|
|
||||||
writeTime uint64 // cumulative ms spent on writes (from WriteTime)
|
|
||||||
ioTime uint64 // cumulative ms spent doing I/O (from IoTime)
|
|
||||||
weightedIO uint64 // cumulative weighted ms (queue-depth × ms, from WeightedIO)
|
|
||||||
readCount uint64 // cumulative read operation count
|
|
||||||
writeCount uint64 // cumulative write operation count
|
|
||||||
at time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// prevDiskFromCounter creates a prevDisk snapshot from a disk.IOCountersStat at time t.
|
|
||||||
func prevDiskFromCounter(d disk.IOCountersStat, t time.Time) prevDisk {
|
|
||||||
return prevDisk{
|
|
||||||
readBytes: d.ReadBytes,
|
|
||||||
writeBytes: d.WriteBytes,
|
|
||||||
readTime: d.ReadTime,
|
|
||||||
writeTime: d.WriteTime,
|
|
||||||
ioTime: d.IoTime,
|
|
||||||
weightedIO: d.WeightedIO,
|
|
||||||
readCount: d.ReadCount,
|
|
||||||
writeCount: d.WriteCount,
|
|
||||||
at: t,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseFilesystemEntry parses a filesystem entry in the format "device__customname"
|
// parseFilesystemEntry parses a filesystem entry in the format "device__customname"
|
||||||
// Returns the device/filesystem part and the custom name part
|
// Returns the device/filesystem part and the custom name part
|
||||||
func parseFilesystemEntry(entry string) (device, customName string) {
|
func parseFilesystemEntry(entry string) (device, customName string) {
|
||||||
@@ -267,11 +238,9 @@ func (d *diskDiscovery) addConfiguredExtraFilesystems(extraFilesystems string) {
|
|||||||
|
|
||||||
// addPartitionExtraFs registers partitions mounted under /extra-filesystems so
|
// addPartitionExtraFs registers partitions mounted under /extra-filesystems so
|
||||||
// their display names can come from the folder name while their I/O keys still
|
// their display names can come from the folder name while their I/O keys still
|
||||||
// prefer the underlying partition device. Only direct children are matched to
|
// prefer the underlying partition device.
|
||||||
// avoid registering nested virtual mounts (e.g. /proc, /sys) that are returned by
|
|
||||||
// disk.Partitions(true) when the host root is bind-mounted in /extra-filesystems.
|
|
||||||
func (d *diskDiscovery) addPartitionExtraFs(p disk.PartitionStat) {
|
func (d *diskDiscovery) addPartitionExtraFs(p disk.PartitionStat) {
|
||||||
if filepath.Dir(p.Mountpoint) != d.ctx.efPath {
|
if !strings.HasPrefix(p.Mountpoint, d.ctx.efPath) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
device, customName := extraFilesystemPartitionInfo(p)
|
device, customName := extraFilesystemPartitionInfo(p)
|
||||||
@@ -304,7 +273,7 @@ func (a *Agent) initializeDiskInfo() {
|
|||||||
hasRoot := false
|
hasRoot := false
|
||||||
isWindows := runtime.GOOS == "windows"
|
isWindows := runtime.GOOS == "windows"
|
||||||
|
|
||||||
partitions, err := disk.PartitionsWithContext(context.Background(), true)
|
partitions, err := disk.Partitions(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Error getting disk partitions", "err", err)
|
slog.Error("Error getting disk partitions", "err", err)
|
||||||
}
|
}
|
||||||
@@ -609,29 +578,16 @@ func (a *Agent) updateDiskIo(cacheTimeMs uint16, systemStats *system.Stats) {
|
|||||||
prev, hasPrev := a.diskPrev[cacheTimeMs][name]
|
prev, hasPrev := a.diskPrev[cacheTimeMs][name]
|
||||||
if !hasPrev {
|
if !hasPrev {
|
||||||
// Seed from agent-level fsStats if present, else seed from current
|
// Seed from agent-level fsStats if present, else seed from current
|
||||||
prev = prevDisk{
|
prev = prevDisk{readBytes: stats.TotalRead, writeBytes: stats.TotalWrite, at: stats.Time}
|
||||||
readBytes: stats.TotalRead,
|
|
||||||
writeBytes: stats.TotalWrite,
|
|
||||||
readTime: d.ReadTime,
|
|
||||||
writeTime: d.WriteTime,
|
|
||||||
ioTime: d.IoTime,
|
|
||||||
weightedIO: d.WeightedIO,
|
|
||||||
readCount: d.ReadCount,
|
|
||||||
writeCount: d.WriteCount,
|
|
||||||
at: stats.Time,
|
|
||||||
}
|
|
||||||
if prev.at.IsZero() {
|
if prev.at.IsZero() {
|
||||||
prev = prevDiskFromCounter(d, now)
|
prev = prevDisk{readBytes: d.ReadBytes, writeBytes: d.WriteBytes, at: now}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
msElapsed := uint64(now.Sub(prev.at).Milliseconds())
|
msElapsed := uint64(now.Sub(prev.at).Milliseconds())
|
||||||
|
|
||||||
// Update per-interval snapshot
|
|
||||||
a.diskPrev[cacheTimeMs][name] = prevDiskFromCounter(d, now)
|
|
||||||
|
|
||||||
// Avoid division by zero or clock issues
|
|
||||||
if msElapsed < 100 {
|
if msElapsed < 100 {
|
||||||
|
// Avoid division by zero or clock issues; update snapshot and continue
|
||||||
|
a.diskPrev[cacheTimeMs][name] = prevDisk{readBytes: d.ReadBytes, writeBytes: d.WriteBytes, at: now}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -643,38 +599,15 @@ func (a *Agent) updateDiskIo(cacheTimeMs uint16, systemStats *system.Stats) {
|
|||||||
// validate values
|
// validate values
|
||||||
if readMbPerSecond > 50_000 || writeMbPerSecond > 50_000 {
|
if readMbPerSecond > 50_000 || writeMbPerSecond > 50_000 {
|
||||||
slog.Warn("Invalid disk I/O. Resetting.", "name", d.Name, "read", readMbPerSecond, "write", writeMbPerSecond)
|
slog.Warn("Invalid disk I/O. Resetting.", "name", d.Name, "read", readMbPerSecond, "write", writeMbPerSecond)
|
||||||
|
// Reset interval snapshot and seed from current
|
||||||
|
a.diskPrev[cacheTimeMs][name] = prevDisk{readBytes: d.ReadBytes, writeBytes: d.WriteBytes, at: now}
|
||||||
// also refresh agent baseline to avoid future negatives
|
// also refresh agent baseline to avoid future negatives
|
||||||
a.initializeDiskIoStats(ioCounters)
|
a.initializeDiskIoStats(ioCounters)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// These properties are calculated differently on different platforms,
|
// Update per-interval snapshot
|
||||||
// but generally represent cumulative time spent doing reads/writes on the device.
|
a.diskPrev[cacheTimeMs][name] = prevDisk{readBytes: d.ReadBytes, writeBytes: d.WriteBytes, at: now}
|
||||||
// This can surpass 100% if there are multiple concurrent I/O operations.
|
|
||||||
// Linux kernel docs:
|
|
||||||
// This is the total number of milliseconds spent by all reads (as
|
|
||||||
// measured from __make_request() to end_that_request_last()).
|
|
||||||
// https://www.kernel.org/doc/Documentation/iostats.txt (fields 4, 8)
|
|
||||||
diskReadTime := utils.TwoDecimals(float64(d.ReadTime-prev.readTime) / float64(msElapsed) * 100)
|
|
||||||
diskWriteTime := utils.TwoDecimals(float64(d.WriteTime-prev.writeTime) / float64(msElapsed) * 100)
|
|
||||||
|
|
||||||
// I/O utilization %: fraction of wall time the device had any I/O in progress (0-100).
|
|
||||||
diskIoUtilPct := utils.TwoDecimals(float64(d.IoTime-prev.ioTime) / float64(msElapsed) * 100)
|
|
||||||
|
|
||||||
// Weighted I/O: queue-depth weighted I/O time, normalized to interval (can exceed 100%).
|
|
||||||
// Linux kernel field 11: incremented by iops_in_progress × ms_since_last_update.
|
|
||||||
// Used to display queue depth. Multipled by 100 to increase accuracy of digit truncation (divided by 100 in UI).
|
|
||||||
diskWeightedIO := utils.TwoDecimals(float64(d.WeightedIO-prev.weightedIO) / float64(msElapsed) * 100)
|
|
||||||
|
|
||||||
// r_await / w_await: average time per read/write operation in milliseconds.
|
|
||||||
// Equivalent to r_await and w_await in iostat.
|
|
||||||
var rAwait, wAwait float64
|
|
||||||
if deltaReadCount := d.ReadCount - prev.readCount; deltaReadCount > 0 {
|
|
||||||
rAwait = utils.TwoDecimals(float64(d.ReadTime-prev.readTime) / float64(deltaReadCount))
|
|
||||||
}
|
|
||||||
if deltaWriteCount := d.WriteCount - prev.writeCount; deltaWriteCount > 0 {
|
|
||||||
wAwait = utils.TwoDecimals(float64(d.WriteTime-prev.writeTime) / float64(deltaWriteCount))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update global fsStats baseline for cross-interval correctness
|
// Update global fsStats baseline for cross-interval correctness
|
||||||
stats.Time = now
|
stats.Time = now
|
||||||
@@ -684,40 +617,20 @@ func (a *Agent) updateDiskIo(cacheTimeMs uint16, systemStats *system.Stats) {
|
|||||||
stats.DiskWritePs = writeMbPerSecond
|
stats.DiskWritePs = writeMbPerSecond
|
||||||
stats.DiskReadBytes = diskIORead
|
stats.DiskReadBytes = diskIORead
|
||||||
stats.DiskWriteBytes = diskIOWrite
|
stats.DiskWriteBytes = diskIOWrite
|
||||||
stats.DiskIoStats[0] = diskReadTime
|
|
||||||
stats.DiskIoStats[1] = diskWriteTime
|
|
||||||
stats.DiskIoStats[2] = diskIoUtilPct
|
|
||||||
stats.DiskIoStats[3] = rAwait
|
|
||||||
stats.DiskIoStats[4] = wAwait
|
|
||||||
stats.DiskIoStats[5] = diskWeightedIO
|
|
||||||
|
|
||||||
if stats.Root {
|
if stats.Root {
|
||||||
systemStats.DiskReadPs = stats.DiskReadPs
|
systemStats.DiskReadPs = stats.DiskReadPs
|
||||||
systemStats.DiskWritePs = stats.DiskWritePs
|
systemStats.DiskWritePs = stats.DiskWritePs
|
||||||
systemStats.DiskIO[0] = diskIORead
|
systemStats.DiskIO[0] = diskIORead
|
||||||
systemStats.DiskIO[1] = diskIOWrite
|
systemStats.DiskIO[1] = diskIOWrite
|
||||||
systemStats.DiskIoStats[0] = diskReadTime
|
|
||||||
systemStats.DiskIoStats[1] = diskWriteTime
|
|
||||||
systemStats.DiskIoStats[2] = diskIoUtilPct
|
|
||||||
systemStats.DiskIoStats[3] = rAwait
|
|
||||||
systemStats.DiskIoStats[4] = wAwait
|
|
||||||
systemStats.DiskIoStats[5] = diskWeightedIO
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getRootMountPoint returns the appropriate root mount point for the system.
|
// getRootMountPoint returns the appropriate root mount point for the system
|
||||||
// On Windows it returns the system drive (e.g. "C:").
|
|
||||||
// For immutable systems like Fedora Silverblue, it returns /sysroot instead of /
|
// For immutable systems like Fedora Silverblue, it returns /sysroot instead of /
|
||||||
func (a *Agent) getRootMountPoint() string {
|
func (a *Agent) getRootMountPoint() string {
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
if sd := os.Getenv("SystemDrive"); sd != "" {
|
|
||||||
return sd
|
|
||||||
}
|
|
||||||
return "C:"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Check if /etc/os-release contains indicators of an immutable system
|
// 1. Check if /etc/os-release contains indicators of an immutable system
|
||||||
if osReleaseContent, err := os.ReadFile("/etc/os-release"); err == nil {
|
if osReleaseContent, err := os.ReadFile("/etc/os-release"); err == nil {
|
||||||
content := string(osReleaseContent)
|
content := string(osReleaseContent)
|
||||||
|
|||||||
@@ -530,87 +530,6 @@ func TestAddExtraFilesystemFolders(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddPartitionExtraFs(t *testing.T) {
|
|
||||||
makeDiscovery := func(agent *Agent) diskDiscovery {
|
|
||||||
return diskDiscovery{
|
|
||||||
agent: agent,
|
|
||||||
ctx: fsRegistrationContext{
|
|
||||||
isWindows: false,
|
|
||||||
efPath: "/extra-filesystems",
|
|
||||||
diskIoCounters: map[string]disk.IOCountersStat{
|
|
||||||
"nvme0n1p1": {Name: "nvme0n1p1"},
|
|
||||||
"nvme1n1": {Name: "nvme1n1"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("registers direct child of extra-filesystems", func(t *testing.T) {
|
|
||||||
agent := &Agent{fsStats: make(map[string]*system.FsStats)}
|
|
||||||
d := makeDiscovery(agent)
|
|
||||||
|
|
||||||
d.addPartitionExtraFs(disk.PartitionStat{
|
|
||||||
Device: "/dev/nvme0n1p1",
|
|
||||||
Mountpoint: "/extra-filesystems/nvme0n1p1__caddy1-root",
|
|
||||||
})
|
|
||||||
|
|
||||||
stats, exists := agent.fsStats["nvme0n1p1"]
|
|
||||||
assert.True(t, exists)
|
|
||||||
assert.Equal(t, "/extra-filesystems/nvme0n1p1__caddy1-root", stats.Mountpoint)
|
|
||||||
assert.Equal(t, "caddy1-root", stats.Name)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("skips nested mount under extra-filesystem bind mount", func(t *testing.T) {
|
|
||||||
agent := &Agent{fsStats: make(map[string]*system.FsStats)}
|
|
||||||
d := makeDiscovery(agent)
|
|
||||||
|
|
||||||
// These simulate the virtual mounts that appear when host / is bind-mounted
|
|
||||||
// with disk.Partitions(all=true) — e.g. /proc, /sys, /dev visible under the mount.
|
|
||||||
for _, nested := range []string{
|
|
||||||
"/extra-filesystems/nvme0n1p1__caddy1-root/proc",
|
|
||||||
"/extra-filesystems/nvme0n1p1__caddy1-root/sys",
|
|
||||||
"/extra-filesystems/nvme0n1p1__caddy1-root/dev",
|
|
||||||
"/extra-filesystems/nvme0n1p1__caddy1-root/run",
|
|
||||||
} {
|
|
||||||
d.addPartitionExtraFs(disk.PartitionStat{Device: "tmpfs", Mountpoint: nested})
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Empty(t, agent.fsStats)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("registers both direct children, skips their nested mounts", func(t *testing.T) {
|
|
||||||
agent := &Agent{fsStats: make(map[string]*system.FsStats)}
|
|
||||||
d := makeDiscovery(agent)
|
|
||||||
|
|
||||||
partitions := []disk.PartitionStat{
|
|
||||||
{Device: "/dev/nvme0n1p1", Mountpoint: "/extra-filesystems/nvme0n1p1__caddy1-root"},
|
|
||||||
{Device: "/dev/nvme1n1", Mountpoint: "/extra-filesystems/nvme1n1__caddy1-docker"},
|
|
||||||
{Device: "proc", Mountpoint: "/extra-filesystems/nvme0n1p1__caddy1-root/proc"},
|
|
||||||
{Device: "sysfs", Mountpoint: "/extra-filesystems/nvme0n1p1__caddy1-root/sys"},
|
|
||||||
{Device: "overlay", Mountpoint: "/extra-filesystems/nvme0n1p1__caddy1-root/var/lib/docker"},
|
|
||||||
}
|
|
||||||
for _, p := range partitions {
|
|
||||||
d.addPartitionExtraFs(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Len(t, agent.fsStats, 2)
|
|
||||||
assert.Equal(t, "caddy1-root", agent.fsStats["nvme0n1p1"].Name)
|
|
||||||
assert.Equal(t, "caddy1-docker", agent.fsStats["nvme1n1"].Name)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("skips partition not under extra-filesystems", func(t *testing.T) {
|
|
||||||
agent := &Agent{fsStats: make(map[string]*system.FsStats)}
|
|
||||||
d := makeDiscovery(agent)
|
|
||||||
|
|
||||||
d.addPartitionExtraFs(disk.PartitionStat{
|
|
||||||
Device: "/dev/nvme0n1p1",
|
|
||||||
Mountpoint: "/",
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.Empty(t, agent.fsStats)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindIoDevice(t *testing.T) {
|
func TestFindIoDevice(t *testing.T) {
|
||||||
t.Run("matches by device name", func(t *testing.T) {
|
t.Run("matches by device name", func(t *testing.T) {
|
||||||
ioCounters := map[string]disk.IOCountersStat{
|
ioCounters := map[string]disk.IOCountersStat{
|
||||||
|
|||||||
132
agent/docker.go
132
agent/docker.go
@@ -25,7 +25,6 @@ import (
|
|||||||
"github.com/henrygd/beszel/agent/deltatracker"
|
"github.com/henrygd/beszel/agent/deltatracker"
|
||||||
"github.com/henrygd/beszel/agent/utils"
|
"github.com/henrygd/beszel/agent/utils"
|
||||||
"github.com/henrygd/beszel/internal/entities/container"
|
"github.com/henrygd/beszel/internal/entities/container"
|
||||||
"github.com/henrygd/beszel/internal/entities/system"
|
|
||||||
|
|
||||||
"github.com/blang/semver"
|
"github.com/blang/semver"
|
||||||
)
|
)
|
||||||
@@ -53,7 +52,6 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type dockerManager struct {
|
type dockerManager struct {
|
||||||
agent *Agent // Used to propagate system detail changes back to the agent
|
|
||||||
client *http.Client // Client to query Docker API
|
client *http.Client // Client to query Docker API
|
||||||
wg sync.WaitGroup // WaitGroup to wait for all goroutines to finish
|
wg sync.WaitGroup // WaitGroup to wait for all goroutines to finish
|
||||||
sem chan struct{} // Semaphore to limit concurrent container requests
|
sem chan struct{} // Semaphore to limit concurrent container requests
|
||||||
@@ -62,7 +60,6 @@ type dockerManager struct {
|
|||||||
containerStatsMap map[string]*container.Stats // Keeps track of container stats
|
containerStatsMap map[string]*container.Stats // Keeps track of container stats
|
||||||
validIds map[string]struct{} // Map of valid container ids, used to prune invalid containers from containerStatsMap
|
validIds map[string]struct{} // Map of valid container ids, used to prune invalid containers from containerStatsMap
|
||||||
goodDockerVersion bool // Whether docker version is at least 25.0.0 (one-shot works correctly)
|
goodDockerVersion bool // Whether docker version is at least 25.0.0 (one-shot works correctly)
|
||||||
dockerVersionChecked bool // Whether a version probe has completed successfully
|
|
||||||
isWindows bool // Whether the Docker Engine API is running on Windows
|
isWindows bool // Whether the Docker Engine API is running on Windows
|
||||||
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
|
||||||
@@ -81,6 +78,7 @@ type dockerManager struct {
|
|||||||
networkSentTrackers map[uint16]*deltatracker.DeltaTracker[string, uint64]
|
networkSentTrackers map[uint16]*deltatracker.DeltaTracker[string, uint64]
|
||||||
networkRecvTrackers map[uint16]*deltatracker.DeltaTracker[string, uint64]
|
networkRecvTrackers map[uint16]*deltatracker.DeltaTracker[string, uint64]
|
||||||
lastNetworkReadTime map[uint16]map[string]time.Time // cacheTimeMs -> containerId -> last network read time
|
lastNetworkReadTime map[uint16]map[string]time.Time // cacheTimeMs -> containerId -> last network read time
|
||||||
|
retrySleep func(time.Duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
// userAgentRoundTripper is a custom http.RoundTripper that adds a User-Agent header to all requests
|
// userAgentRoundTripper is a custom http.RoundTripper that adds a User-Agent header to all requests
|
||||||
@@ -89,14 +87,6 @@ type userAgentRoundTripper struct {
|
|||||||
userAgent string
|
userAgent string
|
||||||
}
|
}
|
||||||
|
|
||||||
// dockerVersionResponse contains the /version fields used for engine checks.
|
|
||||||
type dockerVersionResponse struct {
|
|
||||||
Version string `json:"Version"`
|
|
||||||
Components []struct {
|
|
||||||
Name string `json:"Name"`
|
|
||||||
} `json:"Components"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoundTrip implements the http.RoundTripper interface
|
// RoundTrip implements the http.RoundTripper interface
|
||||||
func (u *userAgentRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
func (u *userAgentRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
req.Header.Set("User-Agent", u.userAgent)
|
req.Header.Set("User-Agent", u.userAgent)
|
||||||
@@ -144,14 +134,7 @@ func (dm *dockerManager) getDockerStats(cacheTimeMs uint16) ([]*container.Stats,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect Podman and Windows from Server header
|
dm.isWindows = strings.Contains(resp.Header.Get("Server"), "windows")
|
||||||
serverHeader := resp.Header.Get("Server")
|
|
||||||
if !dm.usingPodman && detectPodmanFromHeader(serverHeader) {
|
|
||||||
dm.setIsPodman()
|
|
||||||
}
|
|
||||||
dm.isWindows = strings.Contains(serverHeader, "windows")
|
|
||||||
|
|
||||||
dm.ensureDockerVersionChecked()
|
|
||||||
|
|
||||||
containersLength := len(dm.apiContainerList)
|
containersLength := len(dm.apiContainerList)
|
||||||
|
|
||||||
@@ -605,7 +588,7 @@ func (dm *dockerManager) deleteContainerStatsSync(id string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new http client for Docker or Podman API
|
// Creates a new http client for Docker or Podman API
|
||||||
func newDockerManager(agent *Agent) *dockerManager {
|
func newDockerManager() *dockerManager {
|
||||||
dockerHost, exists := utils.GetEnv("DOCKER_HOST")
|
dockerHost, exists := utils.GetEnv("DOCKER_HOST")
|
||||||
if exists {
|
if exists {
|
||||||
// return nil if set to empty string
|
// return nil if set to empty string
|
||||||
@@ -671,7 +654,6 @@ func newDockerManager(agent *Agent) *dockerManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
manager := &dockerManager{
|
manager := &dockerManager{
|
||||||
agent: agent,
|
|
||||||
client: &http.Client{
|
client: &http.Client{
|
||||||
Timeout: timeout,
|
Timeout: timeout,
|
||||||
Transport: userAgentTransport,
|
Transport: userAgentTransport,
|
||||||
@@ -689,54 +671,51 @@ func newDockerManager(agent *Agent) *dockerManager {
|
|||||||
networkSentTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
|
networkSentTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
|
||||||
networkRecvTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
|
networkRecvTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
|
||||||
lastNetworkReadTime: make(map[uint16]map[string]time.Time),
|
lastNetworkReadTime: make(map[uint16]map[string]time.Time),
|
||||||
|
retrySleep: time.Sleep,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Best-effort startup probe. If the engine is not ready yet, getDockerStats will
|
// If using podman, return client
|
||||||
// retry after the first successful /containers/json request.
|
if strings.Contains(dockerHost, "podman") {
|
||||||
_, _ = manager.checkDockerVersion()
|
manager.usingPodman = true
|
||||||
|
manager.goodDockerVersion = true
|
||||||
|
return manager
|
||||||
|
}
|
||||||
|
|
||||||
|
// run version check in goroutine to avoid blocking (server may not be ready and requires retries)
|
||||||
|
go manager.checkDockerVersion()
|
||||||
|
|
||||||
|
// give version check a chance to complete before returning
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
return manager
|
return manager
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkDockerVersion checks Docker version and sets goodDockerVersion if at least 25.0.0.
|
// checkDockerVersion checks Docker version and sets goodDockerVersion if at least 25.0.0.
|
||||||
// Versions before 25.0.0 have a bug with one-shot which requires all requests to be made in one batch.
|
// Versions before 25.0.0 have a bug with one-shot which requires all requests to be made in one batch.
|
||||||
func (dm *dockerManager) checkDockerVersion() (bool, error) {
|
func (dm *dockerManager) checkDockerVersion() {
|
||||||
resp, err := dm.client.Get("http://localhost/version")
|
var err error
|
||||||
if err != nil {
|
var resp *http.Response
|
||||||
return false, err
|
var versionInfo struct {
|
||||||
|
Version string `json:"Version"`
|
||||||
}
|
}
|
||||||
if resp.StatusCode != http.StatusOK {
|
const versionMaxTries = 2
|
||||||
status := resp.Status
|
for i := 1; i <= versionMaxTries; i++ {
|
||||||
|
resp, err = dm.client.Get("http://localhost/version")
|
||||||
|
if err == nil && resp.StatusCode == http.StatusOK {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if resp != nil {
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
return false, fmt.Errorf("docker version request failed: %s", status)
|
|
||||||
}
|
}
|
||||||
|
if i < versionMaxTries {
|
||||||
var versionInfo dockerVersionResponse
|
slog.Debug("Failed to get Docker version; retrying", "attempt", i, "err", err, "response", resp)
|
||||||
serverHeader := resp.Header.Get("Server")
|
dm.retrySleep(5 * time.Second)
|
||||||
if err := dm.decode(resp, &versionInfo); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
dm.applyDockerVersionInfo(serverHeader, &versionInfo)
|
if err != nil || resp.StatusCode != http.StatusOK {
|
||||||
dm.dockerVersionChecked = true
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensureDockerVersionChecked retries the version probe after a successful
|
|
||||||
// container list request.
|
|
||||||
func (dm *dockerManager) ensureDockerVersionChecked() {
|
|
||||||
if dm.dockerVersionChecked {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, err := dm.checkDockerVersion(); err != nil {
|
if err := dm.decode(resp, &versionInfo); err != nil {
|
||||||
slog.Debug("Failed to get Docker version", "err", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// applyDockerVersionInfo updates version-dependent behavior from engine metadata.
|
|
||||||
func (dm *dockerManager) applyDockerVersionInfo(serverHeader string, versionInfo *dockerVersionResponse) {
|
|
||||||
if detectPodmanEngine(serverHeader, versionInfo) {
|
|
||||||
dm.setIsPodman()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// if version > 24, one-shot works correctly and we can limit concurrent operations
|
// if version > 24, one-shot works correctly and we can limit concurrent operations
|
||||||
@@ -962,46 +941,3 @@ func (dm *dockerManager) GetHostInfo() (info container.HostInfo, err error) {
|
|||||||
func (dm *dockerManager) IsPodman() bool {
|
func (dm *dockerManager) IsPodman() bool {
|
||||||
return dm.usingPodman
|
return dm.usingPodman
|
||||||
}
|
}
|
||||||
|
|
||||||
// setIsPodman sets the manager to Podman mode and updates system details accordingly.
|
|
||||||
func (dm *dockerManager) setIsPodman() {
|
|
||||||
if dm.usingPodman {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dm.usingPodman = true
|
|
||||||
dm.goodDockerVersion = true
|
|
||||||
dm.dockerVersionChecked = true
|
|
||||||
// keep system details updated - this may be detected late if server isn't ready when
|
|
||||||
// agent starts, so make sure we notify the hub if this happens later.
|
|
||||||
if dm.agent != nil {
|
|
||||||
dm.agent.updateSystemDetails(func(details *system.Details) {
|
|
||||||
details.Podman = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// detectPodmanFromHeader identifies Podman from the Docker API server header.
|
|
||||||
func detectPodmanFromHeader(server string) bool {
|
|
||||||
return strings.HasPrefix(server, "Libpod")
|
|
||||||
}
|
|
||||||
|
|
||||||
// detectPodmanFromVersion identifies Podman from the version payload.
|
|
||||||
func detectPodmanFromVersion(versionInfo *dockerVersionResponse) bool {
|
|
||||||
if versionInfo == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, component := range versionInfo.Components {
|
|
||||||
if strings.HasPrefix(component.Name, "Podman") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// detectPodmanEngine checks both header and version metadata for Podman.
|
|
||||||
func detectPodmanEngine(serverHeader string, versionInfo *dockerVersionResponse) bool {
|
|
||||||
if detectPodmanFromHeader(serverHeader) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return detectPodmanFromVersion(versionInfo)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -540,52 +540,58 @@ func TestDockerManagerCreation(t *testing.T) {
|
|||||||
func TestCheckDockerVersion(t *testing.T) {
|
func TestCheckDockerVersion(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
responses []struct {
|
||||||
statusCode int
|
statusCode int
|
||||||
body string
|
body string
|
||||||
server string
|
}
|
||||||
expectSuccess bool
|
|
||||||
expectedGood bool
|
expectedGood bool
|
||||||
expectedPodman bool
|
expectedRequests int
|
||||||
expectError bool
|
|
||||||
expectedRequest string
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "good docker version",
|
name: "200 with good version on first try",
|
||||||
statusCode: http.StatusOK,
|
responses: []struct {
|
||||||
body: `{"Version":"25.0.1"}`,
|
statusCode int
|
||||||
expectSuccess: true,
|
body string
|
||||||
|
}{
|
||||||
|
{http.StatusOK, `{"Version":"25.0.1"}`},
|
||||||
|
},
|
||||||
expectedGood: true,
|
expectedGood: true,
|
||||||
expectedPodman: false,
|
expectedRequests: 1,
|
||||||
expectedRequest: "/version",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "old docker version",
|
name: "200 with old version on first try",
|
||||||
statusCode: http.StatusOK,
|
responses: []struct {
|
||||||
body: `{"Version":"24.0.7"}`,
|
statusCode int
|
||||||
expectSuccess: true,
|
body string
|
||||||
|
}{
|
||||||
|
{http.StatusOK, `{"Version":"24.0.7"}`},
|
||||||
|
},
|
||||||
expectedGood: false,
|
expectedGood: false,
|
||||||
expectedPodman: false,
|
expectedRequests: 1,
|
||||||
expectedRequest: "/version",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "podman from server header",
|
name: "non-200 then 200 with good version",
|
||||||
statusCode: http.StatusOK,
|
responses: []struct {
|
||||||
body: `{"Version":"5.5.0"}`,
|
statusCode int
|
||||||
server: "Libpod/5.5.0",
|
body string
|
||||||
expectSuccess: true,
|
}{
|
||||||
|
{http.StatusServiceUnavailable, `"not ready"`},
|
||||||
|
{http.StatusOK, `{"Version":"25.1.0"}`},
|
||||||
|
},
|
||||||
expectedGood: true,
|
expectedGood: true,
|
||||||
expectedPodman: true,
|
expectedRequests: 2,
|
||||||
expectedRequest: "/version",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "non-200 response",
|
name: "non-200 on all retries",
|
||||||
statusCode: http.StatusServiceUnavailable,
|
responses: []struct {
|
||||||
body: `"not ready"`,
|
statusCode int
|
||||||
expectSuccess: false,
|
body string
|
||||||
|
}{
|
||||||
|
{http.StatusInternalServerError, `"error"`},
|
||||||
|
{http.StatusUnauthorized, `"error"`},
|
||||||
|
},
|
||||||
expectedGood: false,
|
expectedGood: false,
|
||||||
expectedPodman: false,
|
expectedRequests: 2,
|
||||||
expectError: true,
|
|
||||||
expectedRequest: "/version",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -593,13 +599,13 @@ func TestCheckDockerVersion(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
requestCount := 0
|
requestCount := 0
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
idx := requestCount
|
||||||
requestCount++
|
requestCount++
|
||||||
assert.Equal(t, tt.expectedRequest, r.URL.EscapedPath())
|
if idx >= len(tt.responses) {
|
||||||
if tt.server != "" {
|
idx = len(tt.responses) - 1
|
||||||
w.Header().Set("Server", tt.server)
|
|
||||||
}
|
}
|
||||||
w.WriteHeader(tt.statusCode)
|
w.WriteHeader(tt.responses[idx].statusCode)
|
||||||
fmt.Fprint(w, tt.body)
|
fmt.Fprint(w, tt.responses[idx].body)
|
||||||
}))
|
}))
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
@@ -611,24 +617,17 @@ func TestCheckDockerVersion(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
retrySleep: func(time.Duration) {},
|
||||||
}
|
}
|
||||||
|
|
||||||
success, err := dm.checkDockerVersion()
|
dm.checkDockerVersion()
|
||||||
|
|
||||||
assert.Equal(t, tt.expectSuccess, success)
|
|
||||||
assert.Equal(t, tt.expectSuccess, dm.dockerVersionChecked)
|
|
||||||
assert.Equal(t, tt.expectedGood, dm.goodDockerVersion)
|
assert.Equal(t, tt.expectedGood, dm.goodDockerVersion)
|
||||||
assert.Equal(t, tt.expectedPodman, dm.usingPodman)
|
assert.Equal(t, tt.expectedRequests, requestCount)
|
||||||
assert.Equal(t, 1, requestCount)
|
|
||||||
if tt.expectError {
|
|
||||||
require.Error(t, err)
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("request error", func(t *testing.T) {
|
t.Run("request error on all retries", func(t *testing.T) {
|
||||||
requestCount := 0
|
requestCount := 0
|
||||||
dm := &dockerManager{
|
dm := &dockerManager{
|
||||||
client: &http.Client{
|
client: &http.Client{
|
||||||
@@ -639,171 +638,16 @@ func TestCheckDockerVersion(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
retrySleep: func(time.Duration) {},
|
||||||
}
|
}
|
||||||
|
|
||||||
success, err := dm.checkDockerVersion()
|
dm.checkDockerVersion()
|
||||||
|
|
||||||
assert.False(t, success)
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.False(t, dm.dockerVersionChecked)
|
|
||||||
assert.False(t, dm.goodDockerVersion)
|
assert.False(t, dm.goodDockerVersion)
|
||||||
assert.False(t, dm.usingPodman)
|
assert.Equal(t, 2, requestCount)
|
||||||
assert.Equal(t, 1, requestCount)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// newDockerManagerForVersionTest creates a dockerManager wired to a test server.
|
|
||||||
func newDockerManagerForVersionTest(server *httptest.Server) *dockerManager {
|
|
||||||
return &dockerManager{
|
|
||||||
client: &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
DialContext: func(_ context.Context, network, _ string) (net.Conn, error) {
|
|
||||||
return net.Dial(network, server.Listener.Addr().String())
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
containerStatsMap: make(map[string]*container.Stats),
|
|
||||||
lastCpuContainer: make(map[uint16]map[string]uint64),
|
|
||||||
lastCpuSystem: make(map[uint16]map[string]uint64),
|
|
||||||
lastCpuReadTime: make(map[uint16]map[string]time.Time),
|
|
||||||
networkSentTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
|
|
||||||
networkRecvTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
|
|
||||||
lastNetworkReadTime: make(map[uint16]map[string]time.Time),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetDockerStatsChecksDockerVersionAfterContainerList(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
containerServer string
|
|
||||||
versionServer string
|
|
||||||
versionBody string
|
|
||||||
expectedGood bool
|
|
||||||
expectedPodman bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "200 with good version on first try",
|
|
||||||
versionBody: `{"Version":"25.0.1"}`,
|
|
||||||
expectedGood: true,
|
|
||||||
expectedPodman: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "200 with old version on first try",
|
|
||||||
versionBody: `{"Version":"24.0.7"}`,
|
|
||||||
expectedGood: false,
|
|
||||||
expectedPodman: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "podman detected from server header",
|
|
||||||
containerServer: "Libpod/5.5.0",
|
|
||||||
expectedGood: true,
|
|
||||||
expectedPodman: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
requestCounts := map[string]int{}
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
requestCounts[r.URL.EscapedPath()]++
|
|
||||||
switch r.URL.EscapedPath() {
|
|
||||||
case "/containers/json":
|
|
||||||
if tt.containerServer != "" {
|
|
||||||
w.Header().Set("Server", tt.containerServer)
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
fmt.Fprint(w, `[]`)
|
|
||||||
case "/version":
|
|
||||||
if tt.versionServer != "" {
|
|
||||||
w.Header().Set("Server", tt.versionServer)
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
fmt.Fprint(w, tt.versionBody)
|
|
||||||
default:
|
|
||||||
t.Fatalf("unexpected path: %s", r.URL.EscapedPath())
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
dm := newDockerManagerForVersionTest(server)
|
|
||||||
|
|
||||||
stats, err := dm.getDockerStats(defaultCacheTimeMs)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Empty(t, stats)
|
|
||||||
assert.True(t, dm.dockerVersionChecked)
|
|
||||||
assert.Equal(t, tt.expectedGood, dm.goodDockerVersion)
|
|
||||||
assert.Equal(t, tt.expectedPodman, dm.usingPodman)
|
|
||||||
assert.Equal(t, 1, requestCounts["/containers/json"])
|
|
||||||
if tt.expectedPodman {
|
|
||||||
assert.Equal(t, 0, requestCounts["/version"])
|
|
||||||
} else {
|
|
||||||
assert.Equal(t, 1, requestCounts["/version"])
|
|
||||||
}
|
|
||||||
|
|
||||||
stats, err = dm.getDockerStats(defaultCacheTimeMs)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Empty(t, stats)
|
|
||||||
assert.Equal(t, tt.expectedGood, dm.goodDockerVersion)
|
|
||||||
assert.Equal(t, tt.expectedPodman, dm.usingPodman)
|
|
||||||
assert.Equal(t, 2, requestCounts["/containers/json"])
|
|
||||||
if tt.expectedPodman {
|
|
||||||
assert.Equal(t, 0, requestCounts["/version"])
|
|
||||||
} else {
|
|
||||||
assert.Equal(t, 1, requestCounts["/version"])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetDockerStatsRetriesVersionCheckUntilSuccess(t *testing.T) {
|
|
||||||
requestCounts := map[string]int{}
|
|
||||||
versionStatuses := []int{http.StatusServiceUnavailable, http.StatusOK}
|
|
||||||
versionBodies := []string{`"not ready"`, `{"Version":"25.1.0"}`}
|
|
||||||
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
requestCounts[r.URL.EscapedPath()]++
|
|
||||||
switch r.URL.EscapedPath() {
|
|
||||||
case "/containers/json":
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
fmt.Fprint(w, `[]`)
|
|
||||||
case "/version":
|
|
||||||
idx := requestCounts["/version"] - 1
|
|
||||||
if idx >= len(versionStatuses) {
|
|
||||||
idx = len(versionStatuses) - 1
|
|
||||||
}
|
|
||||||
w.WriteHeader(versionStatuses[idx])
|
|
||||||
fmt.Fprint(w, versionBodies[idx])
|
|
||||||
default:
|
|
||||||
t.Fatalf("unexpected path: %s", r.URL.EscapedPath())
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
dm := newDockerManagerForVersionTest(server)
|
|
||||||
|
|
||||||
stats, err := dm.getDockerStats(defaultCacheTimeMs)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Empty(t, stats)
|
|
||||||
assert.False(t, dm.dockerVersionChecked)
|
|
||||||
assert.False(t, dm.goodDockerVersion)
|
|
||||||
assert.Equal(t, 1, requestCounts["/version"])
|
|
||||||
|
|
||||||
stats, err = dm.getDockerStats(defaultCacheTimeMs)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Empty(t, stats)
|
|
||||||
assert.True(t, dm.dockerVersionChecked)
|
|
||||||
assert.True(t, dm.goodDockerVersion)
|
|
||||||
assert.Equal(t, 2, requestCounts["/containers/json"])
|
|
||||||
assert.Equal(t, 2, requestCounts["/version"])
|
|
||||||
|
|
||||||
stats, err = dm.getDockerStats(defaultCacheTimeMs)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Empty(t, stats)
|
|
||||||
assert.Equal(t, 3, requestCounts["/containers/json"])
|
|
||||||
assert.Equal(t, 2, requestCounts["/version"])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCycleCpuDeltas(t *testing.T) {
|
func TestCycleCpuDeltas(t *testing.T) {
|
||||||
dm := &dockerManager{
|
dm := &dockerManager{
|
||||||
lastCpuContainer: map[uint16]map[string]uint64{
|
lastCpuContainer: map[uint16]map[string]uint64{
|
||||||
|
|||||||
11
agent/gpu.go
11
agent/gpu.go
@@ -542,7 +542,7 @@ func (gm *GPUManager) collectorDefinitions(caps gpuCapabilities) map[collectorSo
|
|||||||
return map[collectorSource]collectorDefinition{
|
return map[collectorSource]collectorDefinition{
|
||||||
collectorSourceNVML: {
|
collectorSourceNVML: {
|
||||||
group: collectorGroupNvidia,
|
group: collectorGroupNvidia,
|
||||||
available: true,
|
available: caps.hasNvidiaSmi,
|
||||||
start: func(_ func()) bool {
|
start: func(_ func()) bool {
|
||||||
return gm.startNvmlCollector()
|
return gm.startNvmlCollector()
|
||||||
},
|
},
|
||||||
@@ -734,6 +734,9 @@ func NewGPUManager() (*GPUManager, error) {
|
|||||||
}
|
}
|
||||||
var gm GPUManager
|
var gm GPUManager
|
||||||
caps := gm.discoverGpuCapabilities()
|
caps := gm.discoverGpuCapabilities()
|
||||||
|
if !hasAnyGpuCollector(caps) {
|
||||||
|
return nil, fmt.Errorf(noGPUFoundMsg)
|
||||||
|
}
|
||||||
gm.GpuDataMap = make(map[string]*system.GPUData)
|
gm.GpuDataMap = make(map[string]*system.GPUData)
|
||||||
|
|
||||||
// Jetson devices should always use tegrastats (ignore GPU_COLLECTOR).
|
// Jetson devices should always use tegrastats (ignore GPU_COLLECTOR).
|
||||||
@@ -742,7 +745,7 @@ func NewGPUManager() (*GPUManager, error) {
|
|||||||
return &gm, nil
|
return &gm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Respect explicit collector selection before capability auto-detection.
|
// if GPU_COLLECTOR is set, start user-defined collectors.
|
||||||
if collectorConfig, ok := utils.GetEnv("GPU_COLLECTOR"); ok && strings.TrimSpace(collectorConfig) != "" {
|
if collectorConfig, ok := utils.GetEnv("GPU_COLLECTOR"); ok && strings.TrimSpace(collectorConfig) != "" {
|
||||||
priorities := parseCollectorPriority(collectorConfig)
|
priorities := parseCollectorPriority(collectorConfig)
|
||||||
if gm.startCollectorsByPriority(priorities, caps) == 0 {
|
if gm.startCollectorsByPriority(priorities, caps) == 0 {
|
||||||
@@ -751,10 +754,6 @@ func NewGPUManager() (*GPUManager, error) {
|
|||||||
return &gm, nil
|
return &gm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasAnyGpuCollector(caps) {
|
|
||||||
return nil, fmt.Errorf(noGPUFoundMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// auto-detect and start collectors when GPU_COLLECTOR is unset.
|
// auto-detect and start collectors when GPU_COLLECTOR is unset.
|
||||||
if gm.startCollectorsByPriority(gm.resolveLegacyCollectorPriority(caps), caps) == 0 {
|
if gm.startCollectorsByPriority(gm.resolveLegacyCollectorPriority(caps), caps) == 0 {
|
||||||
return nil, fmt.Errorf(noGPUFoundMsg)
|
return nil, fmt.Errorf(noGPUFoundMsg)
|
||||||
|
|||||||
@@ -1461,25 +1461,6 @@ func TestNewGPUManagerConfiguredCollectorsMustStart(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCollectorDefinitionsNvmlDoesNotRequireNvidiaSmi(t *testing.T) {
|
|
||||||
gm := &GPUManager{}
|
|
||||||
definitions := gm.collectorDefinitions(gpuCapabilities{})
|
|
||||||
require.Contains(t, definitions, collectorSourceNVML)
|
|
||||||
assert.True(t, definitions[collectorSourceNVML].available)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewGPUManagerConfiguredNvmlBypassesCapabilityGate(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
t.Setenv("PATH", dir)
|
|
||||||
t.Setenv("BESZEL_AGENT_GPU_COLLECTOR", "nvml")
|
|
||||||
|
|
||||||
gm, err := NewGPUManager()
|
|
||||||
require.Nil(t, gm)
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Contains(t, err.Error(), "no configured GPU collectors are available")
|
|
||||||
assert.NotContains(t, err.Error(), noGPUFoundMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewGPUManagerJetsonIgnoresCollectorConfig(t *testing.T) {
|
func TestNewGPUManagerJetsonIgnoresCollectorConfig(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
t.Setenv("PATH", dir)
|
t.Setenv("PATH", dir)
|
||||||
|
|||||||
@@ -8,6 +8,6 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="LibreHardwareMonitorLib" Version="0.9.6" />
|
<PackageReference Include="LibreHardwareMonitorLib" Version="0.9.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -19,20 +19,13 @@ import (
|
|||||||
"github.com/shirou/gopsutil/v4/sensors"
|
"github.com/shirou/gopsutil/v4/sensors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errTemperatureFetchTimeout = errors.New("temperature collection timed out")
|
|
||||||
|
|
||||||
// Matches sensors.TemperaturesWithContext to allow for panic recovery (gopsutil/issues/1832)
|
|
||||||
type getTempsFn func(ctx context.Context) ([]sensors.TemperatureStat, error)
|
|
||||||
|
|
||||||
type SensorConfig struct {
|
type SensorConfig struct {
|
||||||
context context.Context
|
context context.Context
|
||||||
sensors map[string]struct{}
|
sensors map[string]struct{}
|
||||||
primarySensor string
|
primarySensor string
|
||||||
timeout time.Duration
|
|
||||||
isBlacklist bool
|
isBlacklist bool
|
||||||
hasWildcards bool
|
hasWildcards bool
|
||||||
skipCollection bool
|
skipCollection bool
|
||||||
firstRun bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) newSensorConfig() *SensorConfig {
|
func (a *Agent) newSensorConfig() *SensorConfig {
|
||||||
@@ -40,29 +33,25 @@ func (a *Agent) newSensorConfig() *SensorConfig {
|
|||||||
sysSensors, _ := utils.GetEnv("SYS_SENSORS")
|
sysSensors, _ := utils.GetEnv("SYS_SENSORS")
|
||||||
sensorsEnvVal, sensorsSet := utils.GetEnv("SENSORS")
|
sensorsEnvVal, sensorsSet := utils.GetEnv("SENSORS")
|
||||||
skipCollection := sensorsSet && sensorsEnvVal == ""
|
skipCollection := sensorsSet && sensorsEnvVal == ""
|
||||||
sensorsTimeout, _ := utils.GetEnv("SENSORS_TIMEOUT")
|
|
||||||
|
|
||||||
return a.newSensorConfigWithEnv(primarySensor, sysSensors, sensorsEnvVal, sensorsTimeout, skipCollection)
|
return a.newSensorConfigWithEnv(primarySensor, sysSensors, sensorsEnvVal, skipCollection)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Matches sensors.TemperaturesWithContext to allow for panic recovery (gopsutil/issues/1832)
|
||||||
|
type getTempsFn func(ctx context.Context) ([]sensors.TemperatureStat, error)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errTemperatureFetchTimeout = errors.New("temperature collection timed out")
|
||||||
|
temperatureFetchTimeout = 2 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
// newSensorConfigWithEnv creates a SensorConfig with the provided environment variables
|
// newSensorConfigWithEnv creates a SensorConfig with the provided environment variables
|
||||||
// sensorsSet indicates if the SENSORS environment variable was explicitly set (even to empty string)
|
// sensorsSet indicates if the SENSORS environment variable was explicitly set (even to empty string)
|
||||||
func (a *Agent) newSensorConfigWithEnv(primarySensor, sysSensors, sensorsEnvVal, sensorsTimeout string, skipCollection bool) *SensorConfig {
|
func (a *Agent) newSensorConfigWithEnv(primarySensor, sysSensors, sensorsEnvVal string, skipCollection bool) *SensorConfig {
|
||||||
timeout := 2 * time.Second
|
|
||||||
if sensorsTimeout != "" {
|
|
||||||
if d, err := time.ParseDuration(sensorsTimeout); err == nil {
|
|
||||||
timeout = d
|
|
||||||
} else {
|
|
||||||
slog.Warn("Invalid SENSORS_TIMEOUT", "value", sensorsTimeout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &SensorConfig{
|
config := &SensorConfig{
|
||||||
context: context.Background(),
|
context: context.Background(),
|
||||||
primarySensor: primarySensor,
|
primarySensor: primarySensor,
|
||||||
timeout: timeout,
|
|
||||||
skipCollection: skipCollection,
|
skipCollection: skipCollection,
|
||||||
firstRun: true,
|
|
||||||
sensors: make(map[string]struct{}),
|
sensors: make(map[string]struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,14 +167,6 @@ func (a *Agent) getTempsWithTimeout(getTemps getTempsFn) ([]sensors.TemperatureS
|
|||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use a longer timeout on the first run to allow for initialization
|
|
||||||
// (e.g. Windows LHM subprocess startup)
|
|
||||||
timeout := a.sensorConfig.timeout
|
|
||||||
if a.sensorConfig.firstRun {
|
|
||||||
a.sensorConfig.firstRun = false
|
|
||||||
timeout = 10 * time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
resultCh := make(chan result, 1)
|
resultCh := make(chan result, 1)
|
||||||
go func() {
|
go func() {
|
||||||
temps, err := a.getTempsWithPanicRecovery(getTemps)
|
temps, err := a.getTempsWithPanicRecovery(getTemps)
|
||||||
@@ -195,7 +176,7 @@ func (a *Agent) getTempsWithTimeout(getTemps getTempsFn) ([]sensors.TemperatureS
|
|||||||
select {
|
select {
|
||||||
case res := <-resultCh:
|
case res := <-resultCh:
|
||||||
return res.temps, res.err
|
return res.temps, res.err
|
||||||
case <-time.After(timeout):
|
case <-time.After(temperatureFetchTimeout):
|
||||||
return nil, errTemperatureFetchTimeout
|
return nil, errTemperatureFetchTimeout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,7 +168,6 @@ func TestNewSensorConfigWithEnv(t *testing.T) {
|
|||||||
primarySensor string
|
primarySensor string
|
||||||
sysSensors string
|
sysSensors string
|
||||||
sensors string
|
sensors string
|
||||||
sensorsTimeout string
|
|
||||||
skipCollection bool
|
skipCollection bool
|
||||||
expectedConfig *SensorConfig
|
expectedConfig *SensorConfig
|
||||||
}{
|
}{
|
||||||
@@ -180,37 +179,12 @@ func TestNewSensorConfigWithEnv(t *testing.T) {
|
|||||||
expectedConfig: &SensorConfig{
|
expectedConfig: &SensorConfig{
|
||||||
context: context.Background(),
|
context: context.Background(),
|
||||||
primarySensor: "",
|
primarySensor: "",
|
||||||
timeout: 2 * time.Second,
|
|
||||||
sensors: map[string]struct{}{},
|
sensors: map[string]struct{}{},
|
||||||
isBlacklist: false,
|
isBlacklist: false,
|
||||||
hasWildcards: false,
|
hasWildcards: false,
|
||||||
skipCollection: false,
|
skipCollection: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Custom timeout",
|
|
||||||
primarySensor: "",
|
|
||||||
sysSensors: "",
|
|
||||||
sensors: "",
|
|
||||||
sensorsTimeout: "5s",
|
|
||||||
expectedConfig: &SensorConfig{
|
|
||||||
context: context.Background(),
|
|
||||||
timeout: 5 * time.Second,
|
|
||||||
sensors: map[string]struct{}{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid timeout falls back to default",
|
|
||||||
primarySensor: "",
|
|
||||||
sysSensors: "",
|
|
||||||
sensors: "",
|
|
||||||
sensorsTimeout: "notaduration",
|
|
||||||
expectedConfig: &SensorConfig{
|
|
||||||
context: context.Background(),
|
|
||||||
timeout: 2 * time.Second,
|
|
||||||
sensors: map[string]struct{}{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "Explicitly set to empty string",
|
name: "Explicitly set to empty string",
|
||||||
primarySensor: "",
|
primarySensor: "",
|
||||||
@@ -220,7 +194,6 @@ func TestNewSensorConfigWithEnv(t *testing.T) {
|
|||||||
expectedConfig: &SensorConfig{
|
expectedConfig: &SensorConfig{
|
||||||
context: context.Background(),
|
context: context.Background(),
|
||||||
primarySensor: "",
|
primarySensor: "",
|
||||||
timeout: 2 * time.Second,
|
|
||||||
sensors: map[string]struct{}{},
|
sensors: map[string]struct{}{},
|
||||||
isBlacklist: false,
|
isBlacklist: false,
|
||||||
hasWildcards: false,
|
hasWildcards: false,
|
||||||
@@ -235,7 +208,6 @@ func TestNewSensorConfigWithEnv(t *testing.T) {
|
|||||||
expectedConfig: &SensorConfig{
|
expectedConfig: &SensorConfig{
|
||||||
context: context.Background(),
|
context: context.Background(),
|
||||||
primarySensor: "cpu_temp",
|
primarySensor: "cpu_temp",
|
||||||
timeout: 2 * time.Second,
|
|
||||||
sensors: map[string]struct{}{},
|
sensors: map[string]struct{}{},
|
||||||
isBlacklist: false,
|
isBlacklist: false,
|
||||||
hasWildcards: false,
|
hasWildcards: false,
|
||||||
@@ -249,7 +221,6 @@ func TestNewSensorConfigWithEnv(t *testing.T) {
|
|||||||
expectedConfig: &SensorConfig{
|
expectedConfig: &SensorConfig{
|
||||||
context: context.Background(),
|
context: context.Background(),
|
||||||
primarySensor: "cpu_temp",
|
primarySensor: "cpu_temp",
|
||||||
timeout: 2 * time.Second,
|
|
||||||
sensors: map[string]struct{}{
|
sensors: map[string]struct{}{
|
||||||
"cpu_temp": {},
|
"cpu_temp": {},
|
||||||
"gpu_temp": {},
|
"gpu_temp": {},
|
||||||
@@ -266,7 +237,6 @@ func TestNewSensorConfigWithEnv(t *testing.T) {
|
|||||||
expectedConfig: &SensorConfig{
|
expectedConfig: &SensorConfig{
|
||||||
context: context.Background(),
|
context: context.Background(),
|
||||||
primarySensor: "cpu_temp",
|
primarySensor: "cpu_temp",
|
||||||
timeout: 2 * time.Second,
|
|
||||||
sensors: map[string]struct{}{
|
sensors: map[string]struct{}{
|
||||||
"cpu_temp": {},
|
"cpu_temp": {},
|
||||||
"gpu_temp": {},
|
"gpu_temp": {},
|
||||||
@@ -283,7 +253,6 @@ func TestNewSensorConfigWithEnv(t *testing.T) {
|
|||||||
expectedConfig: &SensorConfig{
|
expectedConfig: &SensorConfig{
|
||||||
context: context.Background(),
|
context: context.Background(),
|
||||||
primarySensor: "cpu_temp",
|
primarySensor: "cpu_temp",
|
||||||
timeout: 2 * time.Second,
|
|
||||||
sensors: map[string]struct{}{
|
sensors: map[string]struct{}{
|
||||||
"cpu_*": {},
|
"cpu_*": {},
|
||||||
"gpu_temp": {},
|
"gpu_temp": {},
|
||||||
@@ -300,7 +269,6 @@ func TestNewSensorConfigWithEnv(t *testing.T) {
|
|||||||
expectedConfig: &SensorConfig{
|
expectedConfig: &SensorConfig{
|
||||||
context: context.Background(),
|
context: context.Background(),
|
||||||
primarySensor: "cpu_temp",
|
primarySensor: "cpu_temp",
|
||||||
timeout: 2 * time.Second,
|
|
||||||
sensors: map[string]struct{}{
|
sensors: map[string]struct{}{
|
||||||
"cpu_*": {},
|
"cpu_*": {},
|
||||||
"gpu_temp": {},
|
"gpu_temp": {},
|
||||||
@@ -316,7 +284,6 @@ func TestNewSensorConfigWithEnv(t *testing.T) {
|
|||||||
sensors: "cpu_temp",
|
sensors: "cpu_temp",
|
||||||
expectedConfig: &SensorConfig{
|
expectedConfig: &SensorConfig{
|
||||||
primarySensor: "cpu_temp",
|
primarySensor: "cpu_temp",
|
||||||
timeout: 2 * time.Second,
|
|
||||||
sensors: map[string]struct{}{
|
sensors: map[string]struct{}{
|
||||||
"cpu_temp": {},
|
"cpu_temp": {},
|
||||||
},
|
},
|
||||||
@@ -328,7 +295,7 @@ func TestNewSensorConfigWithEnv(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
result := agent.newSensorConfigWithEnv(tt.primarySensor, tt.sysSensors, tt.sensors, tt.sensorsTimeout, tt.skipCollection)
|
result := agent.newSensorConfigWithEnv(tt.primarySensor, tt.sysSensors, tt.sensors, tt.skipCollection)
|
||||||
|
|
||||||
// Check primary sensor
|
// Check primary sensor
|
||||||
assert.Equal(t, tt.expectedConfig.primarySensor, result.primarySensor)
|
assert.Equal(t, tt.expectedConfig.primarySensor, result.primarySensor)
|
||||||
@@ -347,7 +314,6 @@ func TestNewSensorConfigWithEnv(t *testing.T) {
|
|||||||
// Check flags
|
// Check flags
|
||||||
assert.Equal(t, tt.expectedConfig.isBlacklist, result.isBlacklist)
|
assert.Equal(t, tt.expectedConfig.isBlacklist, result.isBlacklist)
|
||||||
assert.Equal(t, tt.expectedConfig.hasWildcards, result.hasWildcards)
|
assert.Equal(t, tt.expectedConfig.hasWildcards, result.hasWildcards)
|
||||||
assert.Equal(t, tt.expectedConfig.timeout, result.timeout)
|
|
||||||
|
|
||||||
// Check context
|
// Check context
|
||||||
if tt.sysSensors != "" {
|
if tt.sysSensors != "" {
|
||||||
@@ -367,14 +333,12 @@ func TestNewSensorConfig(t *testing.T) {
|
|||||||
t.Setenv("BESZEL_AGENT_PRIMARY_SENSOR", "test_primary")
|
t.Setenv("BESZEL_AGENT_PRIMARY_SENSOR", "test_primary")
|
||||||
t.Setenv("BESZEL_AGENT_SYS_SENSORS", "/test/path")
|
t.Setenv("BESZEL_AGENT_SYS_SENSORS", "/test/path")
|
||||||
t.Setenv("BESZEL_AGENT_SENSORS", "test_sensor1,test_*,test_sensor3")
|
t.Setenv("BESZEL_AGENT_SENSORS", "test_sensor1,test_*,test_sensor3")
|
||||||
t.Setenv("BESZEL_AGENT_SENSORS_TIMEOUT", "7s")
|
|
||||||
|
|
||||||
agent := &Agent{}
|
agent := &Agent{}
|
||||||
result := agent.newSensorConfig()
|
result := agent.newSensorConfig()
|
||||||
|
|
||||||
// Verify results
|
// Verify results
|
||||||
assert.Equal(t, "test_primary", result.primarySensor)
|
assert.Equal(t, "test_primary", result.primarySensor)
|
||||||
assert.Equal(t, 7*time.Second, result.timeout)
|
|
||||||
assert.NotNil(t, result.sensors)
|
assert.NotNil(t, result.sensors)
|
||||||
assert.Equal(t, 3, len(result.sensors))
|
assert.Equal(t, 3, len(result.sensors))
|
||||||
assert.True(t, result.hasWildcards)
|
assert.True(t, result.hasWildcards)
|
||||||
@@ -568,10 +532,15 @@ func TestGetTempsWithTimeout(t *testing.T) {
|
|||||||
agent := &Agent{
|
agent := &Agent{
|
||||||
sensorConfig: &SensorConfig{
|
sensorConfig: &SensorConfig{
|
||||||
context: context.Background(),
|
context: context.Background(),
|
||||||
timeout: 10 * time.Millisecond,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
originalTimeout := temperatureFetchTimeout
|
||||||
|
t.Cleanup(func() {
|
||||||
|
temperatureFetchTimeout = originalTimeout
|
||||||
|
})
|
||||||
|
temperatureFetchTimeout = 10 * time.Millisecond
|
||||||
|
|
||||||
t.Run("returns temperatures before timeout", func(t *testing.T) {
|
t.Run("returns temperatures before timeout", func(t *testing.T) {
|
||||||
temps, err := agent.getTempsWithTimeout(func(ctx context.Context) ([]sensors.TemperatureStat, error) {
|
temps, err := agent.getTempsWithTimeout(func(ctx context.Context) ([]sensors.TemperatureStat, error) {
|
||||||
return []sensors.TemperatureStat{{SensorKey: "cpu_temp", Temperature: 42}}, nil
|
return []sensors.TemperatureStat{{SensorKey: "cpu_temp", Temperature: 42}}, nil
|
||||||
@@ -598,13 +567,15 @@ func TestUpdateTemperaturesSkipsOnTimeout(t *testing.T) {
|
|||||||
systemInfo: system.Info{DashboardTemp: 99},
|
systemInfo: system.Info{DashboardTemp: 99},
|
||||||
sensorConfig: &SensorConfig{
|
sensorConfig: &SensorConfig{
|
||||||
context: context.Background(),
|
context: context.Background(),
|
||||||
timeout: 10 * time.Millisecond,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
originalTimeout := temperatureFetchTimeout
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
|
temperatureFetchTimeout = originalTimeout
|
||||||
getSensorTemps = sensors.TemperaturesWithContext
|
getSensorTemps = sensors.TemperaturesWithContext
|
||||||
})
|
})
|
||||||
|
temperatureFetchTimeout = 10 * time.Millisecond
|
||||||
getSensorTemps = func(ctx context.Context) ([]sensors.TemperatureStat, error) {
|
getSensorTemps = func(ctx context.Context) ([]sensors.TemperatureStat, error) {
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ func (a *Agent) handleSSHRequest(w io.Writer, req *common.HubRequest[cbor.RawMes
|
|||||||
|
|
||||||
// handleLegacyStats serves the legacy one-shot stats payload for older hubs
|
// handleLegacyStats serves the legacy one-shot stats payload for older hubs
|
||||||
func (a *Agent) handleLegacyStats(w io.Writer, hubVersion semver.Version) error {
|
func (a *Agent) handleLegacyStats(w io.Writer, hubVersion semver.Version) error {
|
||||||
stats := a.gatherStats(common.DataRequestOptions{CacheTimeMs: defaultDataCacheTimeMs})
|
stats := a.gatherStats(common.DataRequestOptions{CacheTimeMs: 60_000})
|
||||||
return a.writeToSession(w, stats, hubVersion)
|
return a.writeToSession(w, stats, hubVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,9 +31,6 @@ type SmartManager struct {
|
|||||||
lastScanTime time.Time
|
lastScanTime time.Time
|
||||||
smartctlPath string
|
smartctlPath string
|
||||||
excludedDevices map[string]struct{}
|
excludedDevices map[string]struct{}
|
||||||
darwinNvmeOnce sync.Once
|
|
||||||
darwinNvmeCapacity map[string]uint64 // serial → bytes cache, written once via darwinNvmeOnce
|
|
||||||
darwinNvmeProvider func() ([]byte, error) // overridable for testing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type scanOutput struct {
|
type scanOutput struct {
|
||||||
@@ -1036,52 +1033,6 @@ func parseScsiGigabytesProcessed(value string) int64 {
|
|||||||
return parsed
|
return parsed
|
||||||
}
|
}
|
||||||
|
|
||||||
// lookupDarwinNvmeCapacity returns the capacity in bytes for a given NVMe serial number on Darwin.
|
|
||||||
// It uses system_profiler SPNVMeDataType to get capacity since Apple SSDs don't report user_capacity
|
|
||||||
// via smartctl. Results are cached after the first call via sync.Once.
|
|
||||||
func (sm *SmartManager) lookupDarwinNvmeCapacity(serial string) uint64 {
|
|
||||||
sm.darwinNvmeOnce.Do(func() {
|
|
||||||
sm.darwinNvmeCapacity = make(map[string]uint64)
|
|
||||||
|
|
||||||
provider := sm.darwinNvmeProvider
|
|
||||||
if provider == nil {
|
|
||||||
provider = func() ([]byte, error) {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
return exec.CommandContext(ctx, "system_profiler", "SPNVMeDataType", "-json").Output()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := provider()
|
|
||||||
if err != nil {
|
|
||||||
slog.Debug("system_profiler NVMe lookup failed", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var result struct {
|
|
||||||
SPNVMeDataType []struct {
|
|
||||||
Items []struct {
|
|
||||||
DeviceSerial string `json:"device_serial"`
|
|
||||||
SizeInBytes uint64 `json:"size_in_bytes"`
|
|
||||||
} `json:"_items"`
|
|
||||||
} `json:"SPNVMeDataType"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(out, &result); err != nil {
|
|
||||||
slog.Debug("system_profiler NVMe parse failed", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, controller := range result.SPNVMeDataType {
|
|
||||||
for _, item := range controller.Items {
|
|
||||||
if item.DeviceSerial != "" && item.SizeInBytes > 0 {
|
|
||||||
sm.darwinNvmeCapacity[item.DeviceSerial] = item.SizeInBytes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return sm.darwinNvmeCapacity[serial]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
||||||
@@ -1118,9 +1069,6 @@ func (sm *SmartManager) parseSmartForNvme(output []byte) (bool, int) {
|
|||||||
smartData.SerialNumber = data.SerialNumber
|
smartData.SerialNumber = data.SerialNumber
|
||||||
smartData.FirmwareVersion = data.FirmwareVersion
|
smartData.FirmwareVersion = data.FirmwareVersion
|
||||||
smartData.Capacity = data.UserCapacity.Bytes
|
smartData.Capacity = data.UserCapacity.Bytes
|
||||||
if smartData.Capacity == 0 && (runtime.GOOS == "darwin" || sm.darwinNvmeProvider != nil) {
|
|
||||||
smartData.Capacity = sm.lookupDarwinNvmeCapacity(data.SerialNumber)
|
|
||||||
}
|
|
||||||
smartData.Temperature = data.NVMeSmartHealthInformationLog.Temperature
|
smartData.Temperature = data.NVMeSmartHealthInformationLog.Temperature
|
||||||
smartData.SmartStatus = getSmartStatus(smartData.Temperature, data.SmartStatus.Passed)
|
smartData.SmartStatus = getSmartStatus(smartData.Temperature, data.SmartStatus.Passed)
|
||||||
smartData.DiskName = data.Device.Name
|
smartData.DiskName = data.Device.Name
|
||||||
|
|||||||
@@ -1199,81 +1199,3 @@ func TestIsNvmeControllerPath(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseSmartForNvmeAppleSSD(t *testing.T) {
|
|
||||||
// Apple SSDs don't report user_capacity via smartctl; capacity should be fetched
|
|
||||||
// from system_profiler via the darwinNvmeProvider fallback.
|
|
||||||
fixturePath := filepath.Join("test-data", "smart", "apple_nvme.json")
|
|
||||||
data, err := os.ReadFile(fixturePath)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
providerCalls := 0
|
|
||||||
fakeProvider := func() ([]byte, error) {
|
|
||||||
providerCalls++
|
|
||||||
return []byte(`{
|
|
||||||
"SPNVMeDataType": [{
|
|
||||||
"_items": [{
|
|
||||||
"device_serial": "0ba0147940253c15",
|
|
||||||
"size_in_bytes": 251000193024
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
}`), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
sm := &SmartManager{
|
|
||||||
SmartDataMap: make(map[string]*smart.SmartData),
|
|
||||||
darwinNvmeProvider: fakeProvider,
|
|
||||||
}
|
|
||||||
|
|
||||||
hasData, _ := sm.parseSmartForNvme(data)
|
|
||||||
require.True(t, hasData)
|
|
||||||
|
|
||||||
deviceData, ok := sm.SmartDataMap["0ba0147940253c15"]
|
|
||||||
require.True(t, ok)
|
|
||||||
assert.Equal(t, "APPLE SSD AP0256Q", deviceData.ModelName)
|
|
||||||
assert.Equal(t, uint64(251000193024), deviceData.Capacity)
|
|
||||||
assert.Equal(t, uint8(42), deviceData.Temperature)
|
|
||||||
assert.Equal(t, "PASSED", deviceData.SmartStatus)
|
|
||||||
assert.Equal(t, 1, providerCalls, "system_profiler should be called once")
|
|
||||||
|
|
||||||
// Second parse: provider should NOT be called again (cache hit)
|
|
||||||
_, _ = sm.parseSmartForNvme(data)
|
|
||||||
assert.Equal(t, 1, providerCalls, "system_profiler should not be called again after caching")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLookupDarwinNvmeCapacityMultipleDisks(t *testing.T) {
|
|
||||||
fakeProvider := func() ([]byte, error) {
|
|
||||||
return []byte(`{
|
|
||||||
"SPNVMeDataType": [
|
|
||||||
{
|
|
||||||
"_items": [
|
|
||||||
{"device_serial": "serial-disk0", "size_in_bytes": 251000193024},
|
|
||||||
{"device_serial": "serial-disk1", "size_in_bytes": 1000204886016}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_items": [
|
|
||||||
{"device_serial": "serial-disk2", "size_in_bytes": 512110190592}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}`), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
sm := &SmartManager{darwinNvmeProvider: fakeProvider}
|
|
||||||
assert.Equal(t, uint64(251000193024), sm.lookupDarwinNvmeCapacity("serial-disk0"))
|
|
||||||
assert.Equal(t, uint64(1000204886016), sm.lookupDarwinNvmeCapacity("serial-disk1"))
|
|
||||||
assert.Equal(t, uint64(512110190592), sm.lookupDarwinNvmeCapacity("serial-disk2"))
|
|
||||||
assert.Equal(t, uint64(0), sm.lookupDarwinNvmeCapacity("unknown-serial"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLookupDarwinNvmeCapacityProviderError(t *testing.T) {
|
|
||||||
fakeProvider := func() ([]byte, error) {
|
|
||||||
return nil, errors.New("system_profiler not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
sm := &SmartManager{darwinNvmeProvider: fakeProvider}
|
|
||||||
assert.Equal(t, uint64(0), sm.lookupDarwinNvmeCapacity("any-serial"))
|
|
||||||
// Cache should be initialized even on error so we don't retry (Once already fired)
|
|
||||||
assert.NotNil(t, sm.darwinNvmeCapacity)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/henrygd/beszel"
|
"github.com/henrygd/beszel"
|
||||||
"github.com/henrygd/beszel/agent/battery"
|
"github.com/henrygd/beszel/agent/battery"
|
||||||
@@ -22,6 +23,13 @@ import (
|
|||||||
"github.com/shirou/gopsutil/v4/mem"
|
"github.com/shirou/gopsutil/v4/mem"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// prevDisk stores previous per-device disk counters for a given cache interval
|
||||||
|
type prevDisk struct {
|
||||||
|
readBytes uint64
|
||||||
|
writeBytes uint64
|
||||||
|
at time.Time
|
||||||
|
}
|
||||||
|
|
||||||
// Sets initial / non-changing values about the host system
|
// Sets initial / non-changing values about the host system
|
||||||
func (a *Agent) refreshSystemDetails() {
|
func (a *Agent) refreshSystemDetails() {
|
||||||
a.systemInfo.AgentVersion = beszel.Version
|
a.systemInfo.AgentVersion = beszel.Version
|
||||||
@@ -107,26 +115,6 @@ func (a *Agent) refreshSystemDetails() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// attachSystemDetails returns details only for fresh default-interval responses.
|
|
||||||
func (a *Agent) attachSystemDetails(data *system.CombinedData, cacheTimeMs uint16, includeRequested bool) *system.CombinedData {
|
|
||||||
if cacheTimeMs != defaultDataCacheTimeMs || (!includeRequested && !a.detailsDirty) {
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy data to avoid adding details to the original cached struct
|
|
||||||
response := *data
|
|
||||||
response.Details = &a.systemDetails
|
|
||||||
a.detailsDirty = false
|
|
||||||
return &response
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateSystemDetails applies a mutation to the static details payload and marks
|
|
||||||
// it for inclusion on the next fresh default-interval response.
|
|
||||||
func (a *Agent) updateSystemDetails(updateFunc func(details *system.Details)) {
|
|
||||||
updateFunc(&a.systemDetails)
|
|
||||||
a.detailsDirty = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns current info, stats about the host system
|
// Returns current info, stats about the host system
|
||||||
func (a *Agent) getSystemStats(cacheTimeMs uint16) system.Stats {
|
func (a *Agent) getSystemStats(cacheTimeMs uint16) system.Stats {
|
||||||
var systemStats system.Stats
|
var systemStats system.Stats
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
package agent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/henrygd/beszel/internal/common"
|
|
||||||
"github.com/henrygd/beszel/internal/entities/system"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGatherStatsDoesNotAttachDetailsToCachedRequests(t *testing.T) {
|
|
||||||
agent := &Agent{
|
|
||||||
cache: NewSystemDataCache(),
|
|
||||||
systemDetails: system.Details{Hostname: "updated-host", Podman: true},
|
|
||||||
detailsDirty: true,
|
|
||||||
}
|
|
||||||
cached := &system.CombinedData{
|
|
||||||
Info: system.Info{Hostname: "cached-host"},
|
|
||||||
}
|
|
||||||
agent.cache.Set(cached, defaultDataCacheTimeMs)
|
|
||||||
|
|
||||||
response := agent.gatherStats(common.DataRequestOptions{CacheTimeMs: defaultDataCacheTimeMs})
|
|
||||||
|
|
||||||
assert.Same(t, cached, response)
|
|
||||||
assert.Nil(t, response.Details)
|
|
||||||
assert.True(t, agent.detailsDirty)
|
|
||||||
assert.Equal(t, "cached-host", response.Info.Hostname)
|
|
||||||
assert.Nil(t, cached.Details)
|
|
||||||
|
|
||||||
secondResponse := agent.gatherStats(common.DataRequestOptions{CacheTimeMs: defaultDataCacheTimeMs})
|
|
||||||
assert.Same(t, cached, secondResponse)
|
|
||||||
assert.Nil(t, secondResponse.Details)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdateSystemDetailsMarksDetailsDirty(t *testing.T) {
|
|
||||||
agent := &Agent{}
|
|
||||||
|
|
||||||
agent.updateSystemDetails(func(details *system.Details) {
|
|
||||||
details.Hostname = "updated-host"
|
|
||||||
details.Podman = true
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.True(t, agent.detailsDirty)
|
|
||||||
assert.Equal(t, "updated-host", agent.systemDetails.Hostname)
|
|
||||||
assert.True(t, agent.systemDetails.Podman)
|
|
||||||
|
|
||||||
original := &system.CombinedData{}
|
|
||||||
realTimeResponse := agent.attachSystemDetails(original, 1000, true)
|
|
||||||
assert.Same(t, original, realTimeResponse)
|
|
||||||
assert.Nil(t, realTimeResponse.Details)
|
|
||||||
assert.True(t, agent.detailsDirty)
|
|
||||||
|
|
||||||
response := agent.attachSystemDetails(original, defaultDataCacheTimeMs, false)
|
|
||||||
require.NotNil(t, response.Details)
|
|
||||||
assert.NotSame(t, original, response)
|
|
||||||
assert.Equal(t, "updated-host", response.Details.Hostname)
|
|
||||||
assert.True(t, response.Details.Podman)
|
|
||||||
assert.False(t, agent.detailsDirty)
|
|
||||||
assert.Nil(t, original.Details)
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
{
|
|
||||||
"json_format_version": [1, 0],
|
|
||||||
"smartctl": {
|
|
||||||
"version": [7, 4],
|
|
||||||
"argv": ["smartctl", "-aix", "-j", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddy(ANS2)/RTBuddyService/AppleANS3NVMeController/NS_01@1"],
|
|
||||||
"exit_status": 4
|
|
||||||
},
|
|
||||||
"device": {
|
|
||||||
"name": "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddy(ANS2)/RTBuddyService/AppleANS3NVMeController/NS_01@1",
|
|
||||||
"info_name": "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddy(ANS2)/RTBuddyService/AppleANS3NVMeController/NS_01@1",
|
|
||||||
"type": "nvme",
|
|
||||||
"protocol": "NVMe"
|
|
||||||
},
|
|
||||||
"model_name": "APPLE SSD AP0256Q",
|
|
||||||
"serial_number": "0ba0147940253c15",
|
|
||||||
"firmware_version": "555",
|
|
||||||
"smart_support": {
|
|
||||||
"available": true,
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"smart_status": {
|
|
||||||
"passed": true,
|
|
||||||
"nvme": {
|
|
||||||
"value": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nvme_smart_health_information_log": {
|
|
||||||
"critical_warning": 0,
|
|
||||||
"temperature": 42,
|
|
||||||
"available_spare": 100,
|
|
||||||
"available_spare_threshold": 99,
|
|
||||||
"percentage_used": 1,
|
|
||||||
"data_units_read": 270189386,
|
|
||||||
"data_units_written": 166753862,
|
|
||||||
"host_reads": 7543766995,
|
|
||||||
"host_writes": 3761621926,
|
|
||||||
"controller_busy_time": 0,
|
|
||||||
"power_cycles": 366,
|
|
||||||
"power_on_hours": 2850,
|
|
||||||
"unsafe_shutdowns": 195,
|
|
||||||
"media_errors": 0,
|
|
||||||
"num_err_log_entries": 0
|
|
||||||
},
|
|
||||||
"temperature": {
|
|
||||||
"current": 42
|
|
||||||
},
|
|
||||||
"power_cycle_count": 366,
|
|
||||||
"power_on_time": {
|
|
||||||
"hours": 2850
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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.18.7"
|
Version = "0.18.5"
|
||||||
// AppName is the name of the application.
|
// AppName is the name of the application.
|
||||||
AppName = "beszel"
|
AppName = "beszel"
|
||||||
)
|
)
|
||||||
|
|||||||
9
go.mod
9
go.mod
@@ -5,6 +5,7 @@ go 1.26.1
|
|||||||
require (
|
require (
|
||||||
github.com/blang/semver v3.5.1+incompatible
|
github.com/blang/semver v3.5.1+incompatible
|
||||||
github.com/coreos/go-systemd/v22 v22.7.0
|
github.com/coreos/go-systemd/v22 v22.7.0
|
||||||
|
github.com/distatus/battery v0.11.0
|
||||||
github.com/ebitengine/purego v0.10.0
|
github.com/ebitengine/purego v0.10.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
|
||||||
@@ -12,8 +13,8 @@ require (
|
|||||||
github.com/lxzan/gws v1.9.1
|
github.com/lxzan/gws v1.9.1
|
||||||
github.com/nicholas-fedor/shoutrrr v0.14.1
|
github.com/nicholas-fedor/shoutrrr v0.14.1
|
||||||
github.com/pocketbase/dbx v1.12.0
|
github.com/pocketbase/dbx v1.12.0
|
||||||
github.com/pocketbase/pocketbase v0.36.8
|
github.com/pocketbase/pocketbase v0.36.7
|
||||||
github.com/shirou/gopsutil/v4 v4.26.3
|
github.com/shirou/gopsutil/v4 v4.26.2
|
||||||
github.com/spf13/cast v1.10.0
|
github.com/spf13/cast v1.10.0
|
||||||
github.com/spf13/cobra v1.10.2
|
github.com/spf13/cobra v1.10.2
|
||||||
github.com/spf13/pflag v1.0.10
|
github.com/spf13/pflag v1.0.10
|
||||||
@@ -22,7 +23,6 @@ require (
|
|||||||
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90
|
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90
|
||||||
golang.org/x/sys v0.42.0
|
golang.org/x/sys v0.42.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
howett.net/plist v1.0.1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -61,8 +61,9 @@ require (
|
|||||||
golang.org/x/sync v0.20.0 // indirect
|
golang.org/x/sync v0.20.0 // indirect
|
||||||
golang.org/x/term v0.41.0 // indirect
|
golang.org/x/term v0.41.0 // indirect
|
||||||
golang.org/x/text v0.35.0 // indirect
|
golang.org/x/text v0.35.0 // indirect
|
||||||
|
howett.net/plist v1.0.1 // indirect
|
||||||
modernc.org/libc v1.70.0 // indirect
|
modernc.org/libc v1.70.0 // 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.48.0 // indirect
|
modernc.org/sqlite v1.46.2 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
14
go.sum
14
go.sum
@@ -17,6 +17,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
|||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
|
github.com/distatus/battery v0.11.0 h1:KJk89gz90Iq/wJtbjjM9yUzBXV+ASV/EG2WOOL7N8lc=
|
||||||
|
github.com/distatus/battery v0.11.0/go.mod h1:KmVkE8A8hpIX4T78QRdMktYpEp35QfOL8A8dwZBxq2k=
|
||||||
github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8=
|
github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8=
|
||||||
github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c=
|
github.com/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=
|
||||||
@@ -96,8 +98,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.12.0 h1:/oLErM+A0b4xI0PWTGPqSDVjzix48PqI/bng2l0PzoA=
|
github.com/pocketbase/dbx v1.12.0 h1:/oLErM+A0b4xI0PWTGPqSDVjzix48PqI/bng2l0PzoA=
|
||||||
github.com/pocketbase/dbx v1.12.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
|
github.com/pocketbase/dbx v1.12.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
|
||||||
github.com/pocketbase/pocketbase v0.36.8 h1:gCNqoesZ44saYOD3J7edhi5nDwUWKyQG7boM/kVwz2c=
|
github.com/pocketbase/pocketbase v0.36.7 h1:MrViB7BptPYrf2Nt25pJEYBqUdFjuhRKu1p5GTrkvPA=
|
||||||
github.com/pocketbase/pocketbase v0.36.8/go.mod h1:OY4WaXbP0WnF/EXoBbboWJK+ZSZ1A85tiA0sjrTKxTA=
|
github.com/pocketbase/pocketbase v0.36.7/go.mod h1:qX4HuVjoKXtEg41fSJVM0JLfGWXbBmHxVv/FaE446r4=
|
||||||
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=
|
||||||
@@ -105,8 +107,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.26.3 h1:2ESdQt90yU3oXF/CdOlRCJxrP+Am1aBYubTMTfxJ1qc=
|
github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI=
|
||||||
github.com/shirou/gopsutil/v4 v4.26.3/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
|
github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
|
||||||
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.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||||
@@ -197,8 +199,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.48.0 h1:ElZyLop3Q2mHYk5IFPPXADejZrlHu7APbpB0sF78bq4=
|
modernc.org/sqlite v1.46.2 h1:gkXQ6R0+AjxFC/fTDaeIVLbNLNrRoOK7YYVz5BKhTcE=
|
||||||
modernc.org/sqlite v1.48.0/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
|
modernc.org/sqlite v1.46.2/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
|
||||||
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=
|
||||||
|
|||||||
@@ -109,18 +109,6 @@ func (am *AlertManager) cancelPendingAlert(alertID string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// CancelPendingStatusAlerts cancels all pending status alert timers for a given system.
|
|
||||||
// This is called when a system is paused to prevent delayed alerts from firing.
|
|
||||||
func (am *AlertManager) CancelPendingStatusAlerts(systemID string) {
|
|
||||||
am.pendingAlerts.Range(func(key, value any) bool {
|
|
||||||
info := value.(*alertInfo)
|
|
||||||
if info.alertData.SystemID == systemID {
|
|
||||||
am.cancelPendingAlert(key.(string))
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// processPendingAlert sends a "down" alert if the pending alert has expired and the system is still down.
|
// processPendingAlert sends a "down" alert if the pending alert has expired and the system is still down.
|
||||||
func (am *AlertManager) processPendingAlert(alertID string) {
|
func (am *AlertManager) processPendingAlert(alertID string) {
|
||||||
value, loaded := am.pendingAlerts.LoadAndDelete(alertID)
|
value, loaded := am.pendingAlerts.LoadAndDelete(alertID)
|
||||||
|
|||||||
@@ -941,68 +941,3 @@ func TestStatusAlertClearedBeforeSend(t *testing.T) {
|
|||||||
assert.EqualValues(t, 0, alertHistoryCount, "Should have no unresolved alert history records since alert never triggered")
|
assert.EqualValues(t, 0, alertHistoryCount, "Should have no unresolved alert history records since alert never triggered")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCancelPendingStatusAlertsClearsAllAlertsForSystem(t *testing.T) {
|
|
||||||
hub, user := beszelTests.GetHubWithUser(t)
|
|
||||||
defer hub.Cleanup()
|
|
||||||
|
|
||||||
userSettings, err := hub.FindFirstRecordByFilter("user_settings", "user={:user}", map[string]any{"user": user.Id})
|
|
||||||
require.NoError(t, err)
|
|
||||||
userSettings.Set("settings", `{"emails":["test@example.com"],"webhooks":[]}`)
|
|
||||||
require.NoError(t, hub.Save(userSettings))
|
|
||||||
|
|
||||||
systemCollection, err := hub.FindCollectionByNameOrId("systems")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
system1 := core.NewRecord(systemCollection)
|
|
||||||
system1.Set("name", "system-1")
|
|
||||||
system1.Set("status", "up")
|
|
||||||
system1.Set("host", "127.0.0.1")
|
|
||||||
system1.Set("users", []string{user.Id})
|
|
||||||
require.NoError(t, hub.Save(system1))
|
|
||||||
|
|
||||||
system2 := core.NewRecord(systemCollection)
|
|
||||||
system2.Set("name", "system-2")
|
|
||||||
system2.Set("status", "up")
|
|
||||||
system2.Set("host", "127.0.0.2")
|
|
||||||
system2.Set("users", []string{user.Id})
|
|
||||||
require.NoError(t, hub.Save(system2))
|
|
||||||
|
|
||||||
alertCollection, err := hub.FindCollectionByNameOrId("alerts")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
alert1 := core.NewRecord(alertCollection)
|
|
||||||
alert1.Set("user", user.Id)
|
|
||||||
alert1.Set("system", system1.Id)
|
|
||||||
alert1.Set("name", "Status")
|
|
||||||
alert1.Set("triggered", false)
|
|
||||||
alert1.Set("min", 5)
|
|
||||||
require.NoError(t, hub.Save(alert1))
|
|
||||||
|
|
||||||
alert2 := core.NewRecord(alertCollection)
|
|
||||||
alert2.Set("user", user.Id)
|
|
||||||
alert2.Set("system", system2.Id)
|
|
||||||
alert2.Set("name", "Status")
|
|
||||||
alert2.Set("triggered", false)
|
|
||||||
alert2.Set("min", 5)
|
|
||||||
require.NoError(t, hub.Save(alert2))
|
|
||||||
|
|
||||||
am := alerts.NewTestAlertManagerWithoutWorker(hub)
|
|
||||||
initialEmailCount := hub.TestMailer.TotalSend()
|
|
||||||
|
|
||||||
// Both systems go down
|
|
||||||
require.NoError(t, am.HandleStatusAlerts("down", system1))
|
|
||||||
require.NoError(t, am.HandleStatusAlerts("down", system2))
|
|
||||||
assert.Equal(t, 2, am.GetPendingAlertsCount(), "both systems should have pending alerts")
|
|
||||||
|
|
||||||
// System 1 is paused — cancel its pending alerts
|
|
||||||
am.CancelPendingStatusAlerts(system1.Id)
|
|
||||||
assert.Equal(t, 1, am.GetPendingAlertsCount(), "only system2 alert should remain pending after pausing system1")
|
|
||||||
|
|
||||||
// Expire and process remaining alerts — only system2 should fire
|
|
||||||
am.ForceExpirePendingAlerts()
|
|
||||||
processed, err := am.ProcessPendingAlerts()
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Len(t, processed, 1, "only the non-paused system's alert should be processed")
|
|
||||||
assert.Equal(t, initialEmailCount+1, hub.TestMailer.TotalSend(), "only system2 should send a down notification")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -48,8 +48,6 @@ type Stats struct {
|
|||||||
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]
|
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..]
|
CpuCoresUsage Uint8Slice `json:"cpus,omitempty" cbor:"34,keyasint,omitempty"` // per-core busy usage [CPU0..]
|
||||||
DiskIoStats [6]float64 `json:"dios,omitzero" cbor:"35,keyasint,omitzero"` // [read time %, write time %, io utilization %, r_await ms, w_await ms, weighted io %]
|
|
||||||
MaxDiskIoStats [6]float64 `json:"diosm,omitzero" cbor:"-"` // max values for DiskIoStats
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Uint8Slice wraps []uint8 to customize JSON encoding while keeping CBOR efficient.
|
// Uint8Slice wraps []uint8 to customize JSON encoding while keeping CBOR efficient.
|
||||||
@@ -99,8 +97,6 @@ type FsStats struct {
|
|||||||
DiskWriteBytes uint64 `json:"wb" cbor:"7,keyasint,omitempty"`
|
DiskWriteBytes uint64 `json:"wb" cbor:"7,keyasint,omitempty"`
|
||||||
MaxDiskReadBytes uint64 `json:"rbm,omitempty" cbor:"-"`
|
MaxDiskReadBytes uint64 `json:"rbm,omitempty" cbor:"-"`
|
||||||
MaxDiskWriteBytes uint64 `json:"wbm,omitempty" cbor:"-"`
|
MaxDiskWriteBytes uint64 `json:"wbm,omitempty" cbor:"-"`
|
||||||
DiskIoStats [6]float64 `json:"dios,omitzero" cbor:"8,keyasint,omitzero"` // [read time %, write time %, io utilization %, r_await ms, w_await ms, weighted io %]
|
|
||||||
MaxDiskIoStats [6]float64 `json:"diosm,omitzero" cbor:"-"` // max values for DiskIoStats
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type NetIoStats struct {
|
type NetIoStats struct {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package hub
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -26,32 +25,6 @@ type UpdateInfo struct {
|
|||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var containerIDPattern = regexp.MustCompile(`^[a-fA-F0-9]{12,64}$`)
|
|
||||||
|
|
||||||
// Middleware to allow only admin role users
|
|
||||||
var requireAdminRole = customAuthMiddleware(func(e *core.RequestEvent) bool {
|
|
||||||
return e.Auth.GetString("role") == "admin"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Middleware to exclude readonly users
|
|
||||||
var excludeReadOnlyRole = customAuthMiddleware(func(e *core.RequestEvent) bool {
|
|
||||||
return e.Auth.GetString("role") != "readonly"
|
|
||||||
})
|
|
||||||
|
|
||||||
// customAuthMiddleware handles boilerplate for custom authentication middlewares. fn should
|
|
||||||
// return true if the request is allowed, false otherwise. e.Auth is guaranteed to be non-nil.
|
|
||||||
func customAuthMiddleware(fn func(*core.RequestEvent) bool) func(*core.RequestEvent) error {
|
|
||||||
return func(e *core.RequestEvent) error {
|
|
||||||
if e.Auth == nil {
|
|
||||||
return e.UnauthorizedError("The request requires valid record authorization token.", nil)
|
|
||||||
}
|
|
||||||
if !fn(e) {
|
|
||||||
return e.ForbiddenError("The authorized record is not allowed to perform this action.", nil)
|
|
||||||
}
|
|
||||||
return e.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// registerMiddlewares registers custom middlewares
|
// registerMiddlewares registers custom middlewares
|
||||||
func (h *Hub) registerMiddlewares(se *core.ServeEvent) {
|
func (h *Hub) registerMiddlewares(se *core.ServeEvent) {
|
||||||
// authorizes request with user matching the provided email
|
// authorizes request with user matching the provided email
|
||||||
@@ -60,7 +33,7 @@ func (h *Hub) registerMiddlewares(se *core.ServeEvent) {
|
|||||||
return e.Next()
|
return e.Next()
|
||||||
}
|
}
|
||||||
isAuthRefresh := e.Request.URL.Path == "/api/collections/users/auth-refresh" && e.Request.Method == http.MethodPost
|
isAuthRefresh := e.Request.URL.Path == "/api/collections/users/auth-refresh" && e.Request.Method == http.MethodPost
|
||||||
e.Auth, err = e.App.FindAuthRecordByEmail("users", email)
|
e.Auth, err = e.App.FindFirstRecordByData("users", "email", email)
|
||||||
if err != nil || !isAuthRefresh {
|
if err != nil || !isAuthRefresh {
|
||||||
return e.Next()
|
return e.Next()
|
||||||
}
|
}
|
||||||
@@ -111,19 +84,19 @@ func (h *Hub) registerApiRoutes(se *core.ServeEvent) error {
|
|||||||
// send test notification
|
// send test notification
|
||||||
apiAuth.POST("/test-notification", h.SendTestNotification)
|
apiAuth.POST("/test-notification", h.SendTestNotification)
|
||||||
// heartbeat status and test
|
// heartbeat status and test
|
||||||
apiAuth.GET("/heartbeat-status", h.getHeartbeatStatus).BindFunc(requireAdminRole)
|
apiAuth.GET("/heartbeat-status", h.getHeartbeatStatus)
|
||||||
apiAuth.POST("/test-heartbeat", h.testHeartbeat).BindFunc(requireAdminRole)
|
apiAuth.POST("/test-heartbeat", h.testHeartbeat)
|
||||||
// get config.yml content
|
// get config.yml content
|
||||||
apiAuth.GET("/config-yaml", config.GetYamlConfig).BindFunc(requireAdminRole)
|
apiAuth.GET("/config-yaml", config.GetYamlConfig)
|
||||||
// handle agent websocket connection
|
// handle agent websocket connection
|
||||||
apiNoAuth.GET("/agent-connect", h.handleAgentConnect)
|
apiNoAuth.GET("/agent-connect", h.handleAgentConnect)
|
||||||
// get or create universal tokens
|
// get or create universal tokens
|
||||||
apiAuth.GET("/universal-token", h.getUniversalToken).BindFunc(excludeReadOnlyRole)
|
apiAuth.GET("/universal-token", h.getUniversalToken)
|
||||||
// update / delete user alerts
|
// update / delete user alerts
|
||||||
apiAuth.POST("/user-alerts", alerts.UpsertUserAlerts)
|
apiAuth.POST("/user-alerts", alerts.UpsertUserAlerts)
|
||||||
apiAuth.DELETE("/user-alerts", alerts.DeleteUserAlerts)
|
apiAuth.DELETE("/user-alerts", alerts.DeleteUserAlerts)
|
||||||
// refresh SMART devices for a system
|
// refresh SMART devices for a system
|
||||||
apiAuth.POST("/smart/refresh", h.refreshSmartData).BindFunc(excludeReadOnlyRole)
|
apiAuth.POST("/smart/refresh", h.refreshSmartData)
|
||||||
// get systemd service details
|
// get systemd service details
|
||||||
apiAuth.GET("/systemd/info", h.getSystemdInfo)
|
apiAuth.GET("/systemd/info", h.getSystemdInfo)
|
||||||
// /containers routes
|
// /containers routes
|
||||||
@@ -180,10 +153,6 @@ func (info *UpdateInfo) getUpdate(e *core.RequestEvent) error {
|
|||||||
|
|
||||||
// GetUniversalToken handles the universal token API endpoint (create, read, delete)
|
// GetUniversalToken handles the universal token API endpoint (create, read, delete)
|
||||||
func (h *Hub) getUniversalToken(e *core.RequestEvent) error {
|
func (h *Hub) getUniversalToken(e *core.RequestEvent) error {
|
||||||
if e.Auth.IsSuperuser() {
|
|
||||||
return e.ForbiddenError("Superusers cannot use universal tokens", nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenMap := universalTokenMap.GetMap()
|
tokenMap := universalTokenMap.GetMap()
|
||||||
userID := e.Auth.Id
|
userID := e.Auth.Id
|
||||||
query := e.Request.URL.Query()
|
query := e.Request.URL.Query()
|
||||||
@@ -277,6 +246,9 @@ func (h *Hub) getUniversalToken(e *core.RequestEvent) error {
|
|||||||
|
|
||||||
// getHeartbeatStatus returns current heartbeat configuration and whether it's enabled
|
// getHeartbeatStatus returns current heartbeat configuration and whether it's enabled
|
||||||
func (h *Hub) getHeartbeatStatus(e *core.RequestEvent) error {
|
func (h *Hub) getHeartbeatStatus(e *core.RequestEvent) error {
|
||||||
|
if e.Auth.GetString("role") != "admin" {
|
||||||
|
return e.ForbiddenError("Requires admin role", nil)
|
||||||
|
}
|
||||||
if h.hb == nil {
|
if h.hb == nil {
|
||||||
return e.JSON(http.StatusOK, map[string]any{
|
return e.JSON(http.StatusOK, map[string]any{
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
@@ -294,6 +266,9 @@ func (h *Hub) getHeartbeatStatus(e *core.RequestEvent) error {
|
|||||||
|
|
||||||
// testHeartbeat triggers a single heartbeat ping and returns the result
|
// testHeartbeat triggers a single heartbeat ping and returns the result
|
||||||
func (h *Hub) testHeartbeat(e *core.RequestEvent) error {
|
func (h *Hub) testHeartbeat(e *core.RequestEvent) error {
|
||||||
|
if e.Auth.GetString("role") != "admin" {
|
||||||
|
return e.ForbiddenError("Requires admin role", nil)
|
||||||
|
}
|
||||||
if h.hb == nil {
|
if h.hb == nil {
|
||||||
return e.JSON(http.StatusOK, map[string]any{
|
return e.JSON(http.StatusOK, map[string]any{
|
||||||
"err": "Heartbeat not configured. Set HEARTBEAT_URL environment variable.",
|
"err": "Heartbeat not configured. Set HEARTBEAT_URL environment variable.",
|
||||||
@@ -310,18 +285,21 @@ func (h *Hub) containerRequestHandler(e *core.RequestEvent, fetchFunc func(*syst
|
|||||||
systemID := e.Request.URL.Query().Get("system")
|
systemID := e.Request.URL.Query().Get("system")
|
||||||
containerID := e.Request.URL.Query().Get("container")
|
containerID := e.Request.URL.Query().Get("container")
|
||||||
|
|
||||||
if systemID == "" || containerID == "" || !containerIDPattern.MatchString(containerID) {
|
if systemID == "" || containerID == "" {
|
||||||
return e.BadRequestError("Invalid system or container parameter", nil)
|
return e.JSON(http.StatusBadRequest, map[string]string{"error": "system and container parameters are required"})
|
||||||
|
}
|
||||||
|
if !containerIDPattern.MatchString(containerID) {
|
||||||
|
return e.JSON(http.StatusBadRequest, map[string]string{"error": "invalid container parameter"})
|
||||||
}
|
}
|
||||||
|
|
||||||
system, err := h.sm.GetSystem(systemID)
|
system, err := h.sm.GetSystem(systemID)
|
||||||
if err != nil || !system.HasUser(e.App, e.Auth.Id) {
|
if err != nil {
|
||||||
return e.NotFoundError("", nil)
|
return e.JSON(http.StatusNotFound, map[string]string{"error": "system not found"})
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := fetchFunc(system, containerID)
|
data, err := fetchFunc(system, containerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return e.InternalServerError("", err)
|
return e.JSON(http.StatusNotFound, map[string]string{"error": err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.JSON(http.StatusOK, map[string]string{responseKey: data})
|
return e.JSON(http.StatusOK, map[string]string{responseKey: data})
|
||||||
@@ -347,23 +325,15 @@ func (h *Hub) getSystemdInfo(e *core.RequestEvent) error {
|
|||||||
serviceName := query.Get("service")
|
serviceName := query.Get("service")
|
||||||
|
|
||||||
if systemID == "" || serviceName == "" {
|
if systemID == "" || serviceName == "" {
|
||||||
return e.BadRequestError("Invalid system or service parameter", nil)
|
return e.JSON(http.StatusBadRequest, map[string]string{"error": "system and service parameters are required"})
|
||||||
}
|
}
|
||||||
system, err := h.sm.GetSystem(systemID)
|
system, err := h.sm.GetSystem(systemID)
|
||||||
if err != nil || !system.HasUser(e.App, e.Auth.Id) {
|
|
||||||
return e.NotFoundError("", nil)
|
|
||||||
}
|
|
||||||
// verify service exists before fetching details
|
|
||||||
_, err = e.App.FindFirstRecordByFilter("systemd_services", "system = {:system} && name = {:name}", dbx.Params{
|
|
||||||
"system": systemID,
|
|
||||||
"name": serviceName,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return e.NotFoundError("", err)
|
return e.JSON(http.StatusNotFound, map[string]string{"error": "system not found"})
|
||||||
}
|
}
|
||||||
details, err := system.FetchSystemdInfoFromAgent(serviceName)
|
details, err := system.FetchSystemdInfoFromAgent(serviceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return e.InternalServerError("", err)
|
return e.JSON(http.StatusNotFound, map[string]string{"error": err.Error()})
|
||||||
}
|
}
|
||||||
e.Response.Header().Set("Cache-Control", "public, max-age=60")
|
e.Response.Header().Set("Cache-Control", "public, max-age=60")
|
||||||
return e.JSON(http.StatusOK, map[string]any{"details": details})
|
return e.JSON(http.StatusOK, map[string]any{"details": details})
|
||||||
@@ -374,16 +344,17 @@ func (h *Hub) getSystemdInfo(e *core.RequestEvent) error {
|
|||||||
func (h *Hub) refreshSmartData(e *core.RequestEvent) error {
|
func (h *Hub) refreshSmartData(e *core.RequestEvent) error {
|
||||||
systemID := e.Request.URL.Query().Get("system")
|
systemID := e.Request.URL.Query().Get("system")
|
||||||
if systemID == "" {
|
if systemID == "" {
|
||||||
return e.BadRequestError("Invalid system parameter", nil)
|
return e.JSON(http.StatusBadRequest, map[string]string{"error": "system parameter is required"})
|
||||||
}
|
}
|
||||||
|
|
||||||
system, err := h.sm.GetSystem(systemID)
|
system, err := h.sm.GetSystem(systemID)
|
||||||
if err != nil || !system.HasUser(e.App, e.Auth.Id) {
|
if err != nil {
|
||||||
return e.NotFoundError("", nil)
|
return e.JSON(http.StatusNotFound, map[string]string{"error": "system not found"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch and save SMART devices
|
||||||
if err := system.FetchAndSaveSmartDevices(); err != nil {
|
if err := system.FetchAndSaveSmartDevices(); err != nil {
|
||||||
return e.InternalServerError("", err)
|
return e.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.JSON(http.StatusOK, map[string]string{"status": "ok"})
|
return e.JSON(http.StatusOK, map[string]string{"status": "ok"})
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package hub_test
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -26,33 +25,33 @@ func jsonReader(v any) io.Reader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestApiRoutesAuthentication(t *testing.T) {
|
func TestApiRoutesAuthentication(t *testing.T) {
|
||||||
hub, user := beszelTests.GetHubWithUser(t)
|
hub, _ := beszelTests.NewTestHub(t.TempDir())
|
||||||
defer hub.Cleanup()
|
defer hub.Cleanup()
|
||||||
|
|
||||||
|
hub.StartHub()
|
||||||
|
|
||||||
|
// Create test user and get auth token
|
||||||
|
user, err := beszelTests.CreateUser(hub, "testuser@example.com", "password123")
|
||||||
|
require.NoError(t, err, "Failed to create test user")
|
||||||
|
|
||||||
|
adminUser, err := beszelTests.CreateRecord(hub, "users", map[string]any{
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"password": "password123",
|
||||||
|
"role": "admin",
|
||||||
|
})
|
||||||
|
require.NoError(t, err, "Failed to create admin user")
|
||||||
|
adminUserToken, err := adminUser.NewAuthToken()
|
||||||
|
|
||||||
|
// superUser, err := beszelTests.CreateRecord(hub, core.CollectionNameSuperusers, map[string]any{
|
||||||
|
// "email": "superuser@example.com",
|
||||||
|
// "password": "password123",
|
||||||
|
// })
|
||||||
|
// require.NoError(t, err, "Failed to create superuser")
|
||||||
|
|
||||||
userToken, err := user.NewAuthToken()
|
userToken, err := user.NewAuthToken()
|
||||||
require.NoError(t, err, "Failed to create auth token")
|
require.NoError(t, err, "Failed to create auth token")
|
||||||
|
|
||||||
// Create test user and get auth token
|
// Create test system for user-alerts endpoints
|
||||||
user2, err := beszelTests.CreateUser(hub, "testuser@example.com", "password123")
|
|
||||||
require.NoError(t, err, "Failed to create test user")
|
|
||||||
user2Token, err := user2.NewAuthToken()
|
|
||||||
require.NoError(t, err, "Failed to create user2 auth token")
|
|
||||||
|
|
||||||
adminUser, err := beszelTests.CreateUserWithRole(hub, "admin@example.com", "password123", "admin")
|
|
||||||
require.NoError(t, err, "Failed to create admin user")
|
|
||||||
adminUserToken, err := adminUser.NewAuthToken()
|
|
||||||
|
|
||||||
readOnlyUser, err := beszelTests.CreateUserWithRole(hub, "readonly@example.com", "password123", "readonly")
|
|
||||||
require.NoError(t, err, "Failed to create readonly user")
|
|
||||||
readOnlyUserToken, err := readOnlyUser.NewAuthToken()
|
|
||||||
require.NoError(t, err, "Failed to create readonly user auth token")
|
|
||||||
|
|
||||||
superuser, err := beszelTests.CreateSuperuser(hub, "superuser@example.com", "password123")
|
|
||||||
require.NoError(t, err, "Failed to create superuser")
|
|
||||||
superuserToken, err := superuser.NewAuthToken()
|
|
||||||
require.NoError(t, err, "Failed to create superuser auth token")
|
|
||||||
|
|
||||||
// Create test system
|
|
||||||
system, err := beszelTests.CreateRecord(hub, "systems", map[string]any{
|
system, err := beszelTests.CreateRecord(hub, "systems", map[string]any{
|
||||||
"name": "test-system",
|
"name": "test-system",
|
||||||
"users": []string{user.Id},
|
"users": []string{user.Id},
|
||||||
@@ -107,7 +106,7 @@ func TestApiRoutesAuthentication(t *testing.T) {
|
|||||||
"Authorization": userToken,
|
"Authorization": userToken,
|
||||||
},
|
},
|
||||||
ExpectedStatus: 403,
|
ExpectedStatus: 403,
|
||||||
ExpectedContent: []string{"The authorized record is not allowed to perform this action."},
|
ExpectedContent: []string{"Requires admin"},
|
||||||
TestAppFactory: testAppFactory,
|
TestAppFactory: testAppFactory,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -137,7 +136,7 @@ func TestApiRoutesAuthentication(t *testing.T) {
|
|||||||
"Authorization": userToken,
|
"Authorization": userToken,
|
||||||
},
|
},
|
||||||
ExpectedStatus: 403,
|
ExpectedStatus: 403,
|
||||||
ExpectedContent: []string{"The authorized record is not allowed to perform this action."},
|
ExpectedContent: []string{"Requires admin role"},
|
||||||
TestAppFactory: testAppFactory,
|
TestAppFactory: testAppFactory,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -159,7 +158,7 @@ func TestApiRoutesAuthentication(t *testing.T) {
|
|||||||
"Authorization": userToken,
|
"Authorization": userToken,
|
||||||
},
|
},
|
||||||
ExpectedStatus: 403,
|
ExpectedStatus: 403,
|
||||||
ExpectedContent: []string{"The authorized record is not allowed to perform this action."},
|
ExpectedContent: []string{"Requires admin role"},
|
||||||
TestAppFactory: testAppFactory,
|
TestAppFactory: testAppFactory,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -203,74 +202,6 @@ func TestApiRoutesAuthentication(t *testing.T) {
|
|||||||
ExpectedContent: []string{"\"permanent\":true", "permanent-token-123"},
|
ExpectedContent: []string{"\"permanent\":true", "permanent-token-123"},
|
||||||
TestAppFactory: testAppFactory,
|
TestAppFactory: testAppFactory,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "GET /universal-token - superuser should fail",
|
|
||||||
Method: http.MethodGet,
|
|
||||||
URL: "/api/beszel/universal-token",
|
|
||||||
Headers: map[string]string{
|
|
||||||
"Authorization": superuserToken,
|
|
||||||
},
|
|
||||||
ExpectedStatus: 403,
|
|
||||||
ExpectedContent: []string{"Superusers cannot use universal tokens"},
|
|
||||||
TestAppFactory: func(t testing.TB) *pbTests.TestApp {
|
|
||||||
return hub.TestApp
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "GET /universal-token - with readonly auth should fail",
|
|
||||||
Method: http.MethodGet,
|
|
||||||
URL: "/api/beszel/universal-token",
|
|
||||||
Headers: map[string]string{
|
|
||||||
"Authorization": readOnlyUserToken,
|
|
||||||
},
|
|
||||||
ExpectedStatus: 403,
|
|
||||||
ExpectedContent: []string{"The authorized record is not allowed to perform this action."},
|
|
||||||
TestAppFactory: testAppFactory,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "POST /smart/refresh - missing system should fail 400 with user auth",
|
|
||||||
Method: http.MethodPost,
|
|
||||||
URL: "/api/beszel/smart/refresh",
|
|
||||||
Headers: map[string]string{
|
|
||||||
"Authorization": userToken,
|
|
||||||
},
|
|
||||||
ExpectedStatus: 400,
|
|
||||||
ExpectedContent: []string{"Invalid", "system", "parameter"},
|
|
||||||
TestAppFactory: testAppFactory,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "POST /smart/refresh - with readonly auth should fail",
|
|
||||||
Method: http.MethodPost,
|
|
||||||
URL: fmt.Sprintf("/api/beszel/smart/refresh?system=%s", system.Id),
|
|
||||||
Headers: map[string]string{
|
|
||||||
"Authorization": readOnlyUserToken,
|
|
||||||
},
|
|
||||||
ExpectedStatus: 403,
|
|
||||||
ExpectedContent: []string{"The authorized record is not allowed to perform this action."},
|
|
||||||
TestAppFactory: testAppFactory,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "POST /smart/refresh - non-user system should fail",
|
|
||||||
Method: http.MethodPost,
|
|
||||||
URL: fmt.Sprintf("/api/beszel/smart/refresh?system=%s", system.Id),
|
|
||||||
Headers: map[string]string{
|
|
||||||
"Authorization": user2Token,
|
|
||||||
},
|
|
||||||
ExpectedStatus: 404,
|
|
||||||
ExpectedContent: []string{"The requested resource wasn't found."},
|
|
||||||
TestAppFactory: testAppFactory,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "POST /smart/refresh - good user should pass validation",
|
|
||||||
Method: http.MethodPost,
|
|
||||||
URL: fmt.Sprintf("/api/beszel/smart/refresh?system=%s", system.Id),
|
|
||||||
Headers: map[string]string{
|
|
||||||
"Authorization": userToken,
|
|
||||||
},
|
|
||||||
ExpectedStatus: 500,
|
|
||||||
ExpectedContent: []string{"Something went wrong while processing your request."},
|
|
||||||
TestAppFactory: testAppFactory,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "POST /user-alerts - no auth should fail",
|
Name: "POST /user-alerts - no auth should fail",
|
||||||
Method: http.MethodPost,
|
Method: http.MethodPost,
|
||||||
@@ -342,42 +273,20 @@ func TestApiRoutesAuthentication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Name: "GET /containers/logs - no auth should fail",
|
Name: "GET /containers/logs - no auth should fail",
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
URL: "/api/beszel/containers/logs?system=test-system&container=abababababab",
|
URL: "/api/beszel/containers/logs?system=test-system&container=test-container",
|
||||||
ExpectedStatus: 401,
|
ExpectedStatus: 401,
|
||||||
ExpectedContent: []string{"requires valid"},
|
ExpectedContent: []string{"requires valid"},
|
||||||
TestAppFactory: testAppFactory,
|
TestAppFactory: testAppFactory,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "GET /containers/logs - request for valid non-user system should fail",
|
|
||||||
Method: http.MethodGet,
|
|
||||||
URL: fmt.Sprintf("/api/beszel/containers/logs?system=%s&container=abababababab", system.Id),
|
|
||||||
ExpectedStatus: 404,
|
|
||||||
ExpectedContent: []string{"The requested resource wasn't found."},
|
|
||||||
TestAppFactory: testAppFactory,
|
|
||||||
Headers: map[string]string{
|
|
||||||
"Authorization": user2Token,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "GET /containers/info - request for valid non-user system should fail",
|
|
||||||
Method: http.MethodGet,
|
|
||||||
URL: fmt.Sprintf("/api/beszel/containers/info?system=%s&container=abababababab", system.Id),
|
|
||||||
ExpectedStatus: 404,
|
|
||||||
ExpectedContent: []string{"The requested resource wasn't found."},
|
|
||||||
TestAppFactory: testAppFactory,
|
|
||||||
Headers: map[string]string{
|
|
||||||
"Authorization": user2Token,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "GET /containers/logs - with auth but missing system param should fail",
|
Name: "GET /containers/logs - with auth but missing system param should fail",
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
URL: "/api/beszel/containers/logs?container=abababababab",
|
URL: "/api/beszel/containers/logs?container=test-container",
|
||||||
Headers: map[string]string{
|
Headers: map[string]string{
|
||||||
"Authorization": userToken,
|
"Authorization": userToken,
|
||||||
},
|
},
|
||||||
ExpectedStatus: 400,
|
ExpectedStatus: 400,
|
||||||
ExpectedContent: []string{"Invalid", "parameter"},
|
ExpectedContent: []string{"system and container parameters are required"},
|
||||||
TestAppFactory: testAppFactory,
|
TestAppFactory: testAppFactory,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -388,7 +297,7 @@ func TestApiRoutesAuthentication(t *testing.T) {
|
|||||||
"Authorization": userToken,
|
"Authorization": userToken,
|
||||||
},
|
},
|
||||||
ExpectedStatus: 400,
|
ExpectedStatus: 400,
|
||||||
ExpectedContent: []string{"Invalid", "parameter"},
|
ExpectedContent: []string{"system and container parameters are required"},
|
||||||
TestAppFactory: testAppFactory,
|
TestAppFactory: testAppFactory,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -399,7 +308,7 @@ func TestApiRoutesAuthentication(t *testing.T) {
|
|||||||
"Authorization": userToken,
|
"Authorization": userToken,
|
||||||
},
|
},
|
||||||
ExpectedStatus: 404,
|
ExpectedStatus: 404,
|
||||||
ExpectedContent: []string{"The requested resource wasn't found."},
|
ExpectedContent: []string{"system not found"},
|
||||||
TestAppFactory: testAppFactory,
|
TestAppFactory: testAppFactory,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -410,7 +319,7 @@ func TestApiRoutesAuthentication(t *testing.T) {
|
|||||||
"Authorization": userToken,
|
"Authorization": userToken,
|
||||||
},
|
},
|
||||||
ExpectedStatus: 400,
|
ExpectedStatus: 400,
|
||||||
ExpectedContent: []string{"Invalid", "parameter"},
|
ExpectedContent: []string{"invalid container parameter"},
|
||||||
TestAppFactory: testAppFactory,
|
TestAppFactory: testAppFactory,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -421,7 +330,7 @@ func TestApiRoutesAuthentication(t *testing.T) {
|
|||||||
"Authorization": userToken,
|
"Authorization": userToken,
|
||||||
},
|
},
|
||||||
ExpectedStatus: 400,
|
ExpectedStatus: 400,
|
||||||
ExpectedContent: []string{"Invalid", "parameter"},
|
ExpectedContent: []string{"invalid container parameter"},
|
||||||
TestAppFactory: testAppFactory,
|
TestAppFactory: testAppFactory,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -432,114 +341,9 @@ func TestApiRoutesAuthentication(t *testing.T) {
|
|||||||
"Authorization": userToken,
|
"Authorization": userToken,
|
||||||
},
|
},
|
||||||
ExpectedStatus: 400,
|
ExpectedStatus: 400,
|
||||||
ExpectedContent: []string{"Invalid", "parameter"},
|
ExpectedContent: []string{"invalid container parameter"},
|
||||||
TestAppFactory: testAppFactory,
|
TestAppFactory: testAppFactory,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "GET /containers/logs - good user should pass validation",
|
|
||||||
Method: http.MethodGet,
|
|
||||||
URL: "/api/beszel/containers/logs?system=" + system.Id + "&container=0123456789ab",
|
|
||||||
Headers: map[string]string{
|
|
||||||
"Authorization": userToken,
|
|
||||||
},
|
|
||||||
ExpectedStatus: 500,
|
|
||||||
ExpectedContent: []string{"Something went wrong while processing your request."},
|
|
||||||
TestAppFactory: testAppFactory,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "GET /containers/info - good user should pass validation",
|
|
||||||
Method: http.MethodGet,
|
|
||||||
URL: "/api/beszel/containers/info?system=" + system.Id + "&container=0123456789ab",
|
|
||||||
Headers: map[string]string{
|
|
||||||
"Authorization": userToken,
|
|
||||||
},
|
|
||||||
ExpectedStatus: 500,
|
|
||||||
ExpectedContent: []string{"Something went wrong while processing your request."},
|
|
||||||
TestAppFactory: testAppFactory,
|
|
||||||
},
|
|
||||||
// /systemd routes
|
|
||||||
{
|
|
||||||
Name: "GET /systemd/info - no auth should fail",
|
|
||||||
Method: http.MethodGet,
|
|
||||||
URL: fmt.Sprintf("/api/beszel/systemd/info?system=%s&service=nginx.service", system.Id),
|
|
||||||
ExpectedStatus: 401,
|
|
||||||
ExpectedContent: []string{"requires valid"},
|
|
||||||
TestAppFactory: testAppFactory,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "GET /systemd/info - request for valid non-user system should fail",
|
|
||||||
Method: http.MethodGet,
|
|
||||||
URL: fmt.Sprintf("/api/beszel/systemd/info?system=%s&service=nginx.service", system.Id),
|
|
||||||
ExpectedStatus: 404,
|
|
||||||
ExpectedContent: []string{"The requested resource wasn't found."},
|
|
||||||
TestAppFactory: testAppFactory,
|
|
||||||
Headers: map[string]string{
|
|
||||||
"Authorization": user2Token,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "GET /systemd/info - with auth but missing system param should fail",
|
|
||||||
Method: http.MethodGet,
|
|
||||||
URL: "/api/beszel/systemd/info?service=nginx.service",
|
|
||||||
Headers: map[string]string{
|
|
||||||
"Authorization": userToken,
|
|
||||||
},
|
|
||||||
ExpectedStatus: 400,
|
|
||||||
ExpectedContent: []string{"Invalid", "parameter"},
|
|
||||||
TestAppFactory: testAppFactory,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "GET /systemd/info - with auth but missing service param should fail",
|
|
||||||
Method: http.MethodGet,
|
|
||||||
URL: fmt.Sprintf("/api/beszel/systemd/info?system=%s", system.Id),
|
|
||||||
Headers: map[string]string{
|
|
||||||
"Authorization": userToken,
|
|
||||||
},
|
|
||||||
ExpectedStatus: 400,
|
|
||||||
ExpectedContent: []string{"Invalid", "parameter"},
|
|
||||||
TestAppFactory: testAppFactory,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "GET /systemd/info - with auth but invalid system should fail",
|
|
||||||
Method: http.MethodGet,
|
|
||||||
URL: "/api/beszel/systemd/info?system=invalid-system&service=nginx.service",
|
|
||||||
Headers: map[string]string{
|
|
||||||
"Authorization": userToken,
|
|
||||||
},
|
|
||||||
ExpectedStatus: 404,
|
|
||||||
ExpectedContent: []string{"The requested resource wasn't found."},
|
|
||||||
TestAppFactory: testAppFactory,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "GET /systemd/info - service not in systemd_services collection should fail",
|
|
||||||
Method: http.MethodGet,
|
|
||||||
URL: fmt.Sprintf("/api/beszel/systemd/info?system=%s&service=notregistered.service", system.Id),
|
|
||||||
Headers: map[string]string{
|
|
||||||
"Authorization": userToken,
|
|
||||||
},
|
|
||||||
ExpectedStatus: 404,
|
|
||||||
ExpectedContent: []string{"The requested resource wasn't found."},
|
|
||||||
TestAppFactory: testAppFactory,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "GET /systemd/info - with auth and existing service record should pass validation",
|
|
||||||
Method: http.MethodGet,
|
|
||||||
URL: fmt.Sprintf("/api/beszel/systemd/info?system=%s&service=nginx.service", system.Id),
|
|
||||||
Headers: map[string]string{
|
|
||||||
"Authorization": userToken,
|
|
||||||
},
|
|
||||||
ExpectedStatus: 500,
|
|
||||||
ExpectedContent: []string{"Something went wrong while processing your request."},
|
|
||||||
TestAppFactory: testAppFactory,
|
|
||||||
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
|
|
||||||
beszelTests.CreateRecord(app, "systemd_services", map[string]any{
|
|
||||||
"system": system.Id,
|
|
||||||
"name": "nginx.service",
|
|
||||||
"state": 0,
|
|
||||||
"sub": 1,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Auth Optional Routes - Should work without authentication
|
// Auth Optional Routes - Should work without authentication
|
||||||
{
|
{
|
||||||
@@ -630,17 +434,13 @@ func TestApiRoutesAuthentication(t *testing.T) {
|
|||||||
"systems": []string{system.Id},
|
"systems": []string{system.Id},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
// this works but diff behavior on prod vs dev.
|
{
|
||||||
// dev returns 502; prod returns 200 with static html page 404
|
Name: "GET /update - shouldn't exist without CHECK_UPDATES env var",
|
||||||
// TODO: align dev and prod behavior and re-enable this test
|
Method: http.MethodGet,
|
||||||
// {
|
URL: "/api/beszel/update",
|
||||||
// Name: "GET /update - shouldn't exist without CHECK_UPDATES env var",
|
ExpectedStatus: 502,
|
||||||
// Method: http.MethodGet,
|
TestAppFactory: testAppFactory,
|
||||||
// URL: "/api/beszel/update",
|
},
|
||||||
// NotExpectedContent: []string{"v:", "\"v\":"},
|
|
||||||
// ExpectedStatus: 502,
|
|
||||||
// TestAppFactory: testAppFactory,
|
|
||||||
// },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, scenario := range scenarios {
|
for _, scenario := range scenarios {
|
||||||
|
|||||||
@@ -279,6 +279,9 @@ func createFingerprintRecord(app core.App, systemID, token string) error {
|
|||||||
|
|
||||||
// Returns the current config.yml file as a JSON object
|
// Returns the current config.yml file as a JSON object
|
||||||
func GetYamlConfig(e *core.RequestEvent) error {
|
func GetYamlConfig(e *core.RequestEvent) error {
|
||||||
|
if e.Auth.GetString("role") != "admin" {
|
||||||
|
return e.ForbiddenError("Requires admin role", nil)
|
||||||
|
}
|
||||||
configContent, err := generateYAML(e.App)
|
configContent, err := generateYAML(e.App)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/henrygd/beszel/internal/alerts"
|
"github.com/henrygd/beszel/internal/alerts"
|
||||||
@@ -37,6 +38,8 @@ type Hub struct {
|
|||||||
appURL string
|
appURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var containerIDPattern = regexp.MustCompile(`^[a-fA-F0-9]{12,64}$`)
|
||||||
|
|
||||||
// NewHub creates a new Hub instance with default configuration
|
// NewHub creates a new Hub instance with default configuration
|
||||||
func NewHub(app core.App) *Hub {
|
func NewHub(app core.App) *Hub {
|
||||||
hub := &Hub{App: app}
|
hub := &Hub{App: app}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package hub
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -61,6 +62,7 @@ func (rm *responseModifier) modifyHTML(html string) string {
|
|||||||
|
|
||||||
// startServer sets up the development server for Beszel
|
// startServer sets up the development server for Beszel
|
||||||
func (h *Hub) startServer(se *core.ServeEvent) error {
|
func (h *Hub) startServer(se *core.ServeEvent) error {
|
||||||
|
slog.Info("starting server", "appURL", h.appURL)
|
||||||
proxy := httputil.NewSingleHostReverseProxy(&url.URL{
|
proxy := httputil.NewSingleHostReverseProxy(&url.URL{
|
||||||
Scheme: "http",
|
Scheme: "http",
|
||||||
Host: "localhost:5173",
|
Host: "localhost:5173",
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@@ -146,7 +145,6 @@ func (sys *System) update() error {
|
|||||||
// update smart interval if it's set on the agent side
|
// update smart interval if it's set on the agent side
|
||||||
if data.Details.SmartInterval > 0 {
|
if data.Details.SmartInterval > 0 {
|
||||||
sys.smartInterval = data.Details.SmartInterval
|
sys.smartInterval = data.Details.SmartInterval
|
||||||
sys.manager.hub.Logger().Info("SMART interval updated from agent details", "system", sys.Id, "interval", sys.smartInterval.String())
|
|
||||||
// make sure we reset expiration of lastFetch to remain as long as the new smart interval
|
// make sure we reset expiration of lastFetch to remain as long as the new smart interval
|
||||||
// to prevent premature expiration leading to new fetch if interval is different.
|
// to prevent premature expiration leading to new fetch if interval is different.
|
||||||
sys.manager.smartFetchMap.UpdateExpiration(sys.Id, sys.smartInterval+time.Minute)
|
sys.manager.smartFetchMap.UpdateExpiration(sys.Id, sys.smartInterval+time.Minute)
|
||||||
@@ -158,10 +156,11 @@ func (sys *System) update() error {
|
|||||||
if sys.smartInterval <= 0 {
|
if sys.smartInterval <= 0 {
|
||||||
sys.smartInterval = time.Hour
|
sys.smartInterval = time.Hour
|
||||||
}
|
}
|
||||||
if sys.shouldFetchSmart() && sys.smartFetching.CompareAndSwap(false, true) {
|
lastFetch, _ := sys.manager.smartFetchMap.GetOk(sys.Id)
|
||||||
sys.manager.hub.Logger().Info("SMART fetch", "system", sys.Id, "interval", sys.smartInterval.String())
|
if time.Since(time.UnixMilli(lastFetch-1e4)) >= sys.smartInterval && sys.smartFetching.CompareAndSwap(false, true) {
|
||||||
go func() {
|
go func() {
|
||||||
defer sys.smartFetching.Store(false)
|
defer sys.smartFetching.Store(false)
|
||||||
|
sys.manager.smartFetchMap.Set(sys.Id, time.Now().UnixMilli(), sys.smartInterval+time.Minute)
|
||||||
_ = sys.FetchAndSaveSmartDevices()
|
_ = sys.FetchAndSaveSmartDevices()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@@ -185,7 +184,7 @@ func (sys *System) handlePaused() {
|
|||||||
|
|
||||||
// createRecords updates the system record and adds system_stats and container_stats records
|
// createRecords updates the system record and adds system_stats and container_stats records
|
||||||
func (sys *System) createRecords(data *system.CombinedData) (*core.Record, error) {
|
func (sys *System) createRecords(data *system.CombinedData) (*core.Record, error) {
|
||||||
systemRecord, err := sys.getRecord(sys.manager.hub)
|
systemRecord, err := sys.getRecord()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -344,8 +343,8 @@ func createContainerRecords(app core.App, data []*container.Stats, systemId stri
|
|||||||
|
|
||||||
// getRecord retrieves the system record from the database.
|
// getRecord retrieves the system record from the database.
|
||||||
// If the record is not found, it removes the system from the manager.
|
// If the record is not found, it removes the system from the manager.
|
||||||
func (sys *System) getRecord(app core.App) (*core.Record, error) {
|
func (sys *System) getRecord() (*core.Record, error) {
|
||||||
record, err := app.FindRecordById("systems", sys.Id)
|
record, err := sys.manager.hub.FindRecordById("systems", sys.Id)
|
||||||
if err != nil || record == nil {
|
if err != nil || record == nil {
|
||||||
_ = sys.manager.RemoveSystem(sys.Id)
|
_ = sys.manager.RemoveSystem(sys.Id)
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -353,16 +352,6 @@ func (sys *System) getRecord(app core.App) (*core.Record, error) {
|
|||||||
return record, nil
|
return record, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasUser checks if the given user ID is in the system's users list.
|
|
||||||
func (sys *System) HasUser(app core.App, userID string) bool {
|
|
||||||
record, err := sys.getRecord(app)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
users := record.GetStringSlice("users")
|
|
||||||
return slices.Contains(users, userID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// setDown marks a system as down in the database.
|
// setDown marks a system as down in the database.
|
||||||
// It takes the original error that caused the system to go down and returns any error
|
// It takes the original error that caused the system to go down and returns any error
|
||||||
// encountered during the process of updating the system status.
|
// encountered during the process of updating the system status.
|
||||||
@@ -370,7 +359,7 @@ func (sys *System) setDown(originalError error) error {
|
|||||||
if sys.Status == down || sys.Status == paused {
|
if sys.Status == down || sys.Status == paused {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
record, err := sys.getRecord(sys.manager.hub)
|
record, err := sys.getRecord()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -654,7 +643,6 @@ func (s *System) createSSHClient() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.agentVersion, _ = extractAgentVersion(string(s.client.Conn.ServerVersion()))
|
s.agentVersion, _ = extractAgentVersion(string(s.client.Conn.ServerVersion()))
|
||||||
s.manager.resetFailedSmartFetchState(s.Id)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ type SystemManager struct {
|
|||||||
hub hubLike // Hub interface for database and alert operations
|
hub hubLike // Hub interface for database and alert operations
|
||||||
systems *store.Store[string, *System] // Thread-safe store of active systems
|
systems *store.Store[string, *System] // Thread-safe store of active systems
|
||||||
sshConfig *ssh.ClientConfig // SSH client configuration for system connections
|
sshConfig *ssh.ClientConfig // SSH client configuration for system connections
|
||||||
smartFetchMap *expirymap.ExpiryMap[smartFetchState] // Stores last SMART fetch time/result; TTL is only for cleanup
|
smartFetchMap *expirymap.ExpiryMap[int64] // Stores last SMART fetch time per system ID
|
||||||
}
|
}
|
||||||
|
|
||||||
// hubLike defines the interface requirements for the hub dependency.
|
// hubLike defines the interface requirements for the hub dependency.
|
||||||
@@ -54,7 +54,6 @@ type hubLike interface {
|
|||||||
GetSSHKey(dataDir string) (ssh.Signer, error)
|
GetSSHKey(dataDir string) (ssh.Signer, error)
|
||||||
HandleSystemAlerts(systemRecord *core.Record, data *system.CombinedData) error
|
HandleSystemAlerts(systemRecord *core.Record, data *system.CombinedData) error
|
||||||
HandleStatusAlerts(status string, systemRecord *core.Record) error
|
HandleStatusAlerts(status string, systemRecord *core.Record) error
|
||||||
CancelPendingStatusAlerts(systemID string)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSystemManager creates a new SystemManager instance with the provided hub.
|
// NewSystemManager creates a new SystemManager instance with the provided hub.
|
||||||
@@ -63,7 +62,7 @@ func NewSystemManager(hub hubLike) *SystemManager {
|
|||||||
return &SystemManager{
|
return &SystemManager{
|
||||||
systems: store.New(map[string]*System{}),
|
systems: store.New(map[string]*System{}),
|
||||||
hub: hub,
|
hub: hub,
|
||||||
smartFetchMap: expirymap.New[smartFetchState](time.Hour),
|
smartFetchMap: expirymap.New[int64](time.Hour),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,7 +189,6 @@ func (sm *SystemManager) onRecordAfterUpdateSuccess(e *core.RecordEvent) error {
|
|||||||
system.closeSSHConnection()
|
system.closeSSHConnection()
|
||||||
}
|
}
|
||||||
_ = deactivateAlerts(e.App, e.Record.Id)
|
_ = deactivateAlerts(e.App, e.Record.Id)
|
||||||
sm.hub.CancelPendingStatusAlerts(e.Record.Id)
|
|
||||||
return e.Next()
|
return e.Next()
|
||||||
case pending:
|
case pending:
|
||||||
// Resume monitoring, preferring existing WebSocket connection
|
// Resume monitoring, preferring existing WebSocket connection
|
||||||
@@ -308,7 +306,6 @@ func (sm *SystemManager) AddWebSocketSystem(systemId string, agentVersion semver
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
sm.resetFailedSmartFetchState(systemId)
|
|
||||||
|
|
||||||
system := sm.NewSystem(systemId)
|
system := sm.NewSystem(systemId)
|
||||||
system.WsConn = wsConn
|
system.WsConn = wsConn
|
||||||
@@ -320,15 +317,6 @@ func (sm *SystemManager) AddWebSocketSystem(systemId string, agentVersion semver
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// resetFailedSmartFetchState clears only failed SMART cooldown entries so a fresh
|
|
||||||
// agent reconnect retries SMART discovery immediately after configuration changes.
|
|
||||||
func (sm *SystemManager) resetFailedSmartFetchState(systemID string) {
|
|
||||||
state, ok := sm.smartFetchMap.GetOk(systemID)
|
|
||||||
if ok && !state.Successful {
|
|
||||||
sm.smartFetchMap.Remove(systemID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// createSSHClientConfig initializes the SSH client configuration for connecting to an agent's server
|
// createSSHClientConfig initializes the SSH client configuration for connecting to an agent's server
|
||||||
func (sm *SystemManager) createSSHClientConfig() error {
|
func (sm *SystemManager) createSSHClientConfig() error {
|
||||||
privateKey, err := sm.hub.GetSSHKey("")
|
privateKey, err := sm.hub.GetSSHKey("")
|
||||||
|
|||||||
@@ -4,61 +4,18 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/henrygd/beszel/internal/entities/smart"
|
"github.com/henrygd/beszel/internal/entities/smart"
|
||||||
"github.com/pocketbase/pocketbase/core"
|
"github.com/pocketbase/pocketbase/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
type smartFetchState struct {
|
|
||||||
LastAttempt int64
|
|
||||||
Successful bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// FetchAndSaveSmartDevices fetches SMART data from the agent and saves it to the database
|
// FetchAndSaveSmartDevices fetches SMART data from the agent and saves it to the database
|
||||||
func (sys *System) FetchAndSaveSmartDevices() error {
|
func (sys *System) FetchAndSaveSmartDevices() error {
|
||||||
smartData, err := sys.FetchSmartDataFromAgent()
|
smartData, err := sys.FetchSmartDataFromAgent()
|
||||||
if err != nil {
|
if err != nil || len(smartData) == 0 {
|
||||||
sys.recordSmartFetchResult(err, 0)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = sys.saveSmartDevices(smartData)
|
return sys.saveSmartDevices(smartData)
|
||||||
sys.recordSmartFetchResult(err, len(smartData))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// recordSmartFetchResult stores a cooldown entry for the SMART interval and marks
|
|
||||||
// whether the last fetch produced any devices, so failed setup can retry on reconnect.
|
|
||||||
func (sys *System) recordSmartFetchResult(err error, deviceCount int) {
|
|
||||||
if sys.manager == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
interval := sys.smartFetchInterval()
|
|
||||||
success := err == nil && deviceCount > 0
|
|
||||||
if sys.manager.hub != nil {
|
|
||||||
sys.manager.hub.Logger().Info("SMART fetch result", "system", sys.Id, "success", success, "devices", deviceCount, "interval", interval.String(), "err", err)
|
|
||||||
}
|
|
||||||
sys.manager.smartFetchMap.Set(sys.Id, smartFetchState{LastAttempt: time.Now().UnixMilli(), Successful: success}, interval+time.Minute)
|
|
||||||
}
|
|
||||||
|
|
||||||
// shouldFetchSmart returns true when there is no active SMART cooldown entry for this system.
|
|
||||||
func (sys *System) shouldFetchSmart() bool {
|
|
||||||
if sys.manager == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
state, ok := sys.manager.smartFetchMap.GetOk(sys.Id)
|
|
||||||
if !ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return !time.UnixMilli(state.LastAttempt).Add(sys.smartFetchInterval()).After(time.Now())
|
|
||||||
}
|
|
||||||
|
|
||||||
// smartFetchInterval returns the agent-provided SMART interval or the default when unset.
|
|
||||||
func (sys *System) smartFetchInterval() time.Duration {
|
|
||||||
if sys.smartInterval > 0 {
|
|
||||||
return sys.smartInterval
|
|
||||||
}
|
|
||||||
return time.Hour
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// saveSmartDevices saves SMART device data to the smart_devices collection
|
// saveSmartDevices saves SMART device data to the smart_devices collection
|
||||||
|
|||||||
@@ -1,94 +0,0 @@
|
|||||||
//go:build testing
|
|
||||||
|
|
||||||
package systems
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/henrygd/beszel/internal/hub/expirymap"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRecordSmartFetchResult(t *testing.T) {
|
|
||||||
sm := &SystemManager{smartFetchMap: expirymap.New[smartFetchState](time.Hour)}
|
|
||||||
t.Cleanup(sm.smartFetchMap.StopCleaner)
|
|
||||||
|
|
||||||
sys := &System{
|
|
||||||
Id: "system-1",
|
|
||||||
manager: sm,
|
|
||||||
smartInterval: time.Hour,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Successful fetch with devices
|
|
||||||
sys.recordSmartFetchResult(nil, 5)
|
|
||||||
state, ok := sm.smartFetchMap.GetOk(sys.Id)
|
|
||||||
assert.True(t, ok, "expected smart fetch result to be stored")
|
|
||||||
assert.True(t, state.Successful, "expected successful fetch state to be recorded")
|
|
||||||
|
|
||||||
// Failed fetch
|
|
||||||
sys.recordSmartFetchResult(errors.New("failed"), 0)
|
|
||||||
state, ok = sm.smartFetchMap.GetOk(sys.Id)
|
|
||||||
assert.True(t, ok, "expected failed smart fetch state to be stored")
|
|
||||||
assert.False(t, state.Successful, "expected failed smart fetch state to be marked unsuccessful")
|
|
||||||
|
|
||||||
// Successful fetch but no devices
|
|
||||||
sys.recordSmartFetchResult(nil, 0)
|
|
||||||
state, ok = sm.smartFetchMap.GetOk(sys.Id)
|
|
||||||
assert.True(t, ok, "expected fetch with zero devices to be stored")
|
|
||||||
assert.False(t, state.Successful, "expected fetch with zero devices to be marked unsuccessful")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShouldFetchSmart(t *testing.T) {
|
|
||||||
sm := &SystemManager{smartFetchMap: expirymap.New[smartFetchState](time.Hour)}
|
|
||||||
t.Cleanup(sm.smartFetchMap.StopCleaner)
|
|
||||||
|
|
||||||
sys := &System{
|
|
||||||
Id: "system-1",
|
|
||||||
manager: sm,
|
|
||||||
smartInterval: time.Hour,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.True(t, sys.shouldFetchSmart(), "expected initial smart fetch to be allowed")
|
|
||||||
|
|
||||||
sys.recordSmartFetchResult(errors.New("failed"), 0)
|
|
||||||
assert.False(t, sys.shouldFetchSmart(), "expected smart fetch to be blocked while interval entry exists")
|
|
||||||
|
|
||||||
sm.smartFetchMap.Remove(sys.Id)
|
|
||||||
assert.True(t, sys.shouldFetchSmart(), "expected smart fetch to be allowed after interval entry is cleared")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShouldFetchSmart_IgnoresExtendedTTLWhenFetchIsDue(t *testing.T) {
|
|
||||||
sm := &SystemManager{smartFetchMap: expirymap.New[smartFetchState](time.Hour)}
|
|
||||||
t.Cleanup(sm.smartFetchMap.StopCleaner)
|
|
||||||
|
|
||||||
sys := &System{
|
|
||||||
Id: "system-1",
|
|
||||||
manager: sm,
|
|
||||||
smartInterval: time.Hour,
|
|
||||||
}
|
|
||||||
|
|
||||||
sm.smartFetchMap.Set(sys.Id, smartFetchState{
|
|
||||||
LastAttempt: time.Now().Add(-2 * time.Hour).UnixMilli(),
|
|
||||||
Successful: true,
|
|
||||||
}, 10*time.Minute)
|
|
||||||
sm.smartFetchMap.UpdateExpiration(sys.Id, 3*time.Hour)
|
|
||||||
|
|
||||||
assert.True(t, sys.shouldFetchSmart(), "expected fetch time to take precedence over updated TTL")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResetFailedSmartFetchState(t *testing.T) {
|
|
||||||
sm := &SystemManager{smartFetchMap: expirymap.New[smartFetchState](time.Hour)}
|
|
||||||
t.Cleanup(sm.smartFetchMap.StopCleaner)
|
|
||||||
|
|
||||||
sm.smartFetchMap.Set("system-1", smartFetchState{LastAttempt: time.Now().UnixMilli(), Successful: false}, time.Hour)
|
|
||||||
sm.resetFailedSmartFetchState("system-1")
|
|
||||||
_, ok := sm.smartFetchMap.GetOk("system-1")
|
|
||||||
assert.False(t, ok, "expected failed smart fetch state to be cleared on reconnect")
|
|
||||||
|
|
||||||
sm.smartFetchMap.Set("system-1", smartFetchState{LastAttempt: time.Now().UnixMilli(), Successful: true}, time.Hour)
|
|
||||||
sm.resetFailedSmartFetchState("system-1")
|
|
||||||
_, ok = sm.smartFetchMap.GetOk("system-1")
|
|
||||||
assert.True(t, ok, "expected successful smart fetch state to be preserved")
|
|
||||||
}
|
|
||||||
@@ -230,9 +230,6 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
|
|||||||
sum.Bandwidth[1] += stats.Bandwidth[1]
|
sum.Bandwidth[1] += stats.Bandwidth[1]
|
||||||
sum.DiskIO[0] += stats.DiskIO[0]
|
sum.DiskIO[0] += stats.DiskIO[0]
|
||||||
sum.DiskIO[1] += stats.DiskIO[1]
|
sum.DiskIO[1] += stats.DiskIO[1]
|
||||||
for i := range stats.DiskIoStats {
|
|
||||||
sum.DiskIoStats[i] += stats.DiskIoStats[i]
|
|
||||||
}
|
|
||||||
batterySum += int(stats.Battery[0])
|
batterySum += int(stats.Battery[0])
|
||||||
sum.Battery[1] = stats.Battery[1]
|
sum.Battery[1] = stats.Battery[1]
|
||||||
|
|
||||||
@@ -257,9 +254,6 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
|
|||||||
sum.MaxBandwidth[1] = max(sum.MaxBandwidth[1], stats.MaxBandwidth[1], stats.Bandwidth[1])
|
sum.MaxBandwidth[1] = max(sum.MaxBandwidth[1], stats.MaxBandwidth[1], stats.Bandwidth[1])
|
||||||
sum.MaxDiskIO[0] = max(sum.MaxDiskIO[0], stats.MaxDiskIO[0], stats.DiskIO[0])
|
sum.MaxDiskIO[0] = max(sum.MaxDiskIO[0], stats.MaxDiskIO[0], stats.DiskIO[0])
|
||||||
sum.MaxDiskIO[1] = max(sum.MaxDiskIO[1], stats.MaxDiskIO[1], stats.DiskIO[1])
|
sum.MaxDiskIO[1] = max(sum.MaxDiskIO[1], stats.MaxDiskIO[1], stats.DiskIO[1])
|
||||||
for i := range stats.DiskIoStats {
|
|
||||||
sum.MaxDiskIoStats[i] = max(sum.MaxDiskIoStats[i], stats.MaxDiskIoStats[i], stats.DiskIoStats[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accumulate network interfaces
|
// Accumulate network interfaces
|
||||||
if sum.NetworkInterfaces == nil {
|
if sum.NetworkInterfaces == nil {
|
||||||
@@ -305,10 +299,6 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
|
|||||||
fs.DiskWriteBytes += value.DiskWriteBytes
|
fs.DiskWriteBytes += value.DiskWriteBytes
|
||||||
fs.MaxDiskReadBytes = max(fs.MaxDiskReadBytes, value.MaxDiskReadBytes, value.DiskReadBytes)
|
fs.MaxDiskReadBytes = max(fs.MaxDiskReadBytes, value.MaxDiskReadBytes, value.DiskReadBytes)
|
||||||
fs.MaxDiskWriteBytes = max(fs.MaxDiskWriteBytes, value.MaxDiskWriteBytes, value.DiskWriteBytes)
|
fs.MaxDiskWriteBytes = max(fs.MaxDiskWriteBytes, value.MaxDiskWriteBytes, value.DiskWriteBytes)
|
||||||
for i := range value.DiskIoStats {
|
|
||||||
fs.DiskIoStats[i] += value.DiskIoStats[i]
|
|
||||||
fs.MaxDiskIoStats[i] = max(fs.MaxDiskIoStats[i], value.MaxDiskIoStats[i], value.DiskIoStats[i])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,9 +350,6 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
|
|||||||
sum.DiskWritePs = twoDecimals(sum.DiskWritePs / count)
|
sum.DiskWritePs = twoDecimals(sum.DiskWritePs / count)
|
||||||
sum.DiskIO[0] = sum.DiskIO[0] / uint64(count)
|
sum.DiskIO[0] = sum.DiskIO[0] / uint64(count)
|
||||||
sum.DiskIO[1] = sum.DiskIO[1] / uint64(count)
|
sum.DiskIO[1] = sum.DiskIO[1] / uint64(count)
|
||||||
for i := range sum.DiskIoStats {
|
|
||||||
sum.DiskIoStats[i] = twoDecimals(sum.DiskIoStats[i] / count)
|
|
||||||
}
|
|
||||||
sum.NetworkSent = twoDecimals(sum.NetworkSent / count)
|
sum.NetworkSent = twoDecimals(sum.NetworkSent / count)
|
||||||
sum.NetworkRecv = twoDecimals(sum.NetworkRecv / count)
|
sum.NetworkRecv = twoDecimals(sum.NetworkRecv / count)
|
||||||
sum.LoadAvg[0] = twoDecimals(sum.LoadAvg[0] / count)
|
sum.LoadAvg[0] = twoDecimals(sum.LoadAvg[0] / count)
|
||||||
@@ -401,9 +388,6 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
|
|||||||
fs.DiskReadPs = twoDecimals(fs.DiskReadPs / count)
|
fs.DiskReadPs = twoDecimals(fs.DiskReadPs / count)
|
||||||
fs.DiskReadBytes = fs.DiskReadBytes / uint64(count)
|
fs.DiskReadBytes = fs.DiskReadBytes / uint64(count)
|
||||||
fs.DiskWriteBytes = fs.DiskWriteBytes / uint64(count)
|
fs.DiskWriteBytes = fs.DiskWriteBytes / uint64(count)
|
||||||
for i := range fs.DiskIoStats {
|
|
||||||
fs.DiskIoStats[i] = twoDecimals(fs.DiskIoStats[i] / count)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
"recharts": "^2.15.4",
|
"recharts": "^2.15.4",
|
||||||
"shiki": "^3.13.0",
|
"shiki": "^3.13.0",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"valibot": "^1.3.1",
|
"valibot": "^0.42.1",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "2.2.4",
|
"@biomejs/biome": "2.2.4",
|
||||||
@@ -927,7 +927,7 @@
|
|||||||
|
|
||||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||||
|
|
||||||
"valibot": ["valibot@1.3.1", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-sfdRir/QFM0JaF22hqTroPc5xy4DimuGQVKFrzF1YfGwaS1nJot3Y8VqMdLO2Lg27fMzat2yD3pY5PbAYO39Gg=="],
|
"valibot": ["valibot@0.42.1", "", { "peerDependencies": { "typescript": ">=5" } }, "sha512-3keXV29Ar5b//Hqi4MbSdV7lfVp6zuYLZuA9V1PvQUsXqogr+u5lvLPLk3A4f74VUXDnf/JfWMN6sB+koJ/FFw=="],
|
||||||
|
|
||||||
"vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
|
"vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="manifest" href="./static/manifest.json" crossorigin="use-credentials" />
|
<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" />
|
||||||
<link rel="apple-touch-icon" href="./static/icon.png" />
|
|
||||||
<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" />
|
||||||
<title>Beszel</title>
|
<title>Beszel</title>
|
||||||
|
|||||||
407
internal/site/package-lock.json
generated
407
internal/site/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "beszel",
|
"name": "beszel",
|
||||||
"version": "0.18.7",
|
"version": "0.18.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "beszel",
|
"name": "beszel",
|
||||||
"version": "0.18.7",
|
"version": "0.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@henrygd/queue": "^1.0.7",
|
"@henrygd/queue": "^1.0.7",
|
||||||
"@henrygd/semaphore": "^0.0.2",
|
"@henrygd/semaphore": "^0.0.2",
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
"recharts": "^2.15.4",
|
"recharts": "^2.15.4",
|
||||||
"shiki": "^3.13.0",
|
"shiki": "^3.13.0",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"valibot": "^1.3.1"
|
"valibot": "^0.42.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "2.2.4",
|
"@biomejs/biome": "2.2.4",
|
||||||
@@ -986,6 +986,29 @@
|
|||||||
"integrity": "sha512-N3W7MKwTRmAxOjeG0NAT18oe2Xn3KdjkpMR6crbkF1UDamMGPjyigqEsefiv+qTaxibtc1a+zXCVzb9YXANVqw==",
|
"integrity": "sha512-N3W7MKwTRmAxOjeG0NAT18oe2Xn3KdjkpMR6crbkF1UDamMGPjyigqEsefiv+qTaxibtc1a+zXCVzb9YXANVqw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@isaacs/balanced-match": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "20 || >=22"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@isaacs/brace-expansion": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@isaacs/balanced-match": "^4.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "20 || >=22"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@isaacs/cliui": {
|
"node_modules/@isaacs/cliui": {
|
||||||
"version": "8.0.2",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||||
@@ -1220,9 +1243,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@lingui/cli/node_modules/picomatch": {
|
"node_modules/@lingui/cli/node_modules/picomatch": {
|
||||||
"version": "2.3.2",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2385,9 +2408,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.60.1",
|
"version": "4.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.48.1.tgz",
|
||||||
"integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==",
|
"integrity": "sha512-rGmb8qoG/zdmKoYELCBwu7vt+9HxZ7Koos3pD0+sH5fR3u3Wb/jGcpnqxcnWsPEKDUyzeLSqksN8LJtgXjqBYw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -2399,9 +2422,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.60.1",
|
"version": "4.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.48.1.tgz",
|
||||||
"integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==",
|
"integrity": "sha512-4e9WtTxrk3gu1DFE+imNJr4WsL13nWbD/Y6wQcyku5qadlKHY3OQ3LJ/INrrjngv2BJIHnIzbqMk1GTAC2P8yQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -2413,9 +2436,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.60.1",
|
"version": "4.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.48.1.tgz",
|
||||||
"integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==",
|
"integrity": "sha512-+XjmyChHfc4TSs6WUQGmVf7Hkg8ferMAE2aNYYWjiLzAS/T62uOsdfnqv+GHRjq7rKRnYh4mwWb4Hz7h/alp8A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -2427,9 +2450,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.60.1",
|
"version": "4.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.48.1.tgz",
|
||||||
"integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==",
|
"integrity": "sha512-upGEY7Ftw8M6BAJyGwnwMw91rSqXTcOKZnnveKrVWsMTF8/k5mleKSuh7D4v4IV1pLxKAk3Tbs0Lo9qYmii5mQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -2441,9 +2464,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||||
"version": "4.60.1",
|
"version": "4.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.48.1.tgz",
|
||||||
"integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==",
|
"integrity": "sha512-P9ViWakdoynYFUOZhqq97vBrhuvRLAbN/p2tAVJvhLb8SvN7rbBnJQcBu8e/rQts42pXGLVhfsAP0k9KXWa3nQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -2455,9 +2478,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||||
"version": "4.60.1",
|
"version": "4.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.48.1.tgz",
|
||||||
"integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==",
|
"integrity": "sha512-VLKIwIpnBya5/saccM8JshpbxfyJt0Dsli0PjXozHwbSVaHTvWXJH1bbCwPXxnMzU4zVEfgD1HpW3VQHomi2AQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -2469,9 +2492,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.60.1",
|
"version": "4.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.48.1.tgz",
|
||||||
"integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==",
|
"integrity": "sha512-3zEuZsXfKaw8n/yF7t8N6NNdhyFw3s8xJTqjbTDXlipwrEHo4GtIKcMJr5Ed29leLpB9AugtAQpAHW0jvtKKaQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -2483,9 +2506,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
"version": "4.60.1",
|
"version": "4.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.48.1.tgz",
|
||||||
"integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==",
|
"integrity": "sha512-leo9tOIlKrcBmmEypzunV/2w946JeLbTdDlwEZ7OnnsUyelZ72NMnT4B2vsikSgwQifjnJUbdXzuW4ToN1wV+Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -2497,9 +2520,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.60.1",
|
"version": "4.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.48.1.tgz",
|
||||||
"integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==",
|
"integrity": "sha512-Vy/WS4z4jEyvnJm+CnPfExIv5sSKqZrUr98h03hpAMbE2aI0aD2wvK6GiSe8Gx2wGp3eD81cYDpLLBqNb2ydwQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -2511,9 +2534,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.60.1",
|
"version": "4.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.48.1.tgz",
|
||||||
"integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==",
|
"integrity": "sha512-x5Kzn7XTwIssU9UYqWDB9VpLpfHYuXw5c6bJr4Mzv9kIv242vmJHbI5PJJEnmBYitUIfoMCODDhR7KoZLot2VQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -2524,24 +2547,10 @@
|
|||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||||
"version": "4.60.1",
|
"version": "4.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.48.1.tgz",
|
||||||
"integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==",
|
"integrity": "sha512-yzCaBbwkkWt/EcgJOKDUdUpMHjhiZT/eDktOPWvSRpqrVE04p0Nd6EGV4/g7MARXXeOqstflqsKuXVM3H9wOIQ==",
|
||||||
"cpu": [
|
|
||||||
"loong64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
|
||||||
"version": "4.60.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz",
|
|
||||||
"integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==",
|
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
@@ -2553,23 +2562,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||||
"version": "4.60.1",
|
"version": "4.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.48.1.tgz",
|
||||||
"integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==",
|
"integrity": "sha512-UK0WzWUjMAJccHIeOpPhPcKBqax7QFg47hwZTp6kiMhQHeOYJeaMwzeRZe1q5IiTKsaLnHu9s6toSYVUlZ2QtQ==",
|
||||||
"cpu": [
|
|
||||||
"ppc64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
|
||||||
"version": "4.60.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz",
|
|
||||||
"integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==",
|
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -2581,9 +2576,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.60.1",
|
"version": "4.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.48.1.tgz",
|
||||||
"integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==",
|
"integrity": "sha512-3NADEIlt+aCdCbWVZ7D3tBjBX1lHpXxcvrLt/kdXTiBrOds8APTdtk2yRL2GgmnSVeX4YS1JIf0imFujg78vpw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -2595,9 +2590,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||||
"version": "4.60.1",
|
"version": "4.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.48.1.tgz",
|
||||||
"integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==",
|
"integrity": "sha512-euuwm/QTXAMOcyiFCcrx0/S2jGvFlKJ2Iro8rsmYL53dlblp3LkUQVFzEidHhvIPPvcIsxDhl2wkBE+I6YVGzA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -2609,9 +2604,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
"version": "4.60.1",
|
"version": "4.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.48.1.tgz",
|
||||||
"integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==",
|
"integrity": "sha512-w8mULUjmPdWLJgmTYJx/W6Qhln1a+yqvgwmGXcQl2vFBkWsKGUBRbtLRuKJUln8Uaimf07zgJNxOhHOvjSQmBQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@@ -2623,9 +2618,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.60.1",
|
"version": "4.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.48.1.tgz",
|
||||||
"integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==",
|
"integrity": "sha512-90taWXCWxTbClWuMZD0DKYohY1EovA+W5iytpE89oUPmT5O1HFdf8cuuVIylE6vCbrGdIGv85lVRzTcpTRZ+kA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -2637,9 +2632,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.60.1",
|
"version": "4.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.48.1.tgz",
|
||||||
"integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==",
|
"integrity": "sha512-2Gu29SkFh1FfTRuN1GR1afMuND2GKzlORQUP3mNMJbqdndOg7gNsa81JnORctazHRokiDzQ5+MLE5XYmZW5VWg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -2650,38 +2645,10 @@
|
|||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-openbsd-x64": {
|
|
||||||
"version": "4.60.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz",
|
|
||||||
"integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"openbsd"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
|
||||||
"version": "4.60.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz",
|
|
||||||
"integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"openharmony"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
"version": "4.60.1",
|
"version": "4.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.48.1.tgz",
|
||||||
"integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==",
|
"integrity": "sha512-6kQFR1WuAO50bxkIlAVeIYsz3RUx+xymwhTo9j94dJ+kmHe9ly7muH23sdfWduD0BA8pD9/yhonUvAjxGh34jQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -2693,9 +2660,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.60.1",
|
"version": "4.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.48.1.tgz",
|
||||||
"integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==",
|
"integrity": "sha512-RUyZZ/mga88lMI3RlXFs4WQ7n3VyU07sPXmMG7/C1NOi8qisUg57Y7LRarqoGoAiopmGmChUhSwfpvQ3H5iGSQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -2706,24 +2673,10 @@
|
|||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
|
||||||
"version": "4.60.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz",
|
|
||||||
"integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.60.1",
|
"version": "4.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.48.1.tgz",
|
||||||
"integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==",
|
"integrity": "sha512-8a/caCUN4vkTChxkaIJcMtwIVcBhi4X2PQRoT+yCK3qRYaZ7cURrmJFL5Ux9H9RaMIXj9RuihckdmkBX3zZsgg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -3282,66 +3235,6 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
|
|
||||||
"version": "1.4.5",
|
|
||||||
"dev": true,
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@emnapi/wasi-threads": "1.0.4",
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
|
|
||||||
"version": "1.4.5",
|
|
||||||
"dev": true,
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
|
||||||
"version": "1.0.4",
|
|
||||||
"dev": true,
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
|
|
||||||
"version": "0.2.12",
|
|
||||||
"dev": true,
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@emnapi/core": "^1.4.3",
|
|
||||||
"@emnapi/runtime": "^1.4.3",
|
|
||||||
"@tybys/wasm-util": "^0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
|
|
||||||
"version": "0.10.0",
|
|
||||||
"dev": true,
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
|
|
||||||
"version": "2.8.0",
|
|
||||||
"dev": true,
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "0BSD",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||||
"version": "4.1.12",
|
"version": "4.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz",
|
||||||
@@ -3696,9 +3589,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/anymatch/node_modules/picomatch": {
|
"node_modules/anymatch/node_modules/picomatch": {
|
||||||
"version": "2.3.2",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -3727,16 +3620,6 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/balanced-match": {
|
|
||||||
"version": "4.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
|
||||||
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": "18 || 20 || >=22"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/base64-js": {
|
"node_modules/base64-js": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||||
@@ -3783,19 +3666,6 @@
|
|||||||
"readable-stream": "^3.4.0"
|
"readable-stream": "^3.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/brace-expansion": {
|
|
||||||
"version": "5.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
|
||||||
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"balanced-match": "^4.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "18 || 20 || >=22"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/braces": {
|
"node_modules/braces": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||||
@@ -5202,9 +5072,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lodash": {
|
"node_modules/lodash": {
|
||||||
"version": "4.18.1",
|
"version": "4.17.23",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||||
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
|
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lodash.sortby": {
|
"node_modules/lodash.sortby": {
|
||||||
@@ -5397,9 +5267,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/micromatch/node_modules/picomatch": {
|
"node_modules/micromatch/node_modules/picomatch": {
|
||||||
"version": "2.3.2",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -5420,16 +5290,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "10.2.5",
|
"version": "10.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz",
|
||||||
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
|
"integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BlueOak-1.0.0",
|
"license": "BlueOak-1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^5.0.5"
|
"@isaacs/brace-expansion": "^5.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "18 || 20 || >=22"
|
"node": "20 || >=22"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
@@ -5705,9 +5575,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -6086,9 +5956,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.60.1",
|
"version": "4.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.48.1.tgz",
|
||||||
"integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==",
|
"integrity": "sha512-jVG20NvbhTYDkGAty2/Yh7HK6/q3DGSRH4o8ALKGArmMuaauM9kLfoMZ+WliPwA5+JHr2lTn3g557FxBV87ifg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -6102,31 +5972,26 @@
|
|||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.60.1",
|
"@rollup/rollup-android-arm-eabi": "4.48.1",
|
||||||
"@rollup/rollup-android-arm64": "4.60.1",
|
"@rollup/rollup-android-arm64": "4.48.1",
|
||||||
"@rollup/rollup-darwin-arm64": "4.60.1",
|
"@rollup/rollup-darwin-arm64": "4.48.1",
|
||||||
"@rollup/rollup-darwin-x64": "4.60.1",
|
"@rollup/rollup-darwin-x64": "4.48.1",
|
||||||
"@rollup/rollup-freebsd-arm64": "4.60.1",
|
"@rollup/rollup-freebsd-arm64": "4.48.1",
|
||||||
"@rollup/rollup-freebsd-x64": "4.60.1",
|
"@rollup/rollup-freebsd-x64": "4.48.1",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.60.1",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.48.1",
|
||||||
"@rollup/rollup-linux-arm-musleabihf": "4.60.1",
|
"@rollup/rollup-linux-arm-musleabihf": "4.48.1",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.60.1",
|
"@rollup/rollup-linux-arm64-gnu": "4.48.1",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.60.1",
|
"@rollup/rollup-linux-arm64-musl": "4.48.1",
|
||||||
"@rollup/rollup-linux-loong64-gnu": "4.60.1",
|
"@rollup/rollup-linux-loongarch64-gnu": "4.48.1",
|
||||||
"@rollup/rollup-linux-loong64-musl": "4.60.1",
|
"@rollup/rollup-linux-ppc64-gnu": "4.48.1",
|
||||||
"@rollup/rollup-linux-ppc64-gnu": "4.60.1",
|
"@rollup/rollup-linux-riscv64-gnu": "4.48.1",
|
||||||
"@rollup/rollup-linux-ppc64-musl": "4.60.1",
|
"@rollup/rollup-linux-riscv64-musl": "4.48.1",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.60.1",
|
"@rollup/rollup-linux-s390x-gnu": "4.48.1",
|
||||||
"@rollup/rollup-linux-riscv64-musl": "4.60.1",
|
"@rollup/rollup-linux-x64-gnu": "4.48.1",
|
||||||
"@rollup/rollup-linux-s390x-gnu": "4.60.1",
|
"@rollup/rollup-linux-x64-musl": "4.48.1",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.60.1",
|
"@rollup/rollup-win32-arm64-msvc": "4.48.1",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.60.1",
|
"@rollup/rollup-win32-ia32-msvc": "4.48.1",
|
||||||
"@rollup/rollup-openbsd-x64": "4.60.1",
|
"@rollup/rollup-win32-x64-msvc": "4.48.1",
|
||||||
"@rollup/rollup-openharmony-arm64": "4.60.1",
|
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.60.1",
|
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.60.1",
|
|
||||||
"@rollup/rollup-win32-x64-gnu": "4.60.1",
|
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.60.1",
|
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -6425,9 +6290,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tar": {
|
"node_modules/tar": {
|
||||||
"version": "7.5.13",
|
"version": "7.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz",
|
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz",
|
||||||
"integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==",
|
"integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BlueOak-1.0.0",
|
"license": "BlueOak-1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -6694,9 +6559,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/valibot": {
|
"node_modules/valibot": {
|
||||||
"version": "1.3.1",
|
"version": "0.42.1",
|
||||||
"resolved": "https://registry.npmjs.org/valibot/-/valibot-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/valibot/-/valibot-0.42.1.tgz",
|
||||||
"integrity": "sha512-sfdRir/QFM0JaF22hqTroPc5xy4DimuGQVKFrzF1YfGwaS1nJot3Y8VqMdLO2Lg27fMzat2yD3pY5PbAYO39Gg==",
|
"integrity": "sha512-3keXV29Ar5b//Hqi4MbSdV7lfVp6zuYLZuA9V1PvQUsXqogr+u5lvLPLk3A4f74VUXDnf/JfWMN6sB+koJ/FFw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": ">=5"
|
"typescript": ">=5"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "beszel",
|
"name": "beszel",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.18.7",
|
"version": "0.18.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host",
|
"dev": "vite --host",
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
"recharts": "^2.15.4",
|
"recharts": "^2.15.4",
|
||||||
"shiki": "^3.13.0",
|
"shiki": "^3.13.0",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"valibot": "^1.3.1"
|
"valibot": "^0.42.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "2.2.4",
|
"@biomejs/biome": "2.2.4",
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export default memo(function AlertsButton({ system }: { system: SystemRecord })
|
|||||||
<SheetTrigger asChild>
|
<SheetTrigger asChild>
|
||||||
<Button variant="ghost" size="icon" aria-label={t`Alerts`} data-nolink onClick={() => setOpened(true)}>
|
<Button variant="ghost" size="icon" aria-label={t`Alerts`} data-nolink onClick={() => setOpened(true)}>
|
||||||
<BellIcon
|
<BellIcon
|
||||||
className={cn("size-[1.2em] pointer-events-none", {
|
className={cn("h-[1.2em] w-[1.2em] pointer-events-none", {
|
||||||
"fill-primary": hasSystemAlert,
|
"fill-primary": hasSystemAlert,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,13 +2,11 @@ import { t } from "@lingui/core/macro"
|
|||||||
import { Plural, Trans } from "@lingui/react/macro"
|
import { Plural, Trans } 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 { ChevronDownIcon, GlobeIcon, ServerIcon } from "lucide-react"
|
import { GlobeIcon, ServerIcon } from "lucide-react"
|
||||||
import { lazy, memo, Suspense, useMemo, useState } from "react"
|
import { lazy, memo, Suspense, useMemo, useState } from "react"
|
||||||
import { $router, Link } from "@/components/router"
|
import { $router, Link } from "@/components/router"
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import { Checkbox } from "@/components/ui/checkbox"
|
import { Checkbox } from "@/components/ui/checkbox"
|
||||||
import { DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
import { DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Switch } from "@/components/ui/switch"
|
import { Switch } from "@/components/ui/switch"
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||||
@@ -66,57 +64,11 @@ const deleteAlerts = debounce(async ({ name, systems }: { name: string; systems:
|
|||||||
|
|
||||||
export const AlertDialogContent = memo(function AlertDialogContent({ system }: { system: SystemRecord }) {
|
export const AlertDialogContent = memo(function AlertDialogContent({ system }: { system: SystemRecord }) {
|
||||||
const alerts = useStore($alerts)
|
const alerts = useStore($alerts)
|
||||||
const systems = useStore($systems)
|
|
||||||
const [overwriteExisting, setOverwriteExisting] = useState<boolean | "indeterminate">(false)
|
const [overwriteExisting, setOverwriteExisting] = useState<boolean | "indeterminate">(false)
|
||||||
const [currentTab, setCurrentTab] = useState("system")
|
const [currentTab, setCurrentTab] = useState("system")
|
||||||
// copyKey is used to force remount AlertContent components with
|
|
||||||
// new alert data after copying alerts from another system
|
|
||||||
const [copyKey, setCopyKey] = useState(0)
|
|
||||||
|
|
||||||
const systemAlerts = alerts[system.id] ?? new Map()
|
const systemAlerts = alerts[system.id] ?? new Map()
|
||||||
|
|
||||||
// Systems that have at least one alert configured (excluding the current system)
|
|
||||||
const systemsWithAlerts = useMemo(
|
|
||||||
() => systems.filter((s) => s.id !== system.id && alerts[s.id]?.size),
|
|
||||||
[systems, alerts, system.id]
|
|
||||||
)
|
|
||||||
|
|
||||||
async function copyAlertsFromSystem(sourceSystemId: string) {
|
|
||||||
const sourceAlerts = $alerts.get()[sourceSystemId]
|
|
||||||
if (!sourceAlerts?.size) return
|
|
||||||
try {
|
|
||||||
const currentTargetAlerts = $alerts.get()[system.id] ?? new Map()
|
|
||||||
// Alert names present on target but absent from source should be deleted
|
|
||||||
const namesToDelete = Array.from(currentTargetAlerts.keys()).filter((name) => !sourceAlerts.has(name))
|
|
||||||
await Promise.all([
|
|
||||||
...Array.from(sourceAlerts.values()).map(({ name, value, min }) =>
|
|
||||||
pb.send<{ success: boolean }>(endpoint, {
|
|
||||||
method: "POST",
|
|
||||||
body: { name, value, min, systems: [system.id], overwrite: true },
|
|
||||||
requestKey: name,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
...namesToDelete.map((name) =>
|
|
||||||
pb.send<{ success: boolean }>(endpoint, {
|
|
||||||
method: "DELETE",
|
|
||||||
body: { name, systems: [system.id] },
|
|
||||||
requestKey: name,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
])
|
|
||||||
// Optimistically update the store so components re-mount with correct data
|
|
||||||
// before the realtime subscription event arrives.
|
|
||||||
const newSystemAlerts = new Map<string, AlertRecord>()
|
|
||||||
for (const alert of sourceAlerts.values()) {
|
|
||||||
newSystemAlerts.set(alert.name, { ...alert, system: system.id, triggered: false })
|
|
||||||
}
|
|
||||||
$alerts.setKey(system.id, newSystemAlerts)
|
|
||||||
setCopyKey((k) => k + 1)
|
|
||||||
} catch (error) {
|
|
||||||
failedUpdateToast(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to keep a copy of alerts when we switch to global tab. If we always compare to
|
// We need to keep a copy of alerts when we switch to global tab. If we always compare to
|
||||||
// current alerts, it will only be updated when first checked, then won't be updated because
|
// current alerts, it will only be updated when first checked, then won't be updated because
|
||||||
// after that it exists.
|
// after that it exists.
|
||||||
@@ -141,8 +93,7 @@ export const AlertDialogContent = memo(function AlertDialogContent({ system }: {
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<Tabs defaultValue="system" onValueChange={setCurrentTab}>
|
<Tabs defaultValue="system" onValueChange={setCurrentTab}>
|
||||||
<div className="flex items-center justify-between mb-1 -mt-0.5">
|
<TabsList className="mb-1 -mt-0.5">
|
||||||
<TabsList>
|
|
||||||
<TabsTrigger value="system">
|
<TabsTrigger value="system">
|
||||||
<ServerIcon className="me-2 h-3.5 w-3.5" />
|
<ServerIcon className="me-2 h-3.5 w-3.5" />
|
||||||
<span className="truncate max-w-60">{system.name}</span>
|
<span className="truncate max-w-60">{system.name}</span>
|
||||||
@@ -152,26 +103,8 @@ export const AlertDialogContent = memo(function AlertDialogContent({ system }: {
|
|||||||
<Trans>All Systems</Trans>
|
<Trans>All Systems</Trans>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
{systemsWithAlerts.length > 0 && currentTab === "system" && (
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button variant="ghost" size="sm" className="text-muted-foreground text-xs gap-1.5">
|
|
||||||
<Trans context="Copy alerts from another system">Copy from</Trans>
|
|
||||||
<ChevronDownIcon className="h-3.5 w-3.5" />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end" className="max-h-100 overflow-auto">
|
|
||||||
{systemsWithAlerts.map((s) => (
|
|
||||||
<DropdownMenuItem key={s.id} className="min-w-44" onSelect={() => copyAlertsFromSystem(s.id)}>
|
|
||||||
{s.name}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
))}
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<TabsContent value="system">
|
<TabsContent value="system">
|
||||||
<div key={copyKey} className="grid gap-3">
|
<div className="grid gap-3">
|
||||||
{alertKeys.map((name) => (
|
{alertKeys.map((name) => (
|
||||||
<AlertContent
|
<AlertContent
|
||||||
key={name}
|
key={name}
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ export default function AreaChartDefault({
|
|||||||
hideYAxis = false,
|
hideYAxis = false,
|
||||||
filter,
|
filter,
|
||||||
truncate = false,
|
truncate = false,
|
||||||
chartProps,
|
}: // logRender = false,
|
||||||
}: {
|
{
|
||||||
chartData: ChartData
|
chartData: ChartData
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: accepts different data source types (systemStats or containerData)
|
// biome-ignore lint/suspicious/noExplicitAny: accepts different data source types (systemStats or containerData)
|
||||||
customData?: any[]
|
customData?: any[]
|
||||||
@@ -62,13 +62,13 @@ export default function AreaChartDefault({
|
|||||||
hideYAxis?: boolean
|
hideYAxis?: boolean
|
||||||
filter?: string
|
filter?: string
|
||||||
truncate?: boolean
|
truncate?: boolean
|
||||||
chartProps?: Omit<React.ComponentProps<typeof AreaChart>, "data" | "margin">
|
// logRender?: boolean
|
||||||
}) {
|
}) {
|
||||||
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
||||||
const { isIntersecting, ref } = useIntersectionObserver({ freeze: false })
|
const { isIntersecting, ref } = useIntersectionObserver({ freeze: false })
|
||||||
const sourceData = customData ?? chartData.systemStats
|
const sourceData = customData ?? chartData.systemStats
|
||||||
|
// Only update the rendered data while the chart is visible
|
||||||
const [displayData, setDisplayData] = useState(sourceData)
|
const [displayData, setDisplayData] = useState(sourceData)
|
||||||
const [displayMaxToggled, setDisplayMaxToggled] = useState(maxToggled)
|
|
||||||
|
|
||||||
// Reduce chart redraws by only updating while visible or when chart time changes
|
// Reduce chart redraws by only updating while visible or when chart time changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -78,10 +78,7 @@ export default function AreaChartDefault({
|
|||||||
if (shouldUpdate) {
|
if (shouldUpdate) {
|
||||||
setDisplayData(sourceData)
|
setDisplayData(sourceData)
|
||||||
}
|
}
|
||||||
if (isIntersecting && maxToggled !== displayMaxToggled) {
|
}, [displayData, isIntersecting, sourceData])
|
||||||
setDisplayMaxToggled(maxToggled)
|
|
||||||
}
|
|
||||||
}, [displayData, displayMaxToggled, isIntersecting, maxToggled, sourceData])
|
|
||||||
|
|
||||||
// Use a stable key derived from data point identities and visual properties
|
// Use a stable key derived from data point identities and visual properties
|
||||||
const areasKey = dataPoints?.map((d) => `${d.label}:${d.opacity}`).join("\0")
|
const areasKey = dataPoints?.map((d) => `${d.label}:${d.opacity}`).join("\0")
|
||||||
@@ -109,14 +106,14 @@ export default function AreaChartDefault({
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}, [areasKey, displayMaxToggled])
|
}, [areasKey, maxToggled])
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
if (displayData.length === 0) {
|
if (displayData.length === 0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
// if (logRender) {
|
// if (logRender) {
|
||||||
// console.log("Rendered", dataPoints?.map((d) => d.label).join(", "), new Date())
|
// console.log("Rendered at", new Date(), "for", dataPoints?.at(0)?.label)
|
||||||
// }
|
// }
|
||||||
return (
|
return (
|
||||||
<ChartContainer
|
<ChartContainer
|
||||||
@@ -131,7 +128,6 @@ export default function AreaChartDefault({
|
|||||||
accessibilityLayer
|
accessibilityLayer
|
||||||
data={displayData}
|
data={displayData}
|
||||||
margin={hideYAxis ? { ...chartMargin, left: 5 } : chartMargin}
|
margin={hideYAxis ? { ...chartMargin, left: 5 } : chartMargin}
|
||||||
{...chartProps}
|
|
||||||
>
|
>
|
||||||
<CartesianGrid vertical={false} />
|
<CartesianGrid vertical={false} />
|
||||||
{!hideYAxis && (
|
{!hideYAxis && (
|
||||||
@@ -167,5 +163,5 @@ export default function AreaChartDefault({
|
|||||||
</AreaChart>
|
</AreaChart>
|
||||||
</ChartContainer>
|
</ChartContainer>
|
||||||
)
|
)
|
||||||
}, [displayData, yAxisWidth, filter, Areas])
|
}, [displayData, yAxisWidth, showTotal, filter])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ export default function LineChartDefault({
|
|||||||
hideYAxis = false,
|
hideYAxis = false,
|
||||||
filter,
|
filter,
|
||||||
truncate = false,
|
truncate = false,
|
||||||
chartProps,
|
}: // logRender = false,
|
||||||
}: {
|
{
|
||||||
chartData: ChartData
|
chartData: ChartData
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: accepts different data source types (systemStats or containerData)
|
// biome-ignore lint/suspicious/noExplicitAny: accepts different data source types (systemStats or containerData)
|
||||||
customData?: any[]
|
customData?: any[]
|
||||||
@@ -61,13 +61,13 @@ export default function LineChartDefault({
|
|||||||
hideYAxis?: boolean
|
hideYAxis?: boolean
|
||||||
filter?: string
|
filter?: string
|
||||||
truncate?: boolean
|
truncate?: boolean
|
||||||
chartProps?: Omit<React.ComponentProps<typeof LineChart>, "data" | "margin">
|
// logRender?: boolean
|
||||||
}) {
|
}) {
|
||||||
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
||||||
const { isIntersecting, ref } = useIntersectionObserver({ freeze: false })
|
const { isIntersecting, ref } = useIntersectionObserver({ freeze: false })
|
||||||
const sourceData = customData ?? chartData.systemStats
|
const sourceData = customData ?? chartData.systemStats
|
||||||
|
// Only update the rendered data while the chart is visible
|
||||||
const [displayData, setDisplayData] = useState(sourceData)
|
const [displayData, setDisplayData] = useState(sourceData)
|
||||||
const [displayMaxToggled, setDisplayMaxToggled] = useState(maxToggled)
|
|
||||||
|
|
||||||
// Reduce chart redraws by only updating while visible or when chart time changes
|
// Reduce chart redraws by only updating while visible or when chart time changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -77,10 +77,7 @@ export default function LineChartDefault({
|
|||||||
if (shouldUpdate) {
|
if (shouldUpdate) {
|
||||||
setDisplayData(sourceData)
|
setDisplayData(sourceData)
|
||||||
}
|
}
|
||||||
if (isIntersecting && maxToggled !== displayMaxToggled) {
|
}, [displayData, isIntersecting, sourceData])
|
||||||
setDisplayMaxToggled(maxToggled)
|
|
||||||
}
|
|
||||||
}, [displayData, displayMaxToggled, isIntersecting, maxToggled, sourceData])
|
|
||||||
|
|
||||||
// Use a stable key derived from data point identities and visual properties
|
// Use a stable key derived from data point identities and visual properties
|
||||||
const linesKey = dataPoints?.map((d) => `${d.label}:${d.strokeOpacity ?? ""}`).join("\0")
|
const linesKey = dataPoints?.map((d) => `${d.label}:${d.strokeOpacity ?? ""}`).join("\0")
|
||||||
@@ -108,14 +105,14 @@ export default function LineChartDefault({
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}, [linesKey, displayMaxToggled])
|
}, [linesKey, maxToggled])
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
if (displayData.length === 0) {
|
if (displayData.length === 0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
// if (logRender) {
|
// if (logRender) {
|
||||||
// console.log("Rendered", dataPoints?.map((d) => d.label).join(", "), new Date())
|
// console.log("Rendered at", new Date(), "for", dataPoints?.at(0)?.label)
|
||||||
// }
|
// }
|
||||||
return (
|
return (
|
||||||
<ChartContainer
|
<ChartContainer
|
||||||
@@ -130,7 +127,6 @@ export default function LineChartDefault({
|
|||||||
accessibilityLayer
|
accessibilityLayer
|
||||||
data={displayData}
|
data={displayData}
|
||||||
margin={hideYAxis ? { ...chartMargin, left: 5 } : chartMargin}
|
margin={hideYAxis ? { ...chartMargin, left: 5 } : chartMargin}
|
||||||
{...chartProps}
|
|
||||||
>
|
>
|
||||||
<CartesianGrid vertical={false} />
|
<CartesianGrid vertical={false} />
|
||||||
{!hideYAxis && (
|
{!hideYAxis && (
|
||||||
@@ -166,5 +162,5 @@ export default function LineChartDefault({
|
|||||||
</LineChart>
|
</LineChart>
|
||||||
</ChartContainer>
|
</ChartContainer>
|
||||||
)
|
)
|
||||||
}, [displayData, yAxisWidth, filter, Lines])
|
}, [displayData, yAxisWidth, showTotal, filter, chartData.chartTime])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export default function Navbar() {
|
|||||||
className="p-2 ps-0 me-3 group"
|
className="p-2 ps-0 me-3 group"
|
||||||
onMouseEnter={runOnce(() => import("@/components/routes/home"))}
|
onMouseEnter={runOnce(() => import("@/components/routes/home"))}
|
||||||
>
|
>
|
||||||
<Logo className="h-[1.2rem] md:h-5 fill-foreground" />
|
<Logo className="h-[1.1rem] md:h-5 fill-foreground" />
|
||||||
</Link>
|
</Link>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -125,7 +125,6 @@ export default function Navbar() {
|
|||||||
<DropdownMenuSubContent>{AdminLinks}</DropdownMenuSubContent>
|
<DropdownMenuSubContent>{AdminLinks}</DropdownMenuSubContent>
|
||||||
</DropdownMenuSub>
|
</DropdownMenuSub>
|
||||||
)}
|
)}
|
||||||
{!isReadOnlyUser() && (
|
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="flex items-center"
|
className="flex items-center"
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
@@ -135,7 +134,6 @@ export default function Navbar() {
|
|||||||
<PlusIcon className="h-4 w-4 me-2.5" />
|
<PlusIcon className="h-4 w-4 me-2.5" />
|
||||||
<Trans>Add {{ foo: systemTranslation }}</Trans>
|
<Trans>Add {{ foo: systemTranslation }}</Trans>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuGroup>
|
<DropdownMenuGroup>
|
||||||
@@ -219,12 +217,10 @@ export default function Navbar() {
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
{!isReadOnlyUser() && (
|
|
||||||
<Button variant="outline" className="flex gap-1 ms-2" onClick={() => setAddSystemDialogOpen(true)}>
|
<Button variant="outline" className="flex gap-1 ms-2" onClick={() => setAddSystemDialogOpen(true)}>
|
||||||
<PlusIcon className="h-4 w-4 -ms-1" />
|
<PlusIcon className="h-4 w-4 -ms-1" />
|
||||||
<Trans>Add {{ foo: systemTranslation }}</Trans>
|
<Trans>Add {{ foo: systemTranslation }}</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import { memo, useState } from "react"
|
import { memo, useState } from "react"
|
||||||
import { Trans } from "@lingui/react/macro"
|
import { Trans } from "@lingui/react/macro"
|
||||||
import { compareSemVer, parseSemVer } from "@/lib/utils"
|
import { compareSemVer, parseSemVer } from "@/lib/utils"
|
||||||
|
|
||||||
import type { GPUData } from "@/types"
|
import type { GPUData } from "@/types"
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||||
import InfoBar from "./system/info-bar"
|
import InfoBar from "./system/info-bar"
|
||||||
import { useSystemData } from "./system/use-system-data"
|
import { useSystemData } from "./system/use-system-data"
|
||||||
import { CpuChart, ContainerCpuChart } from "./system/charts/cpu-charts"
|
import { CpuChart, ContainerCpuChart } from "./system/charts/cpu-charts"
|
||||||
import { MemoryChart, ContainerMemoryChart, SwapChart } from "./system/charts/memory-charts"
|
import { MemoryChart, ContainerMemoryChart, SwapChart } from "./system/charts/memory-charts"
|
||||||
import { RootDiskCharts, ExtraFsCharts } from "./system/charts/disk-charts"
|
import { DiskCharts } from "./system/charts/disk-charts"
|
||||||
import { BandwidthChart, ContainerNetworkChart } from "./system/charts/network-charts"
|
import { BandwidthChart, ContainerNetworkChart } from "./system/charts/network-charts"
|
||||||
import { TemperatureChart, BatteryChart } from "./system/charts/sensor-charts"
|
import { TemperatureChart, BatteryChart } from "./system/charts/sensor-charts"
|
||||||
import { GpuPowerChart, GpuDetailCharts } from "./system/charts/gpu-charts"
|
import { GpuPowerChart, GpuDetailCharts } from "./system/charts/gpu-charts"
|
||||||
|
import { ExtraFsCharts } from "./system/charts/extra-fs-charts"
|
||||||
import { LazyContainersTable, LazySmartTable, LazySystemdTable } from "./system/lazy-tables"
|
import { LazyContainersTable, LazySmartTable, LazySystemdTable } from "./system/lazy-tables"
|
||||||
import { LoadAverageChart } from "./system/charts/load-average-chart"
|
import { LoadAverageChart } from "./system/charts/load-average-chart"
|
||||||
import { ContainerIcon, CpuIcon, HardDriveIcon, TerminalSquareIcon } from "lucide-react"
|
import { ContainerIcon, CpuIcon, HardDriveIcon, TerminalSquareIcon } from "lucide-react"
|
||||||
@@ -22,8 +24,6 @@ const SEMVER_0_14_0 = parseSemVer("0.14.0")
|
|||||||
const SEMVER_0_15_0 = parseSemVer("0.15.0")
|
const SEMVER_0_15_0 = parseSemVer("0.15.0")
|
||||||
|
|
||||||
export default memo(function SystemDetail({ id }: { id: string }) {
|
export default memo(function SystemDetail({ id }: { id: string }) {
|
||||||
const systemData = useSystemData(id)
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
system,
|
system,
|
||||||
systemStats,
|
systemStats,
|
||||||
@@ -48,7 +48,7 @@ export default memo(function SystemDetail({ id }: { id: string }) {
|
|||||||
hasGpuData,
|
hasGpuData,
|
||||||
hasGpuEnginesData,
|
hasGpuEnginesData,
|
||||||
hasGpuPowerData,
|
hasGpuPowerData,
|
||||||
} = systemData
|
} = useSystemData(id)
|
||||||
|
|
||||||
// extra margin to add to bottom of page, specifically for temperature chart,
|
// extra margin to add to bottom of page, specifically for temperature chart,
|
||||||
// where the tooltip can go past the bottom of the page if lots of sensors
|
// where the tooltip can go past the bottom of the page if lots of sensors
|
||||||
@@ -103,7 +103,7 @@ export default memo(function SystemDetail({ id }: { id: string }) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<RootDiskCharts systemData={systemData} />
|
<DiskCharts {...coreProps} systemStats={systemStats} />
|
||||||
|
|
||||||
<BandwidthChart {...coreProps} systemStats={systemStats} />
|
<BandwidthChart {...coreProps} systemStats={systemStats} />
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ export default memo(function SystemDetail({ id }: { id: string }) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ExtraFsCharts systemData={systemData} />
|
<ExtraFsCharts {...coreProps} systemStats={systemStats} />
|
||||||
|
|
||||||
{maybeHasSmartData && <LazySmartTable systemId={system.id} />}
|
{maybeHasSmartData && <LazySmartTable systemId={system.id} />}
|
||||||
|
|
||||||
@@ -188,7 +188,6 @@ export default memo(function SystemDetail({ id }: { id: string }) {
|
|||||||
<LoadAverageChart chartData={chartData} grid={grid} dataEmpty={dataEmpty} />
|
<LoadAverageChart chartData={chartData} grid={grid} dataEmpty={dataEmpty} />
|
||||||
<BandwidthChart {...coreProps} systemStats={systemStats} />
|
<BandwidthChart {...coreProps} systemStats={systemStats} />
|
||||||
<TemperatureChart {...coreProps} setPageBottomExtraMargin={setPageBottomExtraMargin} />
|
<TemperatureChart {...coreProps} setPageBottomExtraMargin={setPageBottomExtraMargin} />
|
||||||
<BatteryChart {...coreProps} />
|
|
||||||
<SwapChart chartData={chartData} grid={grid} dataEmpty={dataEmpty} systemStats={systemStats} />
|
<SwapChart chartData={chartData} grid={grid} dataEmpty={dataEmpty} systemStats={systemStats} />
|
||||||
{pageBottomExtraMargin > 0 && <div style={{ marginBottom: pageBottomExtraMargin }}></div>}
|
{pageBottomExtraMargin > 0 && <div style={{ marginBottom: pageBottomExtraMargin }}></div>}
|
||||||
</div>
|
</div>
|
||||||
@@ -198,9 +197,9 @@ export default memo(function SystemDetail({ id }: { id: string }) {
|
|||||||
{mountedTabs.has("disk") && (
|
{mountedTabs.has("disk") && (
|
||||||
<>
|
<>
|
||||||
<div className="grid xl:grid-cols-2 gap-4">
|
<div className="grid xl:grid-cols-2 gap-4">
|
||||||
<RootDiskCharts systemData={systemData} />
|
<DiskCharts {...coreProps} systemStats={systemStats} />
|
||||||
</div>
|
</div>
|
||||||
<ExtraFsCharts systemData={systemData} />
|
<ExtraFsCharts {...coreProps} systemStats={systemStats} />
|
||||||
{maybeHasSmartData && <LazySmartTable systemId={system.id} />}
|
{maybeHasSmartData && <LazySmartTable systemId={system.id} />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,124 +1,39 @@
|
|||||||
import { t } from "@lingui/core/macro"
|
import { t } from "@lingui/core/macro"
|
||||||
import AreaChartDefault from "@/components/charts/area-chart"
|
import AreaChartDefault from "@/components/charts/area-chart"
|
||||||
|
import { $userSettings } from "@/lib/stores"
|
||||||
import { decimalString, formatBytes, toFixedFloat } from "@/lib/utils"
|
import { decimalString, formatBytes, toFixedFloat } from "@/lib/utils"
|
||||||
import type { SystemStatsRecord } from "@/types"
|
import type { ChartData, SystemStatsRecord } from "@/types"
|
||||||
import { ChartCard, SelectAvgMax } from "../chart-card"
|
import { ChartCard, SelectAvgMax } from "../chart-card"
|
||||||
import { Unit } from "@/lib/enums"
|
import { Unit } from "@/lib/enums"
|
||||||
import { pinnedAxisDomain } from "@/components/ui/chart"
|
|
||||||
import DiskIoSheet from "../disk-io-sheet"
|
|
||||||
import type { SystemData } from "../use-system-data"
|
|
||||||
import { useStore } from "@nanostores/react"
|
|
||||||
import { $userSettings } from "@/lib/stores"
|
|
||||||
|
|
||||||
// Helpers for indexed dios/diosm access
|
export function DiskCharts({
|
||||||
const dios =
|
chartData,
|
||||||
(i: number) =>
|
grid,
|
||||||
({ stats }: SystemStatsRecord) =>
|
dataEmpty,
|
||||||
stats?.dios?.[i] ?? 0
|
showMax,
|
||||||
const diosMax =
|
isLongerChart,
|
||||||
(i: number) =>
|
maxValues,
|
||||||
({ stats }: SystemStatsRecord) =>
|
}: {
|
||||||
stats?.diosm?.[i] ?? 0
|
chartData: ChartData
|
||||||
const extraDios =
|
grid: boolean
|
||||||
(name: string, i: number) =>
|
dataEmpty: boolean
|
||||||
({ stats }: SystemStatsRecord) =>
|
showMax: boolean
|
||||||
stats?.efs?.[name]?.dios?.[i] ?? 0
|
isLongerChart: boolean
|
||||||
const extraDiosMax =
|
maxValues: boolean
|
||||||
(name: string, i: number) =>
|
systemStats: SystemStatsRecord[]
|
||||||
({ stats }: SystemStatsRecord) =>
|
}) {
|
||||||
stats?.efs?.[name]?.diosm?.[i] ?? 0
|
const maxValSelect = isLongerChart ? <SelectAvgMax max={maxValues} /> : null
|
||||||
|
const userSettings = $userSettings.get()
|
||||||
export const diskDataFns = {
|
|
||||||
// usage
|
|
||||||
usage: ({ stats }: SystemStatsRecord) => stats?.du ?? 0,
|
|
||||||
extraUsage:
|
|
||||||
(name: string) =>
|
|
||||||
({ stats }: SystemStatsRecord) =>
|
|
||||||
stats?.efs?.[name]?.du ?? 0,
|
|
||||||
// throughput
|
|
||||||
read: ({ stats }: SystemStatsRecord) => stats?.dio?.[0] ?? (stats?.dr ?? 0) * 1024 * 1024,
|
|
||||||
readMax: ({ stats }: SystemStatsRecord) => stats?.diom?.[0] ?? (stats?.drm ?? 0) * 1024 * 1024,
|
|
||||||
write: ({ stats }: SystemStatsRecord) => stats?.dio?.[1] ?? (stats?.dw ?? 0) * 1024 * 1024,
|
|
||||||
writeMax: ({ stats }: SystemStatsRecord) => stats?.diom?.[1] ?? (stats?.dwm ?? 0) * 1024 * 1024,
|
|
||||||
// extra fs throughput
|
|
||||||
extraRead:
|
|
||||||
(name: string) =>
|
|
||||||
({ stats }: SystemStatsRecord) =>
|
|
||||||
stats?.efs?.[name]?.rb ?? (stats?.efs?.[name]?.r ?? 0) * 1024 * 1024,
|
|
||||||
extraReadMax:
|
|
||||||
(name: string) =>
|
|
||||||
({ stats }: SystemStatsRecord) =>
|
|
||||||
stats?.efs?.[name]?.rbm ?? (stats?.efs?.[name]?.rm ?? 0) * 1024 * 1024,
|
|
||||||
extraWrite:
|
|
||||||
(name: string) =>
|
|
||||||
({ stats }: SystemStatsRecord) =>
|
|
||||||
stats?.efs?.[name]?.wb ?? (stats?.efs?.[name]?.w ?? 0) * 1024 * 1024,
|
|
||||||
extraWriteMax:
|
|
||||||
(name: string) =>
|
|
||||||
({ stats }: SystemStatsRecord) =>
|
|
||||||
stats?.efs?.[name]?.wbm ?? (stats?.efs?.[name]?.wm ?? 0) * 1024 * 1024,
|
|
||||||
// read/write time
|
|
||||||
readTime: dios(0),
|
|
||||||
readTimeMax: diosMax(0),
|
|
||||||
extraReadTime: (name: string) => extraDios(name, 0),
|
|
||||||
extraReadTimeMax: (name: string) => extraDiosMax(name, 0),
|
|
||||||
writeTime: dios(1),
|
|
||||||
writeTimeMax: diosMax(1),
|
|
||||||
extraWriteTime: (name: string) => extraDios(name, 1),
|
|
||||||
extraWriteTimeMax: (name: string) => extraDiosMax(name, 1),
|
|
||||||
// utilization (IoTime-based, 0-100%)
|
|
||||||
util: dios(2),
|
|
||||||
utilMax: diosMax(2),
|
|
||||||
extraUtil: (name: string) => extraDios(name, 2),
|
|
||||||
extraUtilMax: (name: string) => extraDiosMax(name, 2),
|
|
||||||
// r_await / w_await: average service time per read/write operation (ms)
|
|
||||||
rAwait: dios(3),
|
|
||||||
rAwaitMax: diosMax(3),
|
|
||||||
extraRAwait: (name: string) => extraDios(name, 3),
|
|
||||||
extraRAwaitMax: (name: string) => extraDiosMax(name, 3),
|
|
||||||
wAwait: dios(4),
|
|
||||||
wAwaitMax: diosMax(4),
|
|
||||||
extraWAwait: (name: string) => extraDios(name, 4),
|
|
||||||
extraWAwaitMax: (name: string) => extraDiosMax(name, 4),
|
|
||||||
// average queue depth: stored as queue_depth * 100 in Go, divided here
|
|
||||||
weightedIO: ({ stats }: SystemStatsRecord) => (stats?.dios?.[5] ?? 0) / 100,
|
|
||||||
weightedIOMax: ({ stats }: SystemStatsRecord) => (stats?.diosm?.[5] ?? 0) / 100,
|
|
||||||
extraWeightedIO:
|
|
||||||
(name: string) =>
|
|
||||||
({ stats }: SystemStatsRecord) =>
|
|
||||||
(stats?.efs?.[name]?.dios?.[5] ?? 0) / 100,
|
|
||||||
extraWeightedIOMax:
|
|
||||||
(name: string) =>
|
|
||||||
({ stats }: SystemStatsRecord) =>
|
|
||||||
(stats?.efs?.[name]?.diosm?.[5] ?? 0) / 100,
|
|
||||||
}
|
|
||||||
|
|
||||||
export function RootDiskCharts({ systemData }: { systemData: SystemData }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<DiskUsageChart systemData={systemData} />
|
|
||||||
<DiskIOChart systemData={systemData} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DiskUsageChart({ systemData, extraFsName }: { systemData: SystemData; extraFsName?: string }) {
|
|
||||||
const { chartData, grid, dataEmpty } = systemData
|
|
||||||
|
|
||||||
let diskSize = chartData.systemStats?.at(-1)?.stats.d ?? NaN
|
let diskSize = chartData.systemStats?.at(-1)?.stats.d ?? NaN
|
||||||
if (extraFsName) {
|
|
||||||
diskSize = chartData.systemStats?.at(-1)?.stats.efs?.[extraFsName]?.d ?? NaN
|
|
||||||
}
|
|
||||||
// round to nearest GB
|
// round to nearest GB
|
||||||
if (diskSize >= 100) {
|
if (diskSize >= 100) {
|
||||||
diskSize = Math.round(diskSize)
|
diskSize = Math.round(diskSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
const title = extraFsName ? `${extraFsName} ${t`Usage`}` : t`Disk Usage`
|
|
||||||
const description = extraFsName ? t`Disk usage of ${extraFsName}` : t`Usage of root partition`
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChartCard empty={dataEmpty} grid={grid} title={title} description={description}>
|
<>
|
||||||
|
<ChartCard empty={dataEmpty} grid={grid} title={t`Disk Usage`} description={t`Usage of root partition`}>
|
||||||
<AreaChartDefault
|
<AreaChartDefault
|
||||||
chartData={chartData}
|
chartData={chartData}
|
||||||
domain={[0, diskSize]}
|
domain={[0, diskSize]}
|
||||||
@@ -135,62 +50,42 @@ export function DiskUsageChart({ systemData, extraFsName }: { systemData: System
|
|||||||
label: t`Disk Usage`,
|
label: t`Disk Usage`,
|
||||||
color: 4,
|
color: 4,
|
||||||
opacity: 0.4,
|
opacity: 0.4,
|
||||||
dataKey: extraFsName ? diskDataFns.extraUsage(extraFsName) : diskDataFns.usage,
|
dataKey: ({ stats }) => stats?.du,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
></AreaChartDefault>
|
></AreaChartDefault>
|
||||||
</ChartCard>
|
</ChartCard>
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DiskIOChart({ systemData, extraFsName }: { systemData: SystemData; extraFsName?: string }) {
|
<ChartCard
|
||||||
const { chartData, grid, dataEmpty, showMax, isLongerChart, maxValues } = systemData
|
empty={dataEmpty}
|
||||||
const maxValSelect = isLongerChart ? <SelectAvgMax max={maxValues} /> : null
|
grid={grid}
|
||||||
const userSettings = useStore($userSettings)
|
title={t`Disk I/O`}
|
||||||
|
description={t`Throughput of root filesystem`}
|
||||||
if (!chartData.systemStats?.length) {
|
cornerEl={maxValSelect}
|
||||||
return null
|
>
|
||||||
}
|
|
||||||
|
|
||||||
const title = extraFsName ? `${extraFsName} I/O` : t`Disk I/O`
|
|
||||||
const description = extraFsName ? t`Throughput of ${extraFsName}` : t`Throughput of root filesystem`
|
|
||||||
|
|
||||||
const hasMoreIOMetrics = chartData.systemStats?.some((record) => record.stats?.dios?.at(0))
|
|
||||||
|
|
||||||
let CornerEl = maxValSelect
|
|
||||||
if (hasMoreIOMetrics) {
|
|
||||||
CornerEl = (
|
|
||||||
<div className="flex gap-2">
|
|
||||||
{maxValSelect}
|
|
||||||
<DiskIoSheet systemData={systemData} extraFsName={extraFsName} title={title} description={description} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let readFn = showMax ? diskDataFns.readMax : diskDataFns.read
|
|
||||||
let writeFn = showMax ? diskDataFns.writeMax : diskDataFns.write
|
|
||||||
if (extraFsName) {
|
|
||||||
readFn = showMax ? diskDataFns.extraReadMax(extraFsName) : diskDataFns.extraRead(extraFsName)
|
|
||||||
writeFn = showMax ? diskDataFns.extraWriteMax(extraFsName) : diskDataFns.extraWrite(extraFsName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ChartCard empty={dataEmpty} grid={grid} title={title} description={description} cornerEl={CornerEl}>
|
|
||||||
<AreaChartDefault
|
<AreaChartDefault
|
||||||
chartData={chartData}
|
chartData={chartData}
|
||||||
maxToggled={showMax}
|
maxToggled={showMax}
|
||||||
// domain={pinnedAxisDomain(true)}
|
|
||||||
showTotal={true}
|
|
||||||
dataPoints={[
|
dataPoints={[
|
||||||
{
|
{
|
||||||
label: t({ message: "Write", comment: "Disk write" }),
|
label: t({ message: "Write", comment: "Disk write" }),
|
||||||
dataKey: writeFn,
|
dataKey: ({ stats }: SystemStatsRecord) => {
|
||||||
|
if (showMax) {
|
||||||
|
return stats?.dio?.[1] ?? (stats?.dwm ?? 0) * 1024 * 1024
|
||||||
|
}
|
||||||
|
return stats?.dio?.[1] ?? (stats?.dw ?? 0) * 1024 * 1024
|
||||||
|
},
|
||||||
color: 3,
|
color: 3,
|
||||||
opacity: 0.3,
|
opacity: 0.3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t({ message: "Read", comment: "Disk read" }),
|
label: t({ message: "Read", comment: "Disk read" }),
|
||||||
dataKey: readFn,
|
dataKey: ({ stats }: SystemStatsRecord) => {
|
||||||
|
if (showMax) {
|
||||||
|
return stats?.diom?.[0] ?? (stats?.drm ?? 0) * 1024 * 1024
|
||||||
|
}
|
||||||
|
return stats?.dio?.[0] ?? (stats?.dr ?? 0) * 1024 * 1024
|
||||||
|
},
|
||||||
color: 1,
|
color: 1,
|
||||||
opacity: 0.3,
|
opacity: 0.3,
|
||||||
},
|
},
|
||||||
@@ -203,81 +98,9 @@ export function DiskIOChart({ systemData, extraFsName }: { systemData: SystemDat
|
|||||||
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>
|
||||||
)
|
</>
|
||||||
}
|
|
||||||
|
|
||||||
export function DiskUtilizationChart({ systemData, extraFsName }: { systemData: SystemData; extraFsName?: string }) {
|
|
||||||
const { chartData, grid, dataEmpty, showMax, isLongerChart, maxValues } = systemData
|
|
||||||
const maxValSelect = isLongerChart ? <SelectAvgMax max={maxValues} /> : null
|
|
||||||
|
|
||||||
if (!chartData.systemStats?.length) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
let utilFn = showMax ? diskDataFns.utilMax : diskDataFns.util
|
|
||||||
if (extraFsName) {
|
|
||||||
utilFn = showMax ? diskDataFns.extraUtilMax(extraFsName) : diskDataFns.extraUtil(extraFsName)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<ChartCard
|
|
||||||
cornerEl={maxValSelect}
|
|
||||||
empty={dataEmpty}
|
|
||||||
grid={grid}
|
|
||||||
title={t({
|
|
||||||
message: `I/O Utilization`,
|
|
||||||
context: "Percent of time the disk is busy with I/O",
|
|
||||||
})}
|
|
||||||
description={t`Percent of time the disk is busy with I/O`}
|
|
||||||
// legend={true}
|
|
||||||
className="min-h-auto"
|
|
||||||
>
|
|
||||||
<AreaChartDefault
|
|
||||||
chartData={chartData}
|
|
||||||
domain={pinnedAxisDomain()}
|
|
||||||
tickFormatter={(val) => `${toFixedFloat(val, 2)}%`}
|
|
||||||
contentFormatter={({ value }) => `${decimalString(value)}%`}
|
|
||||||
maxToggled={showMax}
|
|
||||||
chartProps={{ syncId: "io" }}
|
|
||||||
dataPoints={[
|
|
||||||
{
|
|
||||||
label: t({ message: "Utilization", context: "Disk I/O utilization" }),
|
|
||||||
dataKey: utilFn,
|
|
||||||
color: 1,
|
|
||||||
opacity: 0.4,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</ChartCard>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ExtraFsCharts({ systemData }: { systemData: SystemData }) {
|
|
||||||
const { systemStats } = systemData.chartData
|
|
||||||
|
|
||||||
const extraFs = systemStats?.at(-1)?.stats.efs
|
|
||||||
|
|
||||||
if (!extraFs || Object.keys(extraFs).length === 0) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="grid xl:grid-cols-2 gap-4">
|
|
||||||
{Object.keys(extraFs).map((extraFsName) => {
|
|
||||||
let diskSize = systemStats.at(-1)?.stats.efs?.[extraFsName].d ?? NaN
|
|
||||||
// round to nearest GB
|
|
||||||
if (diskSize >= 100) {
|
|
||||||
diskSize = Math.round(diskSize)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div key={extraFsName} className="contents">
|
|
||||||
<DiskUsageChart systemData={systemData} extraFsName={extraFsName} />
|
|
||||||
|
|
||||||
<DiskIOChart systemData={systemData} extraFsName={extraFsName} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import { t } from "@lingui/core/macro"
|
||||||
|
import AreaChartDefault from "@/components/charts/area-chart"
|
||||||
|
import { $userSettings } from "@/lib/stores"
|
||||||
|
import { decimalString, formatBytes, toFixedFloat } from "@/lib/utils"
|
||||||
|
import type { ChartData, SystemStatsRecord } from "@/types"
|
||||||
|
import { ChartCard, SelectAvgMax } from "../chart-card"
|
||||||
|
import { Unit } from "@/lib/enums"
|
||||||
|
|
||||||
|
export function ExtraFsCharts({
|
||||||
|
chartData,
|
||||||
|
grid,
|
||||||
|
dataEmpty,
|
||||||
|
showMax,
|
||||||
|
isLongerChart,
|
||||||
|
maxValues,
|
||||||
|
systemStats,
|
||||||
|
}: {
|
||||||
|
chartData: ChartData
|
||||||
|
grid: boolean
|
||||||
|
dataEmpty: boolean
|
||||||
|
showMax: boolean
|
||||||
|
isLongerChart: boolean
|
||||||
|
maxValues: boolean
|
||||||
|
systemStats: SystemStatsRecord[]
|
||||||
|
}) {
|
||||||
|
const maxValSelect = isLongerChart ? <SelectAvgMax max={maxValues} /> : null
|
||||||
|
const userSettings = $userSettings.get()
|
||||||
|
const extraFs = systemStats.at(-1)?.stats.efs
|
||||||
|
if (!extraFs || Object.keys(extraFs).length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid xl:grid-cols-2 gap-4">
|
||||||
|
{Object.keys(extraFs).map((extraFsName) => {
|
||||||
|
let diskSize = systemStats.at(-1)?.stats.efs?.[extraFsName].d ?? NaN
|
||||||
|
// round to nearest GB
|
||||||
|
if (diskSize >= 100) {
|
||||||
|
diskSize = Math.round(diskSize)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div key={extraFsName} className="contents">
|
||||||
|
<ChartCard
|
||||||
|
empty={dataEmpty}
|
||||||
|
grid={grid}
|
||||||
|
title={`${extraFsName} ${t`Usage`}`}
|
||||||
|
description={t`Disk usage of ${extraFsName}`}
|
||||||
|
>
|
||||||
|
<AreaChartDefault
|
||||||
|
chartData={chartData}
|
||||||
|
domain={[0, diskSize]}
|
||||||
|
tickFormatter={(val) => {
|
||||||
|
const { value, unit } = formatBytes(val * 1024, false, Unit.Bytes, true)
|
||||||
|
return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}`
|
||||||
|
}}
|
||||||
|
contentFormatter={({ value }) => {
|
||||||
|
const { value: convertedValue, unit } = formatBytes(value * 1024, false, Unit.Bytes, true)
|
||||||
|
return `${decimalString(convertedValue)} ${unit}`
|
||||||
|
}}
|
||||||
|
dataPoints={[
|
||||||
|
{
|
||||||
|
label: t`Disk Usage`,
|
||||||
|
color: 4,
|
||||||
|
opacity: 0.4,
|
||||||
|
dataKey: ({ stats }) => stats?.efs?.[extraFsName]?.du,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
></AreaChartDefault>
|
||||||
|
</ChartCard>
|
||||||
|
<ChartCard
|
||||||
|
empty={dataEmpty}
|
||||||
|
grid={grid}
|
||||||
|
title={`${extraFsName} I/O`}
|
||||||
|
description={t`Throughput of ${extraFsName}`}
|
||||||
|
cornerEl={maxValSelect}
|
||||||
|
>
|
||||||
|
<AreaChartDefault
|
||||||
|
chartData={chartData}
|
||||||
|
showTotal={true}
|
||||||
|
dataPoints={[
|
||||||
|
{
|
||||||
|
label: t`Write`,
|
||||||
|
dataKey: ({ stats }) => {
|
||||||
|
if (showMax) {
|
||||||
|
return stats?.efs?.[extraFsName]?.wbm || (stats?.efs?.[extraFsName]?.wm ?? 0) * 1024 * 1024
|
||||||
|
}
|
||||||
|
return stats?.efs?.[extraFsName]?.wb || (stats?.efs?.[extraFsName]?.w ?? 0) * 1024 * 1024
|
||||||
|
},
|
||||||
|
color: 3,
|
||||||
|
opacity: 0.3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t`Read`,
|
||||||
|
dataKey: ({ stats }) => {
|
||||||
|
if (showMax) {
|
||||||
|
return stats?.efs?.[extraFsName]?.rbm ?? (stats?.efs?.[extraFsName]?.rm ?? 0) * 1024 * 1024
|
||||||
|
}
|
||||||
|
return stats?.efs?.[extraFsName]?.rb ?? (stats?.efs?.[extraFsName]?.r ?? 0) * 1024 * 1024
|
||||||
|
},
|
||||||
|
color: 1,
|
||||||
|
opacity: 0.3,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
maxToggled={showMax}
|
||||||
|
tickFormatter={(val) => {
|
||||||
|
const { value, unit } = formatBytes(val, true, userSettings.unitDisk, false)
|
||||||
|
return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}`
|
||||||
|
}}
|
||||||
|
contentFormatter={({ value }) => {
|
||||||
|
const { value: convertedValue, unit } = formatBytes(value, true, userSettings.unitDisk, false)
|
||||||
|
return `${decimalString(convertedValue, convertedValue >= 100 ? 1 : 2)} ${unit}`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ChartCard>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,265 +0,0 @@
|
|||||||
import { t } from "@lingui/core/macro"
|
|
||||||
import { useStore } from "@nanostores/react"
|
|
||||||
import { MoreHorizontalIcon } from "lucide-react"
|
|
||||||
import { memo, useRef, useState } from "react"
|
|
||||||
import AreaChartDefault from "@/components/charts/area-chart"
|
|
||||||
import ChartTimeSelect from "@/components/charts/chart-time-select"
|
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"
|
|
||||||
import { DialogTitle } from "@/components/ui/dialog"
|
|
||||||
import { $userSettings } from "@/lib/stores"
|
|
||||||
import { decimalString, formatBytes, toFixedFloat } from "@/lib/utils"
|
|
||||||
import { ChartCard, SelectAvgMax } from "@/components/routes/system/chart-card"
|
|
||||||
import type { SystemData } from "@/components/routes/system/use-system-data"
|
|
||||||
import { diskDataFns, DiskUtilizationChart } from "./charts/disk-charts"
|
|
||||||
import { pinnedAxisDomain } from "@/components/ui/chart"
|
|
||||||
|
|
||||||
export default memo(function DiskIOSheet({
|
|
||||||
systemData,
|
|
||||||
extraFsName,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
}: {
|
|
||||||
systemData: SystemData
|
|
||||||
extraFsName?: string
|
|
||||||
title: string
|
|
||||||
description: string
|
|
||||||
}) {
|
|
||||||
const { chartData, grid, dataEmpty, showMax, maxValues, isLongerChart } = systemData
|
|
||||||
const userSettings = useStore($userSettings)
|
|
||||||
|
|
||||||
const [sheetOpen, setSheetOpen] = useState(false)
|
|
||||||
|
|
||||||
const hasOpened = useRef(false)
|
|
||||||
|
|
||||||
if (sheetOpen && !hasOpened.current) {
|
|
||||||
hasOpened.current = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// throughput functions, with extra fs variants if needed
|
|
||||||
let readFn = showMax ? diskDataFns.readMax : diskDataFns.read
|
|
||||||
let writeFn = showMax ? diskDataFns.writeMax : diskDataFns.write
|
|
||||||
if (extraFsName) {
|
|
||||||
readFn = showMax ? diskDataFns.extraReadMax(extraFsName) : diskDataFns.extraRead(extraFsName)
|
|
||||||
writeFn = showMax ? diskDataFns.extraWriteMax(extraFsName) : diskDataFns.extraWrite(extraFsName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// read and write time functions, with extra fs variants if needed
|
|
||||||
let readTimeFn = showMax ? diskDataFns.readTimeMax : diskDataFns.readTime
|
|
||||||
let writeTimeFn = showMax ? diskDataFns.writeTimeMax : diskDataFns.writeTime
|
|
||||||
if (extraFsName) {
|
|
||||||
readTimeFn = showMax ? diskDataFns.extraReadTimeMax(extraFsName) : diskDataFns.extraReadTime(extraFsName)
|
|
||||||
writeTimeFn = showMax ? diskDataFns.extraWriteTimeMax(extraFsName) : diskDataFns.extraWriteTime(extraFsName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// I/O await functions, with extra fs variants if needed
|
|
||||||
let rAwaitFn = showMax ? diskDataFns.rAwaitMax : diskDataFns.rAwait
|
|
||||||
let wAwaitFn = showMax ? diskDataFns.wAwaitMax : diskDataFns.wAwait
|
|
||||||
if (extraFsName) {
|
|
||||||
rAwaitFn = showMax ? diskDataFns.extraRAwaitMax(extraFsName) : diskDataFns.extraRAwait(extraFsName)
|
|
||||||
wAwaitFn = showMax ? diskDataFns.extraWAwaitMax(extraFsName) : diskDataFns.extraWAwait(extraFsName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// weighted I/O function, with extra fs variant if needed
|
|
||||||
let weightedIOFn = showMax ? diskDataFns.weightedIOMax : diskDataFns.weightedIO
|
|
||||||
if (extraFsName) {
|
|
||||||
weightedIOFn = showMax ? diskDataFns.extraWeightedIOMax(extraFsName) : diskDataFns.extraWeightedIO(extraFsName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for availability of I/O metrics
|
|
||||||
let hasUtilization = false
|
|
||||||
let hasAwait = false
|
|
||||||
let hasWeightedIO = false
|
|
||||||
for (const record of chartData.systemStats ?? []) {
|
|
||||||
const dios = record.stats?.dios
|
|
||||||
if ((dios?.at(2) ?? 0) > 0) hasUtilization = true
|
|
||||||
if ((dios?.at(3) ?? 0) > 0) hasAwait = true
|
|
||||||
if ((dios?.at(5) ?? 0) > 0) hasWeightedIO = true
|
|
||||||
if (hasUtilization && hasAwait && hasWeightedIO) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxValSelect = isLongerChart ? <SelectAvgMax max={maxValues} /> : null
|
|
||||||
|
|
||||||
const chartProps = { syncId: "io" }
|
|
||||||
|
|
||||||
const queueDepthTranslation = t({ message: "Queue Depth", context: "Disk I/O average queue depth" })
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Sheet open={sheetOpen} onOpenChange={setSheetOpen}>
|
|
||||||
<DialogTitle className="sr-only">{title}</DialogTitle>
|
|
||||||
<SheetTrigger asChild>
|
|
||||||
<Button
|
|
||||||
title={t`View more`}
|
|
||||||
variant="outline"
|
|
||||||
size="icon"
|
|
||||||
className="shrink-0 max-sm:absolute max-sm:top-0 max-sm:end-0"
|
|
||||||
>
|
|
||||||
<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} />
|
|
||||||
|
|
||||||
<ChartCard
|
|
||||||
className="min-h-auto"
|
|
||||||
empty={dataEmpty}
|
|
||||||
grid={grid}
|
|
||||||
title={title}
|
|
||||||
description={description}
|
|
||||||
cornerEl={maxValSelect}
|
|
||||||
// legend={true}
|
|
||||||
>
|
|
||||||
<AreaChartDefault
|
|
||||||
chartData={chartData}
|
|
||||||
maxToggled={showMax}
|
|
||||||
chartProps={chartProps}
|
|
||||||
showTotal={true}
|
|
||||||
domain={pinnedAxisDomain()}
|
|
||||||
itemSorter={(a, b) => a.order - b.order}
|
|
||||||
reverseStackOrder={true}
|
|
||||||
dataPoints={[
|
|
||||||
{
|
|
||||||
label: t`Write`,
|
|
||||||
dataKey: writeFn,
|
|
||||||
color: 3,
|
|
||||||
opacity: 0.4,
|
|
||||||
stackId: 0,
|
|
||||||
order: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t`Read`,
|
|
||||||
dataKey: readFn,
|
|
||||||
color: 1,
|
|
||||||
opacity: 0.4,
|
|
||||||
stackId: 0,
|
|
||||||
order: 1,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
tickFormatter={(val) => {
|
|
||||||
const { value, unit } = formatBytes(val, true, userSettings.unitDisk, false)
|
|
||||||
return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}`
|
|
||||||
}}
|
|
||||||
contentFormatter={({ value }) => {
|
|
||||||
const { value: convertedValue, unit } = formatBytes(value, true, userSettings.unitDisk, false)
|
|
||||||
return `${decimalString(convertedValue, convertedValue >= 100 ? 1 : 2)} ${unit}`
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</ChartCard>
|
|
||||||
|
|
||||||
{hasUtilization && <DiskUtilizationChart systemData={systemData} extraFsName={extraFsName} />}
|
|
||||||
|
|
||||||
<ChartCard
|
|
||||||
empty={dataEmpty}
|
|
||||||
grid={grid}
|
|
||||||
title={t({ message: "I/O Time", context: "Disk I/O total time spent on read/write" })}
|
|
||||||
description={t({
|
|
||||||
message: "Total time spent on read/write (can exceed 100%)",
|
|
||||||
context: "Disk I/O",
|
|
||||||
})}
|
|
||||||
className="min-h-auto"
|
|
||||||
cornerEl={maxValSelect}
|
|
||||||
>
|
|
||||||
<AreaChartDefault
|
|
||||||
chartData={chartData}
|
|
||||||
domain={pinnedAxisDomain()}
|
|
||||||
tickFormatter={(val) => `${toFixedFloat(val, 2)}%`}
|
|
||||||
contentFormatter={({ value }) => `${decimalString(value)}%`}
|
|
||||||
maxToggled={showMax}
|
|
||||||
chartProps={chartProps}
|
|
||||||
showTotal={true}
|
|
||||||
itemSorter={(a, b) => a.order - b.order}
|
|
||||||
reverseStackOrder={true}
|
|
||||||
dataPoints={[
|
|
||||||
{
|
|
||||||
label: t`Write`,
|
|
||||||
dataKey: writeTimeFn,
|
|
||||||
color: 3,
|
|
||||||
opacity: 0.4,
|
|
||||||
stackId: 0,
|
|
||||||
order: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t`Read`,
|
|
||||||
dataKey: readTimeFn,
|
|
||||||
color: 1,
|
|
||||||
opacity: 0.4,
|
|
||||||
stackId: 0,
|
|
||||||
order: 1,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</ChartCard>
|
|
||||||
|
|
||||||
{hasWeightedIO && (
|
|
||||||
<ChartCard
|
|
||||||
empty={dataEmpty}
|
|
||||||
grid={grid}
|
|
||||||
title={queueDepthTranslation}
|
|
||||||
description={t`Average number of I/O operations waiting to be serviced`}
|
|
||||||
className="min-h-auto"
|
|
||||||
cornerEl={maxValSelect}
|
|
||||||
>
|
|
||||||
<AreaChartDefault
|
|
||||||
chartData={chartData}
|
|
||||||
domain={pinnedAxisDomain()}
|
|
||||||
tickFormatter={(val) => `${toFixedFloat(val, 2)}`}
|
|
||||||
contentFormatter={({ value }) => decimalString(value, value < 10 ? 3 : 2)}
|
|
||||||
maxToggled={showMax}
|
|
||||||
chartProps={chartProps}
|
|
||||||
dataPoints={[
|
|
||||||
{
|
|
||||||
label: queueDepthTranslation,
|
|
||||||
dataKey: weightedIOFn,
|
|
||||||
color: 1,
|
|
||||||
opacity: 0.4,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</ChartCard>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{hasAwait && (
|
|
||||||
<ChartCard
|
|
||||||
empty={dataEmpty}
|
|
||||||
grid={grid}
|
|
||||||
title={t({ message: "I/O Await", context: "Disk I/O average operation time (iostat await)" })}
|
|
||||||
description={t({
|
|
||||||
message: "Average queue to completion time per operation",
|
|
||||||
context: "Disk I/O average operation time (iostat await)",
|
|
||||||
})}
|
|
||||||
className="min-h-auto"
|
|
||||||
cornerEl={maxValSelect}
|
|
||||||
// legend={true}
|
|
||||||
>
|
|
||||||
<AreaChartDefault
|
|
||||||
chartData={chartData}
|
|
||||||
domain={pinnedAxisDomain()}
|
|
||||||
tickFormatter={(val) => `${toFixedFloat(val, 2)} ms`}
|
|
||||||
contentFormatter={({ value }) => `${decimalString(value)} ms`}
|
|
||||||
maxToggled={showMax}
|
|
||||||
chartProps={chartProps}
|
|
||||||
dataPoints={[
|
|
||||||
{
|
|
||||||
label: t`Write`,
|
|
||||||
dataKey: wAwaitFn,
|
|
||||||
color: 3,
|
|
||||||
opacity: 0.3,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t`Read`,
|
|
||||||
dataKey: rAwaitFn,
|
|
||||||
color: 1,
|
|
||||||
opacity: 0.3,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</ChartCard>
|
|
||||||
)}
|
|
||||||
</SheetContent>
|
|
||||||
)}
|
|
||||||
</Sheet>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
@@ -36,7 +36,7 @@ import { Input } from "@/components/ui/input"
|
|||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { isReadOnlyUser, pb } from "@/lib/api"
|
import { pb } from "@/lib/api"
|
||||||
import type { SmartDeviceRecord, SmartAttribute } from "@/types"
|
import type { SmartDeviceRecord, SmartAttribute } from "@/types"
|
||||||
import {
|
import {
|
||||||
formatBytes,
|
formatBytes,
|
||||||
@@ -492,7 +492,7 @@ export default function DisksTable({ systemId }: { systemId?: string }) {
|
|||||||
const tableColumns = useMemo(() => {
|
const tableColumns = useMemo(() => {
|
||||||
const columns = createColumns(longestName, longestModel, longestDevice)
|
const columns = createColumns(longestName, longestModel, longestDevice)
|
||||||
const baseColumns = systemId ? columns.filter((col) => col.id !== "system") : columns
|
const baseColumns = systemId ? columns.filter((col) => col.id !== "system") : columns
|
||||||
return isReadOnlyUser() ? baseColumns : [...baseColumns, actionColumn]
|
return [...baseColumns, actionColumn]
|
||||||
}, [systemId, actionColumn, longestName, longestModel, longestDevice])
|
}, [systemId, actionColumn, longestName, longestModel, longestDevice])
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ import type {
|
|||||||
import { $router, navigate } from "../../router"
|
import { $router, navigate } from "../../router"
|
||||||
import { appendData, cache, getStats, getTimeData, makeContainerData, makeContainerPoint } from "./chart-data"
|
import { appendData, cache, getStats, getTimeData, makeContainerData, makeContainerPoint } from "./chart-data"
|
||||||
|
|
||||||
export type SystemData = ReturnType<typeof useSystemData>
|
|
||||||
|
|
||||||
export function useSystemData(id: string) {
|
export function useSystemData(id: string) {
|
||||||
const direction = useStore($direction)
|
const direction = useStore($direction)
|
||||||
const systems = useStore($systems)
|
const systems = useStore($systems)
|
||||||
@@ -192,7 +190,7 @@ export function useSystemData(id: string) {
|
|||||||
|
|
||||||
// Skip the fetch if the latest cached point is recent enough that no new point is expected yet
|
// Skip the fetch if the latest cached point is recent enough that no new point is expected yet
|
||||||
const lastCreated = cachedSystemStats.at(-1)?.created as number | undefined
|
const lastCreated = cachedSystemStats.at(-1)?.created as number | undefined
|
||||||
if (lastCreated && Date.now() - lastCreated < expectedInterval * 0.9) {
|
if (lastCreated && Date.now() - lastCreated < expectedInterval) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { listenKeys } from "nanostores"
|
|||||||
import { memo, type ReactNode, useEffect, useMemo, useRef, useState } from "react"
|
import { memo, type ReactNode, useEffect, useMemo, useRef, useState } from "react"
|
||||||
import { getStatusColor, systemdTableCols } from "@/components/systemd-table/systemd-table-columns"
|
import { getStatusColor, systemdTableCols } from "@/components/systemd-table/systemd-table-columns"
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
||||||
import { Card, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet"
|
import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet"
|
||||||
import { TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
import { TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||||
@@ -161,13 +161,13 @@ export default function SystemdTable({ systemId }: { systemId?: string }) {
|
|||||||
<CardTitle className="mb-2">
|
<CardTitle className="mb-2">
|
||||||
<Trans>Systemd Services</Trans>
|
<Trans>Systemd Services</Trans>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<div className="text-sm text-muted-foreground flex items-center flex-wrap">
|
<CardDescription className="flex items-center">
|
||||||
<Trans>Total: {data.length}</Trans>
|
<Trans>Total: {data.length}</Trans>
|
||||||
<Separator orientation="vertical" className="h-4 mx-2 bg-primary/40" />
|
<Separator orientation="vertical" className="h-4 mx-2 bg-primary/40" />
|
||||||
<Trans>Failed: {statusTotals[ServiceStatus.Failed]}</Trans>
|
<Trans>Failed: {statusTotals[ServiceStatus.Failed]}</Trans>
|
||||||
<Separator orientation="vertical" className="h-4 mx-2 bg-primary/40" />
|
<Separator orientation="vertical" className="h-4 mx-2 bg-primary/40" />
|
||||||
<Trans>Updated every 10 minutes.</Trans>
|
<Trans>Updated every 10 minutes.</Trans>
|
||||||
</div>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t`Filter...`}
|
placeholder={t`Filter...`}
|
||||||
|
|||||||
@@ -460,14 +460,14 @@ const SystemCard = memo(
|
|||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CardHeader className="py-1 ps-4 pe-2 bg-muted/30 border-b border-border/60">
|
<CardHeader className="py-1 ps-5 pe-3 bg-muted/30 border-b border-border/60">
|
||||||
<div className="flex items-center gap-1 w-full overflow-hidden">
|
<div className="flex items-center gap-2 w-full overflow-hidden">
|
||||||
<h3 className="text-primary/90 min-w-0 flex-1 gap-2.5 font-semibold">
|
<CardTitle className="text-base tracking-normal text-primary/90 flex items-center min-w-0 flex-1 gap-2.5">
|
||||||
<div className="flex items-center gap-2.5 min-w-0 flex-1">
|
<div className="flex items-center gap-2.5 min-w-0 flex-1">
|
||||||
<IndicatorDot system={system} />
|
<IndicatorDot system={system} />
|
||||||
<span className="text-[.95em]/normal tracking-normal text-primary/90 truncate">{system.name}</span>
|
<span className="text-[.95em]/normal tracking-normal text-primary/90 truncate">{system.name}</span>
|
||||||
</div>
|
</div>
|
||||||
</h3>
|
</CardTitle>
|
||||||
{table.getColumn("actions")?.getIsVisible() && (
|
{table.getColumn("actions")?.getIsVisible() && (
|
||||||
<div className="flex gap-1 shrink-0 relative z-10">
|
<div className="flex gap-1 shrink-0 relative z-10">
|
||||||
<AlertButton system={system} />
|
<AlertButton system={system} />
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const AlertDialogContent = React.forwardRef<
|
|||||||
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
|
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
|
||||||
|
|
||||||
const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
<div className={cn("grid gap-2 text-start", className)} {...props} />
|
<div className={cn("grid gap-2 text-center sm:text-start", className)} {...props} />
|
||||||
)
|
)
|
||||||
AlertDialogHeader.displayName = "AlertDialogHeader"
|
AlertDialogHeader.displayName = "AlertDialogHeader"
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,11 @@ CardHeader.displayName = "CardHeader"
|
|||||||
|
|
||||||
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
||||||
({ className, ...props }, ref) => (
|
({ className, ...props }, ref) => (
|
||||||
<h3 ref={ref} className={cn("text-card-title font-semibold leading-none tracking-tight", className)} {...props} />
|
<h3
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-[1.4em] sm:text-2xl font-semibold leading-none tracking-tight", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
CardTitle.displayName = "CardTitle"
|
CardTitle.displayName = "CardTitle"
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ const DialogContent = React.forwardRef<
|
|||||||
DialogContent.displayName = DialogPrimitive.Content.displayName
|
DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||||
|
|
||||||
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
<div className={cn("grid gap-1.5 text-start", className)} {...props} />
|
<div className={cn("grid gap-1.5 text-center sm:text-start", className)} {...props} />
|
||||||
)
|
)
|
||||||
DialogHeader.displayName = "DialogHeader"
|
DialogHeader.displayName = "DialogHeader"
|
||||||
|
|
||||||
|
|||||||
@@ -177,10 +177,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility text-card-title {
|
|
||||||
@apply text-[1.4rem] sm:text-2xl;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recharts-tooltip-wrapper {
|
.recharts-tooltip-wrapper {
|
||||||
z-index: 51;
|
z-index: 51;
|
||||||
@apply tabular-nums;
|
@apply tabular-nums;
|
||||||
|
|||||||
@@ -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: 2026-04-05 18:27\n"
|
"PO-Revision-Date: 2026-04-05 20:36\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"
|
||||||
@@ -1931,3 +1931,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 "تم تحديث إعدادات المستخدم الخاصة بك."
|
msgstr "تم تحديث إعدادات المستخدم الخاصة بك."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: bg\n"
|
"Language: bg\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-03-28 09:32\n"
|
"PO-Revision-Date: 2026-04-05 20:36\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Bulgarian\n"
|
"Language-Team: Bulgarian\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@@ -858,7 +858,7 @@ msgstr "Глобален"
|
|||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
msgstr ""
|
msgstr "GPU"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
msgid "GPU Engines"
|
msgid "GPU Engines"
|
||||||
@@ -883,7 +883,7 @@ msgstr "Здраве"
|
|||||||
|
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Heartbeat"
|
msgid "Heartbeat"
|
||||||
msgstr ""
|
msgstr "Heartbeat"
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Heartbeat Monitoring"
|
msgid "Heartbeat Monitoring"
|
||||||
@@ -1931,3 +1931,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 "Настройките за потребителя ти са обновени."
|
msgstr "Настройките за потребителя ти са обновени."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: cs\n"
|
"Language: cs\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-04-05 18:27\n"
|
"PO-Revision-Date: 2026-04-05 20:36\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"
|
||||||
@@ -57,7 +57,7 @@ msgstr "1 hodina"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr ""
|
msgstr "1 min"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 minute"
|
msgid "1 minute"
|
||||||
@@ -74,7 +74,7 @@ msgstr "12 hodin"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "15 min"
|
msgid "15 min"
|
||||||
msgstr ""
|
msgstr "15 min"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "24 hours"
|
msgid "24 hours"
|
||||||
@@ -87,7 +87,7 @@ msgstr "30 dní"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "5 min"
|
msgid "5 min"
|
||||||
msgstr ""
|
msgstr "5 min"
|
||||||
|
|
||||||
#. Table column
|
#. Table column
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
@@ -149,7 +149,7 @@ msgstr "Po nastavení proměnných prostředí restartujte hub Beszel, aby se zm
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Agent"
|
msgid "Agent"
|
||||||
msgstr ""
|
msgstr "Agent"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
@@ -587,7 +587,7 @@ msgstr "Popis"
|
|||||||
|
|
||||||
#: src/components/containers-table/containers-table.tsx
|
#: src/components/containers-table/containers-table.tsx
|
||||||
msgid "Detail"
|
msgid "Detail"
|
||||||
msgstr ""
|
msgstr "Detail"
|
||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Device"
|
msgid "Device"
|
||||||
@@ -1931,3 +1931,4 @@ 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."
|
||||||
|
|
||||||
|
|||||||
@@ -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: 2026-04-05 18:27\n"
|
"PO-Revision-Date: 2026-04-05 20:36\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"
|
||||||
@@ -149,7 +149,7 @@ msgstr "Efter indstilling af miljøvariablerne skal du genstarte din Beszel-hub
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Agent"
|
msgid "Agent"
|
||||||
msgstr ""
|
msgstr "Agent"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
@@ -289,7 +289,7 @@ msgstr "Binær"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bits (Kbps, Mbps, Gbps)"
|
msgid "Bits (Kbps, Mbps, Gbps)"
|
||||||
msgstr ""
|
msgstr "Bits (Kbps, Mbps, Gbps)"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Boot state"
|
msgid "Boot state"
|
||||||
@@ -298,7 +298,7 @@ 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)"
|
||||||
msgstr ""
|
msgstr "Bytes (KB/s, MB/s, GB/s)"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/memory-charts.tsx
|
#: src/components/routes/system/charts/memory-charts.tsx
|
||||||
msgid "Cache / Buffers"
|
msgid "Cache / Buffers"
|
||||||
@@ -336,7 +336,7 @@ msgstr "Forsigtig - muligt tab af data"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr ""
|
msgstr "Celsius (°C)"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -496,7 +496,7 @@ msgstr "Kerne"
|
|||||||
#: src/components/systemd-table/systemd-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 "CPU"
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -504,7 +504,7 @@ msgstr "CPU-kerner"
|
|||||||
|
|
||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
msgid "CPU Peak"
|
msgid "CPU Peak"
|
||||||
msgstr ""
|
msgstr "CPU Peak"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "CPU time"
|
msgid "CPU time"
|
||||||
@@ -601,11 +601,11 @@ msgstr "Aflader"
|
|||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Disk"
|
msgid "Disk"
|
||||||
msgstr ""
|
msgstr "Disk"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
msgid "Disk I/O"
|
msgid "Disk I/O"
|
||||||
msgstr ""
|
msgstr "Disk I/O"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Disk unit"
|
msgid "Disk unit"
|
||||||
@@ -677,7 +677,7 @@ msgstr "Rediger {foo}"
|
|||||||
#: 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 ""
|
msgstr "Email"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Email notifications"
|
msgid "Email notifications"
|
||||||
@@ -772,7 +772,7 @@ msgstr "Eksporter din nuværende systemkonfiguration."
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr ""
|
msgstr "Fahrenheit (°F)"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
@@ -816,7 +816,7 @@ msgstr "Mislykkedes: {0}"
|
|||||||
#: src/components/systemd-table/systemd-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 "Filter..."
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Fingerprint"
|
msgid "Fingerprint"
|
||||||
@@ -824,7 +824,7 @@ msgstr "Fingeraftryk"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Firmware"
|
msgid "Firmware"
|
||||||
msgstr ""
|
msgstr "Firmware"
|
||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
||||||
@@ -854,7 +854,7 @@ msgstr "Generelt"
|
|||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Global"
|
msgid "Global"
|
||||||
msgstr ""
|
msgstr "Global"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
@@ -1021,7 +1021,7 @@ msgstr "Loginforsøg mislykkedes"
|
|||||||
#: src/components/containers-table/containers-table.tsx
|
#: src/components/containers-table/containers-table.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Logs"
|
msgid "Logs"
|
||||||
msgstr ""
|
msgstr "Logs"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
|
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
|
||||||
@@ -1074,7 +1074,7 @@ msgstr "Hukommelsesforbrug af dockercontainere"
|
|||||||
#. Device model
|
#. Device model
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Model"
|
msgid "Model"
|
||||||
msgstr ""
|
msgstr "Model"
|
||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
@@ -1087,7 +1087,7 @@ msgstr "Navn"
|
|||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Net"
|
msgid "Net"
|
||||||
msgstr ""
|
msgstr "Net"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/network-charts.tsx
|
#: src/components/routes/system/charts/network-charts.tsx
|
||||||
msgid "Network traffic of docker containers"
|
msgid "Network traffic of docker containers"
|
||||||
@@ -1216,7 +1216,7 @@ msgstr "Tidligere"
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Pause"
|
msgid "Pause"
|
||||||
msgstr ""
|
msgstr "Pause"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
@@ -1286,7 +1286,7 @@ msgstr "Log venligst ind på din konto"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr ""
|
msgstr "Port"
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Container ports"
|
msgctxt "Container ports"
|
||||||
@@ -1384,7 +1384,7 @@ msgstr "Genoptag"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgctxt "Root disk label"
|
msgctxt "Root disk label"
|
||||||
msgid "Root"
|
msgid "Root"
|
||||||
msgstr ""
|
msgstr "Root"
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Rotate token"
|
msgid "Rotate token"
|
||||||
@@ -1536,7 +1536,7 @@ msgstr "Tilstand"
|
|||||||
#: 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 "Status"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
msgid "Sub State"
|
msgid "Sub State"
|
||||||
@@ -1563,7 +1563,7 @@ msgstr "Swap forbrug"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "System"
|
msgid "System"
|
||||||
msgstr ""
|
msgstr "System"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "System load averages over time"
|
msgid "System load averages over time"
|
||||||
@@ -1571,7 +1571,7 @@ msgstr "Gennemsnitlig system belastning over tid"
|
|||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Systemd Services"
|
msgid "Systemd Services"
|
||||||
msgstr ""
|
msgstr "Systemd Services"
|
||||||
|
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Systems"
|
msgid "Systems"
|
||||||
@@ -1615,7 +1615,7 @@ msgstr "Temperaturer i systemsensorer"
|
|||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Test <0>URL</0>"
|
msgid "Test <0>URL</0>"
|
||||||
msgstr ""
|
msgstr "Test <0>URL</0>"
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Test heartbeat"
|
msgid "Test heartbeat"
|
||||||
@@ -1760,7 +1760,7 @@ msgstr "Udløser når brugen af en disk overstiger en tærskel"
|
|||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Type"
|
msgid "Type"
|
||||||
msgstr ""
|
msgstr "Type"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Unit file"
|
msgid "Unit file"
|
||||||
@@ -1931,3 +1931,4 @@ 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."
|
||||||
|
|
||||||
|
|||||||
@@ -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: 2026-04-05 18:27\n"
|
"PO-Revision-Date: 2026-04-05 20:36\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"
|
||||||
@@ -137,7 +137,7 @@ msgstr "Breite des Hauptlayouts anpassen"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr ""
|
msgstr "Admin"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "After"
|
msgid "After"
|
||||||
@@ -149,7 +149,7 @@ msgstr "Starten Sie nach dem Festlegen der Umgebungsvariablen Ihren Beszel-Hub n
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Agent"
|
msgid "Agent"
|
||||||
msgstr ""
|
msgstr "Agent"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
@@ -238,7 +238,7 @@ msgstr "Durchschnittliche Auslastung der GPU-Engines"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Backups"
|
msgid "Backups"
|
||||||
msgstr ""
|
msgstr "Backups"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/network-charts.tsx
|
#: src/components/routes/system/charts/network-charts.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
@@ -289,7 +289,7 @@ msgstr "Binär"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bits (Kbps, Mbps, Gbps)"
|
msgid "Bits (Kbps, Mbps, Gbps)"
|
||||||
msgstr ""
|
msgstr "Bits (Kbps, Mbps, Gbps)"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Boot state"
|
msgid "Boot state"
|
||||||
@@ -298,7 +298,7 @@ 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)"
|
||||||
msgstr ""
|
msgstr "Bytes (KB/s, MB/s, GB/s)"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/memory-charts.tsx
|
#: src/components/routes/system/charts/memory-charts.tsx
|
||||||
msgid "Cache / Buffers"
|
msgid "Cache / Buffers"
|
||||||
@@ -336,7 +336,7 @@ msgstr "Vorsicht - potenzieller Datenverlust"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr ""
|
msgstr "Celsius (°C)"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -496,7 +496,7 @@ msgstr "Kern"
|
|||||||
#: src/components/systemd-table/systemd-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 "CPU"
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -772,7 +772,7 @@ msgstr "Exportiere die aktuelle Systemkonfiguration."
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr ""
|
msgstr "Fahrenheit (°F)"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
@@ -816,7 +816,7 @@ msgstr "Fehlgeschlagen: {0}"
|
|||||||
#: src/components/systemd-table/systemd-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 "Filter..."
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Fingerprint"
|
msgid "Fingerprint"
|
||||||
@@ -854,7 +854,7 @@ msgstr "Allgemein"
|
|||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Global"
|
msgid "Global"
|
||||||
msgstr ""
|
msgstr "Global"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
@@ -883,7 +883,7 @@ msgstr "Gesundheit"
|
|||||||
|
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Heartbeat"
|
msgid "Heartbeat"
|
||||||
msgstr ""
|
msgstr "Heartbeat"
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Heartbeat Monitoring"
|
msgid "Heartbeat Monitoring"
|
||||||
@@ -901,7 +901,7 @@ msgstr "Homebrew-Befehl"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Host / IP"
|
msgid "Host / IP"
|
||||||
msgstr ""
|
msgstr "Host / IP"
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "HTTP Method"
|
msgid "HTTP Method"
|
||||||
@@ -938,7 +938,7 @@ msgstr "Wenn du das Passwort für dein Administratorkonto verloren hast, kannst
|
|||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Docker image"
|
msgctxt "Docker image"
|
||||||
msgid "Image"
|
msgid "Image"
|
||||||
msgstr ""
|
msgstr "Image"
|
||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Inactive"
|
msgid "Inactive"
|
||||||
@@ -1082,7 +1082,7 @@ msgstr "Modell"
|
|||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr ""
|
msgstr "Name"
|
||||||
|
|
||||||
#: 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
|
||||||
@@ -1216,7 +1216,7 @@ msgstr "Vergangen"
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Pause"
|
msgid "Pause"
|
||||||
msgstr ""
|
msgstr "Pause"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
@@ -1286,7 +1286,7 @@ msgstr "Bitte melde dich bei deinem Konto an"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr ""
|
msgstr "Port"
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Container ports"
|
msgctxt "Container ports"
|
||||||
@@ -1384,7 +1384,7 @@ msgstr "Fortsetzen"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgctxt "Root disk label"
|
msgctxt "Root disk label"
|
||||||
msgid "Root"
|
msgid "Root"
|
||||||
msgstr ""
|
msgstr "Root"
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Rotate token"
|
msgid "Rotate token"
|
||||||
@@ -1536,7 +1536,7 @@ msgstr "Status"
|
|||||||
#: 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 "Status"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
msgid "Sub State"
|
msgid "Sub State"
|
||||||
@@ -1563,7 +1563,7 @@ msgstr "Swap-Nutzung"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "System"
|
msgid "System"
|
||||||
msgstr ""
|
msgstr "System"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "System load averages over time"
|
msgid "System load averages over time"
|
||||||
@@ -1615,7 +1615,7 @@ msgstr "Temperaturen der Systemsensoren"
|
|||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Test <0>URL</0>"
|
msgid "Test <0>URL</0>"
|
||||||
msgstr ""
|
msgstr "Test <0>URL</0>"
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Test heartbeat"
|
msgid "Test heartbeat"
|
||||||
@@ -1665,7 +1665,7 @@ msgstr "Darstellung umschalten"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr ""
|
msgstr "Token"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -1931,3 +1931,4 @@ 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."
|
||||||
|
|
||||||
|
|||||||
@@ -204,19 +204,10 @@ msgstr "Average drops below <0>{value}{0}</0>"
|
|||||||
msgid "Average exceeds <0>{value}{0}</0>"
|
msgid "Average exceeds <0>{value}{0}</0>"
|
||||||
msgstr "Average exceeds <0>{value}{0}</0>"
|
msgstr "Average exceeds <0>{value}{0}</0>"
|
||||||
|
|
||||||
#: src/components/routes/system/disk-io-sheet.tsx
|
|
||||||
msgid "Average number of I/O operations waiting to be serviced"
|
|
||||||
msgstr "Average number of I/O operations waiting to be serviced"
|
|
||||||
|
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
msgid "Average power consumption of GPUs"
|
msgid "Average power consumption of GPUs"
|
||||||
msgstr "Average power consumption of GPUs"
|
msgstr "Average power consumption of GPUs"
|
||||||
|
|
||||||
#: src/components/routes/system/disk-io-sheet.tsx
|
|
||||||
msgctxt "Disk I/O average operation time (iostat await)"
|
|
||||||
msgid "Average queue to completion time per operation"
|
|
||||||
msgstr "Average queue to completion time per operation"
|
|
||||||
|
|
||||||
#: src/components/routes/system/charts/cpu-charts.tsx
|
#: src/components/routes/system/charts/cpu-charts.tsx
|
||||||
msgid "Average system-wide CPU utilization"
|
msgid "Average system-wide CPU utilization"
|
||||||
msgstr "Average system-wide CPU utilization"
|
msgstr "Average system-wide CPU utilization"
|
||||||
@@ -448,11 +439,6 @@ msgctxt "Environment variables"
|
|||||||
msgid "Copy env"
|
msgid "Copy env"
|
||||||
msgstr "Copy env"
|
msgstr "Copy env"
|
||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
|
||||||
msgctxt "Copy alerts from another system"
|
|
||||||
msgid "Copy from"
|
|
||||||
msgstr "Copy from"
|
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Copy host"
|
msgid "Copy host"
|
||||||
msgstr "Copy host"
|
msgstr "Copy host"
|
||||||
@@ -608,11 +594,12 @@ msgstr "Disk unit"
|
|||||||
|
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
|
#: src/components/routes/system/charts/extra-fs-charts.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Disk Usage"
|
msgid "Disk Usage"
|
||||||
msgstr "Disk Usage"
|
msgstr "Disk Usage"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/extra-fs-charts.tsx
|
||||||
msgid "Disk usage of {extraFsName}"
|
msgid "Disk usage of {extraFsName}"
|
||||||
msgstr "Disk usage of {extraFsName}"
|
msgstr "Disk usage of {extraFsName}"
|
||||||
|
|
||||||
@@ -906,21 +893,6 @@ msgstr "HTTP Method"
|
|||||||
msgid "HTTP method: POST, GET, or HEAD (default: POST)"
|
msgid "HTTP method: POST, GET, or HEAD (default: POST)"
|
||||||
msgstr "HTTP method: POST, GET, or HEAD (default: POST)"
|
msgstr "HTTP method: POST, GET, or HEAD (default: POST)"
|
||||||
|
|
||||||
#: src/components/routes/system/disk-io-sheet.tsx
|
|
||||||
msgctxt "Disk I/O average operation time (iostat await)"
|
|
||||||
msgid "I/O Await"
|
|
||||||
msgstr "I/O Await"
|
|
||||||
|
|
||||||
#: src/components/routes/system/disk-io-sheet.tsx
|
|
||||||
msgctxt "Disk I/O total time spent on read/write"
|
|
||||||
msgid "I/O Time"
|
|
||||||
msgstr "I/O Time"
|
|
||||||
|
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
|
||||||
msgctxt "Percent of time the disk is busy with I/O"
|
|
||||||
msgid "I/O Utilization"
|
|
||||||
msgstr "I/O Utilization"
|
|
||||||
|
|
||||||
#. Context: Battery state
|
#. Context: Battery state
|
||||||
#: src/lib/i18n.ts
|
#: src/lib/i18n.ts
|
||||||
msgid "Idle"
|
msgid "Idle"
|
||||||
@@ -1230,10 +1202,6 @@ msgstr "Payload format"
|
|||||||
msgid "Per-core average utilization"
|
msgid "Per-core average utilization"
|
||||||
msgstr "Per-core average utilization"
|
msgstr "Per-core average utilization"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
|
||||||
msgid "Percent of time the disk is busy with I/O"
|
|
||||||
msgstr "Percent of time the disk is busy with I/O"
|
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "Percentage of time spent in each state"
|
msgid "Percentage of time spent in each state"
|
||||||
msgstr "Percentage of time spent in each state"
|
msgstr "Percentage of time spent in each state"
|
||||||
@@ -1311,20 +1279,13 @@ msgstr "Process started"
|
|||||||
msgid "Public Key"
|
msgid "Public Key"
|
||||||
msgstr "Public Key"
|
msgstr "Public Key"
|
||||||
|
|
||||||
#: src/components/routes/system/disk-io-sheet.tsx
|
|
||||||
msgctxt "Disk I/O average queue depth"
|
|
||||||
msgid "Queue Depth"
|
|
||||||
msgstr "Queue Depth"
|
|
||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Quiet Hours"
|
msgid "Quiet Hours"
|
||||||
msgstr "Quiet Hours"
|
msgstr "Quiet Hours"
|
||||||
|
|
||||||
#. Disk read
|
#. Disk read
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
#: src/components/routes/system/disk-io-sheet.tsx
|
#: src/components/routes/system/charts/extra-fs-charts.tsx
|
||||||
#: src/components/routes/system/disk-io-sheet.tsx
|
|
||||||
#: src/components/routes/system/disk-io-sheet.tsx
|
|
||||||
msgid "Read"
|
msgid "Read"
|
||||||
msgstr "Read"
|
msgstr "Read"
|
||||||
|
|
||||||
@@ -1636,7 +1597,7 @@ msgstr "This action cannot be undone. This will permanently delete all current r
|
|||||||
msgid "This will permanently delete all selected records from the database."
|
msgid "This will permanently delete all selected records from the database."
|
||||||
msgstr "This will permanently delete all selected records from the database."
|
msgstr "This will permanently delete all selected records from the database."
|
||||||
|
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/extra-fs-charts.tsx
|
||||||
msgid "Throughput of {extraFsName}"
|
msgid "Throughput of {extraFsName}"
|
||||||
msgstr "Throughput of {extraFsName}"
|
msgstr "Throughput of {extraFsName}"
|
||||||
|
|
||||||
@@ -1689,11 +1650,6 @@ 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"
|
||||||
|
|
||||||
#: src/components/routes/system/disk-io-sheet.tsx
|
|
||||||
msgctxt "Disk I/O"
|
|
||||||
msgid "Total time spent on read/write (can exceed 100%)"
|
|
||||||
msgstr "Total time spent on read/write (can exceed 100%)"
|
|
||||||
|
|
||||||
#. placeholder {0}: data.length
|
#. placeholder {0}: data.length
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Total: {0}"
|
msgid "Total: {0}"
|
||||||
@@ -1814,7 +1770,7 @@ msgstr "Upload"
|
|||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
msgstr "Uptime"
|
msgstr "Uptime"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/extra-fs-charts.tsx
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
@@ -1836,11 +1792,6 @@ msgstr "Used"
|
|||||||
msgid "Users"
|
msgid "Users"
|
||||||
msgstr "Users"
|
msgstr "Users"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
|
||||||
msgctxt "Disk I/O utilization"
|
|
||||||
msgid "Utilization"
|
|
||||||
msgstr "Utilization"
|
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Value"
|
msgid "Value"
|
||||||
msgstr "Value"
|
msgstr "Value"
|
||||||
@@ -1850,7 +1801,6 @@ msgid "View"
|
|||||||
msgstr "View"
|
msgstr "View"
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
#: src/components/routes/system/disk-io-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"
|
||||||
@@ -1903,9 +1853,7 @@ msgstr "Windows command"
|
|||||||
|
|
||||||
#. Disk write
|
#. Disk write
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
#: src/components/routes/system/disk-io-sheet.tsx
|
#: src/components/routes/system/charts/extra-fs-charts.tsx
|
||||||
#: src/components/routes/system/disk-io-sheet.tsx
|
|
||||||
#: src/components/routes/system/disk-io-sheet.tsx
|
|
||||||
msgid "Write"
|
msgid "Write"
|
||||||
msgstr "Write"
|
msgstr "Write"
|
||||||
|
|
||||||
|
|||||||
@@ -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: 2026-04-05 18:27\n"
|
"PO-Revision-Date: 2026-04-05 20:36\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"
|
||||||
@@ -57,7 +57,7 @@ msgstr "1 hora"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr ""
|
msgstr "1 min"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 minute"
|
msgid "1 minute"
|
||||||
@@ -74,7 +74,7 @@ msgstr "12 horas"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "15 min"
|
msgid "15 min"
|
||||||
msgstr ""
|
msgstr "15 min"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "24 hours"
|
msgid "24 hours"
|
||||||
@@ -87,7 +87,7 @@ msgstr "30 días"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "5 min"
|
msgid "5 min"
|
||||||
msgstr ""
|
msgstr "5 min"
|
||||||
|
|
||||||
#. Table column
|
#. Table column
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
@@ -248,7 +248,7 @@ msgstr "Ancho de banda"
|
|||||||
#. Battery label in systems table header
|
#. Battery label in systems table header
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Bat"
|
msgid "Bat"
|
||||||
msgstr ""
|
msgstr "Bat"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
@@ -336,7 +336,7 @@ msgstr "Precaución - posible pérdida de datos"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr ""
|
msgstr "Celsius (°C)"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -496,7 +496,7 @@ msgstr "Núcleo"
|
|||||||
#: src/components/systemd-table/systemd-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 "CPU"
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -729,7 +729,7 @@ msgstr "Efímero"
|
|||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr ""
|
msgstr "Error"
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Example:"
|
msgid "Example:"
|
||||||
@@ -772,7 +772,7 @@ msgstr "Exporta la configuración actual de sus sistemas."
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr ""
|
msgstr "Fahrenheit (°F)"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
@@ -824,7 +824,7 @@ msgstr "Huella dactilar"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Firmware"
|
msgid "Firmware"
|
||||||
msgstr ""
|
msgstr "Firmware"
|
||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
||||||
@@ -850,11 +850,11 @@ msgstr "Llena"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "General"
|
msgid "General"
|
||||||
msgstr ""
|
msgstr "General"
|
||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Global"
|
msgid "Global"
|
||||||
msgstr ""
|
msgstr "Global"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
@@ -1109,7 +1109,7 @@ 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
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "No"
|
msgid "No"
|
||||||
msgstr ""
|
msgstr "No"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
@@ -1665,7 +1665,7 @@ msgstr "Alternar tema"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr ""
|
msgstr "Token"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -1684,7 +1684,7 @@ msgstr "Los tokens y las huellas digitales se utilizan para autenticar las conex
|
|||||||
#: src/components/ui/chart.tsx
|
#: src/components/ui/chart.tsx
|
||||||
#: src/components/ui/chart.tsx
|
#: src/components/ui/chart.tsx
|
||||||
msgid "Total"
|
msgid "Total"
|
||||||
msgstr ""
|
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"
|
||||||
@@ -1702,7 +1702,7 @@ msgstr ""
|
|||||||
#. placeholder {0}: data.length
|
#. placeholder {0}: data.length
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Total: {0}"
|
msgid "Total: {0}"
|
||||||
msgstr ""
|
msgstr "Total: {0}"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Triggered by"
|
msgid "Triggered by"
|
||||||
@@ -1931,3 +1931,4 @@ 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 "Tu configuración de usuario ha sido actualizada."
|
msgstr "Tu configuración de usuario ha sido actualizada."
|
||||||
|
|
||||||
|
|||||||
@@ -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: 2026-04-05 18:28\n"
|
"PO-Revision-Date: 2026-04-05 20:37\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"
|
||||||
@@ -1931,3 +1931,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 "تنظیمات کاربری شما بهروزرسانی شد."
|
msgstr "تنظیمات کاربری شما بهروزرسانی شد."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: fr\n"
|
"Language: fr\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-04-05 18:27\n"
|
"PO-Revision-Date: 2026-04-05 20:36\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"
|
||||||
@@ -57,11 +57,11 @@ msgstr "1 heure"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr ""
|
msgstr "1 min"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 minute"
|
msgid "1 minute"
|
||||||
msgstr ""
|
msgstr "1 minute"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
@@ -74,7 +74,7 @@ msgstr "12 heures"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "15 min"
|
msgid "15 min"
|
||||||
msgstr ""
|
msgstr "15 min"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "24 hours"
|
msgid "24 hours"
|
||||||
@@ -87,7 +87,7 @@ msgstr "30 jours"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "5 min"
|
msgid "5 min"
|
||||||
msgstr ""
|
msgstr "5 min"
|
||||||
|
|
||||||
#. Table column
|
#. Table column
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
@@ -95,14 +95,14 @@ msgstr ""
|
|||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Actions"
|
msgid "Actions"
|
||||||
msgstr ""
|
msgstr "Actions"
|
||||||
|
|
||||||
#: 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
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Active"
|
msgid "Active"
|
||||||
msgstr ""
|
msgstr "Active"
|
||||||
|
|
||||||
#: src/components/active-alerts.tsx
|
#: src/components/active-alerts.tsx
|
||||||
msgid "Active Alerts"
|
msgid "Active Alerts"
|
||||||
@@ -137,7 +137,7 @@ msgstr "Ajuster la largeur de la mise en page principale"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr ""
|
msgstr "Admin"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "After"
|
msgid "After"
|
||||||
@@ -149,7 +149,7 @@ msgstr "Après avoir défini les variables d'environnement, redémarrez votre hu
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Agent"
|
msgid "Agent"
|
||||||
msgstr ""
|
msgstr "Agent"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
@@ -248,7 +248,7 @@ msgstr "Bande passante"
|
|||||||
#. Battery label in systems table header
|
#. Battery label in systems table header
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Bat"
|
msgid "Bat"
|
||||||
msgstr ""
|
msgstr "Bat"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
@@ -289,7 +289,7 @@ msgstr "Binaire"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bits (Kbps, Mbps, Gbps)"
|
msgid "Bits (Kbps, Mbps, Gbps)"
|
||||||
msgstr ""
|
msgstr "Bits (Kbps, Mbps, Gbps)"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Boot state"
|
msgid "Boot state"
|
||||||
@@ -298,7 +298,7 @@ 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)"
|
||||||
msgstr ""
|
msgstr "Bytes (KB/s, MB/s, GB/s)"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/memory-charts.tsx
|
#: src/components/routes/system/charts/memory-charts.tsx
|
||||||
msgid "Cache / Buffers"
|
msgid "Cache / Buffers"
|
||||||
@@ -336,7 +336,7 @@ msgstr "Attention - perte de données potentielle"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr ""
|
msgstr "Celsius (°C)"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -348,7 +348,7 @@ msgstr "Modifier les options générales de l'application."
|
|||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
msgid "Charge"
|
msgid "Charge"
|
||||||
msgstr ""
|
msgstr "Charge"
|
||||||
|
|
||||||
#. Context: Battery state
|
#. Context: Battery state
|
||||||
#: src/lib/i18n.ts
|
#: src/lib/i18n.ts
|
||||||
@@ -496,7 +496,7 @@ msgstr "Cœur"
|
|||||||
#: src/components/systemd-table/systemd-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 "CPU"
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -554,7 +554,7 @@ msgstr "État actuel"
|
|||||||
#. Power Cycles
|
#. Power Cycles
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Cycles"
|
msgid "Cycles"
|
||||||
msgstr ""
|
msgstr "Cycles"
|
||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
@@ -583,7 +583,7 @@ msgstr "Supprimer l'empreinte"
|
|||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr ""
|
msgstr "Description"
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table.tsx
|
#: src/components/containers-table/containers-table.tsx
|
||||||
msgid "Detail"
|
msgid "Detail"
|
||||||
@@ -641,7 +641,7 @@ msgstr "Entrée/Sortie réseau Docker"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Documentation"
|
msgid "Documentation"
|
||||||
msgstr ""
|
msgstr "Documentation"
|
||||||
|
|
||||||
#. Context: System is down
|
#. Context: System is down
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
@@ -677,7 +677,7 @@ msgstr "Modifier {foo}"
|
|||||||
#: 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 ""
|
msgstr "Email"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Email notifications"
|
msgid "Email notifications"
|
||||||
@@ -772,7 +772,7 @@ msgstr "Exportez la configuration actuelle de vos systèmes."
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr ""
|
msgstr "Fahrenheit (°F)"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
@@ -854,7 +854,7 @@ msgstr "Général"
|
|||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Global"
|
msgid "Global"
|
||||||
msgstr ""
|
msgstr "Global"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
@@ -938,7 +938,7 @@ msgstr "Si vous avez perdu le mot de passe de votre compte administrateur, vous
|
|||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Docker image"
|
msgctxt "Docker image"
|
||||||
msgid "Image"
|
msgid "Image"
|
||||||
msgstr ""
|
msgstr "Image"
|
||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Inactive"
|
msgid "Inactive"
|
||||||
@@ -1043,7 +1043,7 @@ msgstr "Guide pour une installation manuelle"
|
|||||||
#. Chart select field. Please try to keep this short.
|
#. Chart select field. Please try to keep this short.
|
||||||
#: src/components/routes/system/chart-card.tsx
|
#: src/components/routes/system/chart-card.tsx
|
||||||
msgid "Max 1 min"
|
msgid "Max 1 min"
|
||||||
msgstr ""
|
msgstr "Max 1 min"
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
@@ -1138,7 +1138,7 @@ msgstr "Aucun système trouvé."
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Notifications"
|
msgid "Notifications"
|
||||||
msgstr ""
|
msgstr "Notifications"
|
||||||
|
|
||||||
#: src/components/login/auth-form.tsx
|
#: src/components/login/auth-form.tsx
|
||||||
msgid "OAuth 2 / OIDC support"
|
msgid "OAuth 2 / OIDC support"
|
||||||
@@ -1181,7 +1181,7 @@ msgstr "Écraser les alertes existantes"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
msgid "Page"
|
msgid "Page"
|
||||||
msgstr ""
|
msgstr "Page"
|
||||||
|
|
||||||
#. placeholder {0}: table.getState().pagination.pageIndex + 1
|
#. placeholder {0}: table.getState().pagination.pageIndex + 1
|
||||||
#. placeholder {1}: table.getPageCount()
|
#. placeholder {1}: table.getPageCount()
|
||||||
@@ -1216,7 +1216,7 @@ msgstr "Passé"
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Pause"
|
msgid "Pause"
|
||||||
msgstr ""
|
msgstr "Pause"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
@@ -1245,7 +1245,7 @@ msgstr "Pourcentage de temps passé dans chaque état"
|
|||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Permanent"
|
msgid "Permanent"
|
||||||
msgstr ""
|
msgstr "Permanent"
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Persistence"
|
msgid "Persistence"
|
||||||
@@ -1286,7 +1286,7 @@ msgstr "Veuillez vous connecter à votre compte"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr ""
|
msgstr "Port"
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Container ports"
|
msgctxt "Container ports"
|
||||||
@@ -1482,7 +1482,7 @@ msgstr "Détails du service"
|
|||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr ""
|
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."
|
||||||
@@ -1665,7 +1665,7 @@ msgstr "Changer le thème"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr ""
|
msgstr "Token"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -1684,7 +1684,7 @@ msgstr "Les tokens et les empreintes sont utilisés pour authentifier les connex
|
|||||||
#: src/components/ui/chart.tsx
|
#: src/components/ui/chart.tsx
|
||||||
#: src/components/ui/chart.tsx
|
#: src/components/ui/chart.tsx
|
||||||
msgid "Total"
|
msgid "Total"
|
||||||
msgstr ""
|
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"
|
||||||
@@ -1760,7 +1760,7 @@ msgstr "Déclenchement lorsque l'utilisation de tout disque dépasse un seuil"
|
|||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Type"
|
msgid "Type"
|
||||||
msgstr ""
|
msgstr "Type"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Unit file"
|
msgid "Unit file"
|
||||||
@@ -1931,3 +1931,4 @@ 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."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: he\n"
|
"Language: he\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-04-05 18:27\n"
|
"PO-Revision-Date: 2026-04-05 20:36\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Hebrew\n"
|
"Language-Team: Hebrew\n"
|
||||||
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n"
|
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n"
|
||||||
@@ -496,7 +496,7 @@ msgstr "ליבה"
|
|||||||
#: src/components/systemd-table/systemd-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 "CPU"
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -1665,7 +1665,7 @@ msgstr "החלף ערכת נושא"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr ""
|
msgstr "Token"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -1931,3 +1931,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 "הגדרות המשתמש שלך עודכנו."
|
msgstr "הגדרות המשתמש שלך עודכנו."
|
||||||
|
|
||||||
|
|||||||
@@ -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: 2026-04-05 18:28\n"
|
"PO-Revision-Date: 2026-04-05 20:37\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"
|
||||||
@@ -137,7 +137,7 @@ msgstr "Prilagodite širinu glavnog rasporeda"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr ""
|
msgstr "Admin"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "After"
|
msgid "After"
|
||||||
@@ -149,7 +149,7 @@ msgstr "Nakon postavljanja varijabli okruženja, ponovno pokrenite svoj Beszel h
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Agent"
|
msgid "Agent"
|
||||||
msgstr ""
|
msgstr "Agent"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
@@ -336,7 +336,7 @@ msgstr "Oprez - mogući gubitak podataka"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr ""
|
msgstr "Celsius (°C)"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -601,11 +601,11 @@ msgstr "Prazni se"
|
|||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Disk"
|
msgid "Disk"
|
||||||
msgstr ""
|
msgstr "Disk"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
msgid "Disk I/O"
|
msgid "Disk I/O"
|
||||||
msgstr ""
|
msgstr "Disk I/O"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Disk unit"
|
msgid "Disk unit"
|
||||||
@@ -677,7 +677,7 @@ msgstr "Uredi {foo}"
|
|||||||
#: 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 ""
|
msgstr "Email"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Email notifications"
|
msgid "Email notifications"
|
||||||
@@ -901,7 +901,7 @@ msgstr "Homebrew naredba"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Host / IP"
|
msgid "Host / IP"
|
||||||
msgstr ""
|
msgstr "Host / IP"
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "HTTP Method"
|
msgid "HTTP Method"
|
||||||
@@ -1286,7 +1286,7 @@ msgstr "Molimo prijavite se u svoj račun"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr ""
|
msgstr "Port"
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Container ports"
|
msgctxt "Container ports"
|
||||||
@@ -1536,7 +1536,7 @@ msgstr "Stanje"
|
|||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr ""
|
msgstr "Status"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
msgid "Sub State"
|
msgid "Sub State"
|
||||||
@@ -1598,7 +1598,7 @@ msgstr "Zadaci"
|
|||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Temp"
|
msgid "Temp"
|
||||||
msgstr ""
|
msgstr "Temp"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
@@ -1665,7 +1665,7 @@ msgstr "Uključi/isključi temu"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr ""
|
msgstr "Token"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -1931,3 +1931,4 @@ 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."
|
||||||
|
|
||||||
|
|||||||
@@ -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: 2026-04-05 18:27\n"
|
"PO-Revision-Date: 2026-04-05 20:36\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"
|
||||||
@@ -336,7 +336,7 @@ msgstr "Figyelem - potenciális adatvesztés"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr ""
|
msgstr "Celsius (°C)"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -496,7 +496,7 @@ msgstr "Mag"
|
|||||||
#: src/components/systemd-table/systemd-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 "CPU"
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -677,7 +677,7 @@ msgstr "Szerkesztés {foo}"
|
|||||||
#: 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 ""
|
msgstr "Email"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Email notifications"
|
msgid "Email notifications"
|
||||||
@@ -772,7 +772,7 @@ msgstr "Exportálja a jelenlegi rendszerkonfigurációt."
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr ""
|
msgstr "Fahrenheit (°F)"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
@@ -824,7 +824,7 @@ msgstr "Ujjlenyomat"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Firmware"
|
msgid "Firmware"
|
||||||
msgstr ""
|
msgstr "Firmware"
|
||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
||||||
@@ -1286,7 +1286,7 @@ msgstr "Kérjük, jelentkezzen be a fiókjába"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr ""
|
msgstr "Port"
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Container ports"
|
msgctxt "Container ports"
|
||||||
@@ -1665,7 +1665,7 @@ msgstr "Téma váltása"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr ""
|
msgstr "Token"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -1931,3 +1931,4 @@ 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."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: id\n"
|
"Language: id\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-04-05 18:28\n"
|
"PO-Revision-Date: 2026-04-05 20:37\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Indonesian\n"
|
"Language-Team: Indonesian\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
@@ -137,7 +137,7 @@ msgstr "Sesuaikan lebar layar utama"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr ""
|
msgstr "Admin"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "After"
|
msgid "After"
|
||||||
@@ -302,7 +302,7 @@ msgstr "Byte (KB/s, MB/s, GB/s)"
|
|||||||
|
|
||||||
#: src/components/routes/system/charts/memory-charts.tsx
|
#: src/components/routes/system/charts/memory-charts.tsx
|
||||||
msgid "Cache / Buffers"
|
msgid "Cache / Buffers"
|
||||||
msgstr ""
|
msgstr "Cache / Buffers"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Can reload"
|
msgid "Can reload"
|
||||||
@@ -336,7 +336,7 @@ msgstr "Perhatian - potensi kehilangan data"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr ""
|
msgstr "Celsius (°C)"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -1931,3 +1931,4 @@ msgstr "Ya"
|
|||||||
#: 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 "Pengaturan pengguna anda telah diperbarui."
|
msgstr "Pengaturan pengguna anda telah diperbarui."
|
||||||
|
|
||||||
|
|||||||
@@ -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: 2026-04-05 18:27\n"
|
"PO-Revision-Date: 2026-04-05 20:36\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"
|
||||||
@@ -57,7 +57,7 @@ msgstr "1 ora"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr ""
|
msgstr "1 min"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 minute"
|
msgid "1 minute"
|
||||||
@@ -74,7 +74,7 @@ msgstr "12 ore"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "15 min"
|
msgid "15 min"
|
||||||
msgstr ""
|
msgstr "15 min"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "24 hours"
|
msgid "24 hours"
|
||||||
@@ -87,7 +87,7 @@ msgstr "30 giorni"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "5 min"
|
msgid "5 min"
|
||||||
msgstr ""
|
msgstr "5 min"
|
||||||
|
|
||||||
#. Table column
|
#. Table column
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
@@ -336,7 +336,7 @@ msgstr "Attenzione - possibile perdita di dati"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr ""
|
msgstr "Celsius (°C)"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -496,7 +496,7 @@ msgstr ""
|
|||||||
#: src/components/systemd-table/systemd-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 "CPU"
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -677,7 +677,7 @@ msgstr "Modifica {foo}"
|
|||||||
#: 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 ""
|
msgstr "Email"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Email notifications"
|
msgid "Email notifications"
|
||||||
@@ -772,7 +772,7 @@ msgstr "Esporta la configurazione attuale dei tuoi sistemi."
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr ""
|
msgstr "Fahrenheit (°F)"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
@@ -824,7 +824,7 @@ msgstr "Impronta digitale"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Firmware"
|
msgid "Firmware"
|
||||||
msgstr ""
|
msgstr "Firmware"
|
||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
||||||
@@ -901,7 +901,7 @@ msgstr "Comando Homebrew"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Host / IP"
|
msgid "Host / IP"
|
||||||
msgstr ""
|
msgstr "Host / IP"
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "HTTP Method"
|
msgid "HTTP Method"
|
||||||
@@ -1043,7 +1043,7 @@ msgstr "Istruzioni di configurazione manuale"
|
|||||||
#. Chart select field. Please try to keep this short.
|
#. Chart select field. Please try to keep this short.
|
||||||
#: src/components/routes/system/chart-card.tsx
|
#: src/components/routes/system/chart-card.tsx
|
||||||
msgid "Max 1 min"
|
msgid "Max 1 min"
|
||||||
msgstr ""
|
msgstr "Max 1 min"
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
@@ -1196,7 +1196,7 @@ msgstr "Pagine / Impostazioni"
|
|||||||
#: src/components/login/auth-form.tsx
|
#: src/components/login/auth-form.tsx
|
||||||
#: src/components/login/auth-form.tsx
|
#: src/components/login/auth-form.tsx
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr ""
|
msgstr "Password"
|
||||||
|
|
||||||
#: src/components/login/auth-form.tsx
|
#: src/components/login/auth-form.tsx
|
||||||
msgid "Password must be at least 8 characters."
|
msgid "Password must be at least 8 characters."
|
||||||
@@ -1384,7 +1384,7 @@ msgstr "Riprendi"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgctxt "Root disk label"
|
msgctxt "Root disk label"
|
||||||
msgid "Root"
|
msgid "Root"
|
||||||
msgstr ""
|
msgstr "Root"
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Rotate token"
|
msgid "Rotate token"
|
||||||
@@ -1615,7 +1615,7 @@ msgstr "Temperature dei sensori di sistema"
|
|||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Test <0>URL</0>"
|
msgid "Test <0>URL</0>"
|
||||||
msgstr ""
|
msgstr "Test <0>URL</0>"
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Test heartbeat"
|
msgid "Test heartbeat"
|
||||||
@@ -1665,7 +1665,7 @@ msgstr "Attiva/disattiva tema"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr ""
|
msgstr "Token"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -1931,3 +1931,4 @@ 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."
|
||||||
|
|
||||||
|
|||||||
@@ -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: 2026-04-05 18:27\n"
|
"PO-Revision-Date: 2026-04-05 20:36\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"
|
||||||
@@ -1931,3 +1931,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 "ユーザー設定が更新されました。"
|
msgstr "ユーザー設定が更新されました。"
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: ko\n"
|
"Language: ko\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-04-05 18:27\n"
|
"PO-Revision-Date: 2026-04-05 20:37\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Korean\n"
|
"Language-Team: Korean\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
@@ -496,7 +496,7 @@ msgstr "코어"
|
|||||||
#: src/components/systemd-table/systemd-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 "CPU"
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -1931,3 +1931,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 "사용자 설정이 업데이트되었습니다."
|
msgstr "사용자 설정이 업데이트되었습니다."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: nl\n"
|
"Language: nl\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-04-05 18:27\n"
|
"PO-Revision-Date: 2026-04-05 20:37\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Dutch\n"
|
"Language-Team: Dutch\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@@ -48,7 +48,7 @@ msgstr "{count, plural, one {{countString} minuut} other {{countString} minuten}
|
|||||||
|
|
||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
msgid "{threads, plural, one {# thread} other {# threads}}"
|
msgid "{threads, plural, one {# thread} other {# threads}}"
|
||||||
msgstr ""
|
msgstr "{threads, plural, one {# thread} other {# threads}}"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
@@ -149,7 +149,7 @@ msgstr "Start na het instellen van de omgevingsvariabelen je Beszel-hub opnieuw
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Agent"
|
msgid "Agent"
|
||||||
msgstr ""
|
msgstr "Agent"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
@@ -248,7 +248,7 @@ msgstr "Bandbreedte"
|
|||||||
#. Battery label in systems table header
|
#. Battery label in systems table header
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Bat"
|
msgid "Bat"
|
||||||
msgstr ""
|
msgstr "Bat"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
@@ -289,7 +289,7 @@ msgstr "Binair"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bits (Kbps, Mbps, Gbps)"
|
msgid "Bits (Kbps, Mbps, Gbps)"
|
||||||
msgstr ""
|
msgstr "Bits (Kbps, Mbps, Gbps)"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Boot state"
|
msgid "Boot state"
|
||||||
@@ -298,11 +298,11 @@ msgstr "Opstartstatus"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bytes (KB/s, MB/s, GB/s)"
|
msgid "Bytes (KB/s, MB/s, GB/s)"
|
||||||
msgstr ""
|
msgstr "Bytes (KB/s, MB/s, GB/s)"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/memory-charts.tsx
|
#: src/components/routes/system/charts/memory-charts.tsx
|
||||||
msgid "Cache / Buffers"
|
msgid "Cache / Buffers"
|
||||||
msgstr ""
|
msgstr "Cache / Buffers"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Can reload"
|
msgid "Can reload"
|
||||||
@@ -336,7 +336,7 @@ msgstr "Opgelet - potentieel gegevensverlies"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr ""
|
msgstr "Celsius (°C)"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -496,7 +496,7 @@ msgstr "Kern"
|
|||||||
#: src/components/systemd-table/systemd-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 "CPU"
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -772,7 +772,7 @@ msgstr "Exporteer je huidige systeemconfiguratie."
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr ""
|
msgstr "Fahrenheit (°F)"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
@@ -824,7 +824,7 @@ msgstr "Vingerafdruk"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Firmware"
|
msgid "Firmware"
|
||||||
msgstr ""
|
msgstr "Firmware"
|
||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
||||||
@@ -938,7 +938,7 @@ msgstr "Als je het wachtwoord voor je beheerdersaccount bent kwijtgeraakt, kan j
|
|||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Docker image"
|
msgctxt "Docker image"
|
||||||
msgid "Image"
|
msgid "Image"
|
||||||
msgstr ""
|
msgstr "Image"
|
||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Inactive"
|
msgid "Inactive"
|
||||||
@@ -1043,7 +1043,7 @@ msgstr "Handmatige installatie-instructies"
|
|||||||
#. Chart select field. Please try to keep this short.
|
#. Chart select field. Please try to keep this short.
|
||||||
#: src/components/routes/system/chart-card.tsx
|
#: src/components/routes/system/chart-card.tsx
|
||||||
msgid "Max 1 min"
|
msgid "Max 1 min"
|
||||||
msgstr ""
|
msgstr "Max 1 min"
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
@@ -1074,7 +1074,7 @@ msgstr "Geheugengebruik van docker containers"
|
|||||||
#. Device model
|
#. Device model
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Model"
|
msgid "Model"
|
||||||
msgstr ""
|
msgstr "Model"
|
||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
@@ -1384,7 +1384,7 @@ msgstr "Hervatten"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgctxt "Root disk label"
|
msgctxt "Root disk label"
|
||||||
msgid "Root"
|
msgid "Root"
|
||||||
msgstr ""
|
msgstr "Root"
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Rotate token"
|
msgid "Rotate token"
|
||||||
@@ -1482,7 +1482,7 @@ msgstr "Servicedetails"
|
|||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr ""
|
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."
|
||||||
@@ -1536,7 +1536,7 @@ msgstr "Status"
|
|||||||
#: 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 "Status"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
msgid "Sub State"
|
msgid "Sub State"
|
||||||
@@ -1615,7 +1615,7 @@ msgstr "Temperatuur van systeem sensoren"
|
|||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Test <0>URL</0>"
|
msgid "Test <0>URL</0>"
|
||||||
msgstr ""
|
msgstr "Test <0>URL</0>"
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Test heartbeat"
|
msgid "Test heartbeat"
|
||||||
@@ -1665,7 +1665,7 @@ msgstr "Schakel thema"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr ""
|
msgstr "Token"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -1710,7 +1710,7 @@ msgstr "Geactiveerd door"
|
|||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Triggers"
|
msgid "Triggers"
|
||||||
msgstr ""
|
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"
|
||||||
@@ -1760,7 +1760,7 @@ msgstr "Triggert wanneer het gebruik van een schijf een drempelwaarde overschrij
|
|||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Type"
|
msgid "Type"
|
||||||
msgstr ""
|
msgstr "Type"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Unit file"
|
msgid "Unit file"
|
||||||
@@ -1931,3 +1931,4 @@ 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 "Je gebruikersinstellingen zijn bijgewerkt."
|
msgstr "Je gebruikersinstellingen zijn bijgewerkt."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: no\n"
|
"Language: no\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-03-31 07:42\n"
|
"PO-Revision-Date: 2026-04-05 20:37\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Norwegian\n"
|
"Language-Team: Norwegian\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@@ -57,7 +57,7 @@ msgstr "1 time"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr ""
|
msgstr "1 min"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 minute"
|
msgid "1 minute"
|
||||||
@@ -74,7 +74,7 @@ msgstr "12 timer"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "15 min"
|
msgid "15 min"
|
||||||
msgstr ""
|
msgstr "15 min"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "24 hours"
|
msgid "24 hours"
|
||||||
@@ -87,7 +87,7 @@ msgstr "30 dager"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "5 min"
|
msgid "5 min"
|
||||||
msgstr ""
|
msgstr "5 min"
|
||||||
|
|
||||||
#. Table column
|
#. Table column
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
@@ -137,7 +137,7 @@ msgstr "Juster bredden på hovedlayouten"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr ""
|
msgstr "Admin"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "After"
|
msgid "After"
|
||||||
@@ -149,7 +149,7 @@ msgstr "Etter å ha angitt miljøvariablene, start Beszel-huben på nytt for at
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Agent"
|
msgid "Agent"
|
||||||
msgstr ""
|
msgstr "Agent"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
@@ -289,7 +289,7 @@ msgstr "Binær"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bits (Kbps, Mbps, Gbps)"
|
msgid "Bits (Kbps, Mbps, Gbps)"
|
||||||
msgstr ""
|
msgstr "Bits (Kbps, Mbps, Gbps)"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Boot state"
|
msgid "Boot state"
|
||||||
@@ -298,7 +298,7 @@ msgstr "Oppstartstilstand"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bytes (KB/s, MB/s, GB/s)"
|
msgid "Bytes (KB/s, MB/s, GB/s)"
|
||||||
msgstr ""
|
msgstr "Bytes (KB/s, MB/s, GB/s)"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/memory-charts.tsx
|
#: src/components/routes/system/charts/memory-charts.tsx
|
||||||
msgid "Cache / Buffers"
|
msgid "Cache / Buffers"
|
||||||
@@ -336,7 +336,7 @@ msgstr "Advarsel - potensielt tap av data"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr ""
|
msgstr "Celsius (°C)"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -496,7 +496,7 @@ msgstr "Kjerne"
|
|||||||
#: src/components/systemd-table/systemd-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 "CPU"
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -601,11 +601,11 @@ msgstr "Lader ut"
|
|||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Disk"
|
msgid "Disk"
|
||||||
msgstr ""
|
msgstr "Disk"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
msgid "Disk I/O"
|
msgid "Disk I/O"
|
||||||
msgstr ""
|
msgstr "Disk I/O"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Disk unit"
|
msgid "Disk unit"
|
||||||
@@ -772,7 +772,7 @@ msgstr "Eksporter din nåværende systemkonfigurasjon"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr ""
|
msgstr "Fahrenheit (°F)"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
@@ -816,7 +816,7 @@ msgstr "Mislyktes: {0}"
|
|||||||
#: src/components/systemd-table/systemd-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 "Filter..."
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Fingerprint"
|
msgid "Fingerprint"
|
||||||
@@ -854,11 +854,11 @@ msgstr "Generelt"
|
|||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Global"
|
msgid "Global"
|
||||||
msgstr ""
|
msgstr "Global"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
msgstr ""
|
msgstr "GPU"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
msgid "GPU Engines"
|
msgid "GPU Engines"
|
||||||
@@ -938,7 +938,7 @@ msgstr "Dersom du har mistet passordet til admin-kontoen kan du nullstille det m
|
|||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Docker image"
|
msgctxt "Docker image"
|
||||||
msgid "Image"
|
msgid "Image"
|
||||||
msgstr ""
|
msgstr "Image"
|
||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Inactive"
|
msgid "Inactive"
|
||||||
@@ -1216,7 +1216,7 @@ msgstr "Fortid"
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Pause"
|
msgid "Pause"
|
||||||
msgstr ""
|
msgstr "Pause"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
@@ -1245,7 +1245,7 @@ msgstr "Prosentandel av tid brukt i hver tilstand"
|
|||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Permanent"
|
msgid "Permanent"
|
||||||
msgstr ""
|
msgstr "Permanent"
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Persistence"
|
msgid "Persistence"
|
||||||
@@ -1286,7 +1286,7 @@ msgstr "Vennligst logg inn på kontoen din"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr ""
|
msgstr "Port"
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Container ports"
|
msgctxt "Container ports"
|
||||||
@@ -1536,7 +1536,7 @@ msgstr "Tilstand"
|
|||||||
#: 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 "Status"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
msgid "Sub State"
|
msgid "Sub State"
|
||||||
@@ -1563,7 +1563,7 @@ msgstr "Swap-bruk"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "System"
|
msgid "System"
|
||||||
msgstr ""
|
msgstr "System"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "System load averages over time"
|
msgid "System load averages over time"
|
||||||
@@ -1598,7 +1598,7 @@ msgstr "Oppgaver"
|
|||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Temp"
|
msgid "Temp"
|
||||||
msgstr ""
|
msgstr "Temp"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
@@ -1615,7 +1615,7 @@ msgstr "Temperaturer på system-sensorer"
|
|||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Test <0>URL</0>"
|
msgid "Test <0>URL</0>"
|
||||||
msgstr ""
|
msgstr "Test <0>URL</0>"
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Test heartbeat"
|
msgid "Test heartbeat"
|
||||||
@@ -1665,7 +1665,7 @@ msgstr "Tema av/på"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr ""
|
msgstr "Token"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -1760,7 +1760,7 @@ msgstr "Slår inn når forbruk av hvilken som helst disk overstiger en grensever
|
|||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Type"
|
msgid "Type"
|
||||||
msgstr ""
|
msgstr "Type"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Unit file"
|
msgid "Unit file"
|
||||||
@@ -1774,7 +1774,7 @@ msgstr "Enhetspreferanser"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Universal token"
|
msgid "Universal token"
|
||||||
msgstr ""
|
msgstr "Universal token"
|
||||||
|
|
||||||
#. Context: Battery state
|
#. Context: Battery state
|
||||||
#: src/lib/i18n.ts
|
#: src/lib/i18n.ts
|
||||||
@@ -1931,3 +1931,4 @@ 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 brukerinnstillinger har blitt oppdatert."
|
msgstr "Dine brukerinnstillinger har blitt oppdatert."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: pl\n"
|
"Language: pl\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-04-05 18:27\n"
|
"PO-Revision-Date: 2026-04-05 20:37\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Polish\n"
|
"Language-Team: Polish\n"
|
||||||
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
|
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
|
||||||
@@ -57,7 +57,7 @@ msgstr "1 godzina"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr ""
|
msgstr "1 min"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 minute"
|
msgid "1 minute"
|
||||||
@@ -74,7 +74,7 @@ msgstr "12 godzin"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "15 min"
|
msgid "15 min"
|
||||||
msgstr ""
|
msgstr "15 min"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "24 hours"
|
msgid "24 hours"
|
||||||
@@ -87,7 +87,7 @@ msgstr "30 dni"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "5 min"
|
msgid "5 min"
|
||||||
msgstr ""
|
msgstr "5 min"
|
||||||
|
|
||||||
#. Table column
|
#. Table column
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
@@ -137,7 +137,7 @@ msgstr "Dostosuj szerokość widoku"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr ""
|
msgstr "Admin"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "After"
|
msgid "After"
|
||||||
@@ -149,7 +149,7 @@ msgstr "Po ustawieniu zmiennych środowiskowych zrestartuj Beszel hub, aby zmian
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Agent"
|
msgid "Agent"
|
||||||
msgstr ""
|
msgstr "Agent"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
@@ -772,7 +772,7 @@ msgstr "Eksportuj aktualną konfigurację systemów."
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr ""
|
msgstr "Fahrenheit (°F)"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
@@ -858,7 +858,7 @@ msgstr "Globalny"
|
|||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
msgstr ""
|
msgstr "GPU"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
msgid "GPU Engines"
|
msgid "GPU Engines"
|
||||||
@@ -883,7 +883,7 @@ msgstr "Kondycja"
|
|||||||
|
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Heartbeat"
|
msgid "Heartbeat"
|
||||||
msgstr ""
|
msgstr "Heartbeat"
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Heartbeat Monitoring"
|
msgid "Heartbeat Monitoring"
|
||||||
@@ -972,7 +972,7 @@ msgstr "Cykl życia"
|
|||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "limit"
|
msgid "limit"
|
||||||
msgstr ""
|
msgstr "limit"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "Load Average"
|
msgid "Load Average"
|
||||||
@@ -1074,7 +1074,7 @@ msgstr "Użycie pamięci przez kontenery Docker."
|
|||||||
#. Device model
|
#. Device model
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Model"
|
msgid "Model"
|
||||||
msgstr ""
|
msgstr "Model"
|
||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
@@ -1286,7 +1286,7 @@ msgstr "Zaloguj się na swoje konto"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr ""
|
msgstr "Port"
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Container ports"
|
msgctxt "Container ports"
|
||||||
@@ -1384,7 +1384,7 @@ msgstr "Wznów"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgctxt "Root disk label"
|
msgctxt "Root disk label"
|
||||||
msgid "Root"
|
msgid "Root"
|
||||||
msgstr ""
|
msgstr "Root"
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Rotate token"
|
msgid "Rotate token"
|
||||||
@@ -1536,7 +1536,7 @@ msgstr "Stan"
|
|||||||
#: 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 "Status"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
msgid "Sub State"
|
msgid "Sub State"
|
||||||
@@ -1563,7 +1563,7 @@ msgstr "Użycie pamięci wymiany"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "System"
|
msgid "System"
|
||||||
msgstr ""
|
msgstr "System"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "System load averages over time"
|
msgid "System load averages over time"
|
||||||
@@ -1615,7 +1615,7 @@ msgstr "Temperatury czujników systemowych."
|
|||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Test <0>URL</0>"
|
msgid "Test <0>URL</0>"
|
||||||
msgstr ""
|
msgstr "Test <0>URL</0>"
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Test heartbeat"
|
msgid "Test heartbeat"
|
||||||
@@ -1665,7 +1665,7 @@ msgstr "Zmień motyw"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr ""
|
msgstr "Token"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -1931,3 +1931,4 @@ msgstr "Tak"
|
|||||||
#: 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 "Twoje ustawienia użytkownika zostały zaktualizowane."
|
msgstr "Twoje ustawienia użytkownika zostały zaktualizowane."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: pt\n"
|
"Language: pt\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-04-05 18:27\n"
|
"PO-Revision-Date: 2026-04-05 20:37\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Portuguese\n"
|
"Language-Team: Portuguese\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@@ -57,7 +57,7 @@ msgstr "1 hora"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr ""
|
msgstr "1 min"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 minute"
|
msgid "1 minute"
|
||||||
@@ -74,7 +74,7 @@ msgstr "12 horas"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "15 min"
|
msgid "15 min"
|
||||||
msgstr ""
|
msgstr "15 min"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "24 hours"
|
msgid "24 hours"
|
||||||
@@ -87,7 +87,7 @@ msgstr "30 dias"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "5 min"
|
msgid "5 min"
|
||||||
msgstr ""
|
msgstr "5 min"
|
||||||
|
|
||||||
#. Table column
|
#. Table column
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
@@ -137,7 +137,7 @@ msgstr "Ajustar a largura do layout principal"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr ""
|
msgstr "Admin"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "After"
|
msgid "After"
|
||||||
@@ -289,7 +289,7 @@ msgstr "Binário"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bits (Kbps, Mbps, Gbps)"
|
msgid "Bits (Kbps, Mbps, Gbps)"
|
||||||
msgstr ""
|
msgstr "Bits (Kbps, Mbps, Gbps)"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Boot state"
|
msgid "Boot state"
|
||||||
@@ -298,11 +298,11 @@ msgstr "Estado de inicialização"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bytes (KB/s, MB/s, GB/s)"
|
msgid "Bytes (KB/s, MB/s, GB/s)"
|
||||||
msgstr ""
|
msgstr "Bytes (KB/s, MB/s, GB/s)"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/memory-charts.tsx
|
#: src/components/routes/system/charts/memory-charts.tsx
|
||||||
msgid "Cache / Buffers"
|
msgid "Cache / Buffers"
|
||||||
msgstr ""
|
msgstr "Cache / Buffers"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Can reload"
|
msgid "Can reload"
|
||||||
@@ -336,7 +336,7 @@ msgstr "Cuidado - possível perda de dados"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr ""
|
msgstr "Celsius (°C)"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -496,7 +496,7 @@ msgstr ""
|
|||||||
#: src/components/systemd-table/systemd-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 "CPU"
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -677,7 +677,7 @@ msgstr "Editar {foo}"
|
|||||||
#: 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 ""
|
msgstr "Email"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Email notifications"
|
msgid "Email notifications"
|
||||||
@@ -772,7 +772,7 @@ msgstr "Exporte a configuração atual dos seus sistemas."
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr ""
|
msgstr "Fahrenheit (°F)"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
@@ -824,7 +824,7 @@ msgstr "Impressão digital"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Firmware"
|
msgid "Firmware"
|
||||||
msgstr ""
|
msgstr "Firmware"
|
||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
||||||
@@ -854,7 +854,7 @@ msgstr "Geral"
|
|||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Global"
|
msgid "Global"
|
||||||
msgstr ""
|
msgstr "Global"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
@@ -901,7 +901,7 @@ msgstr "Comando Homebrew"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Host / IP"
|
msgid "Host / IP"
|
||||||
msgstr ""
|
msgstr "Host / IP"
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "HTTP Method"
|
msgid "HTTP Method"
|
||||||
@@ -1021,7 +1021,7 @@ msgstr "Tentativa de login falhou"
|
|||||||
#: src/components/containers-table/containers-table.tsx
|
#: src/components/containers-table/containers-table.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Logs"
|
msgid "Logs"
|
||||||
msgstr ""
|
msgstr "Logs"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
|
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
|
||||||
@@ -1598,7 +1598,7 @@ msgstr "Tarefas"
|
|||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Temp"
|
msgid "Temp"
|
||||||
msgstr ""
|
msgstr "Temp"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
@@ -1665,7 +1665,7 @@ msgstr "Alternar tema"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr ""
|
msgstr "Token"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -1684,7 +1684,7 @@ msgstr "Tokens e impressões digitais são usados para autenticar conexões WebS
|
|||||||
#: src/components/ui/chart.tsx
|
#: src/components/ui/chart.tsx
|
||||||
#: src/components/ui/chart.tsx
|
#: src/components/ui/chart.tsx
|
||||||
msgid "Total"
|
msgid "Total"
|
||||||
msgstr ""
|
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"
|
||||||
@@ -1931,3 +1931,4 @@ msgstr "Sim"
|
|||||||
#: 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 "As configurações do seu usuário foram atualizadas."
|
msgstr "As configurações do seu usuário foram atualizadas."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: ro\n"
|
"Language: ro\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-04-05 18:27\n"
|
"PO-Revision-Date: 2026-04-05 20:36\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Romanian\n"
|
"Language-Team: Romanian\n"
|
||||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n"
|
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n"
|
||||||
@@ -57,7 +57,7 @@ msgstr "1 oră"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr ""
|
msgstr "1 min"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 minute"
|
msgid "1 minute"
|
||||||
@@ -74,7 +74,7 @@ msgstr "12 ore"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "15 min"
|
msgid "15 min"
|
||||||
msgstr ""
|
msgstr "15 min"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "24 hours"
|
msgid "24 hours"
|
||||||
@@ -87,7 +87,7 @@ msgstr "30 zile"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "5 min"
|
msgid "5 min"
|
||||||
msgstr ""
|
msgstr "5 min"
|
||||||
|
|
||||||
#. Table column
|
#. Table column
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
@@ -137,7 +137,7 @@ msgstr ""
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr ""
|
msgstr "Admin"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "After"
|
msgid "After"
|
||||||
@@ -149,7 +149,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Agent"
|
msgid "Agent"
|
||||||
msgstr ""
|
msgstr "Agent"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
@@ -209,10 +209,19 @@ msgstr ""
|
|||||||
msgid "Average exceeds <0>{value}{0}</0>"
|
msgid "Average exceeds <0>{value}{0}</0>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
|
msgid "Average number of I/O operations waiting to be serviced"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
msgid "Average power consumption of GPUs"
|
msgid "Average power consumption of GPUs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
|
msgctxt "Disk I/O average operation time (iostat await)"
|
||||||
|
msgid "Average queue to completion time per operation"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/cpu-charts.tsx
|
#: src/components/routes/system/charts/cpu-charts.tsx
|
||||||
msgid "Average system-wide CPU utilization"
|
msgid "Average system-wide CPU utilization"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -327,7 +336,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr ""
|
msgstr "Celsius (°C)"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -444,6 +453,11 @@ msgctxt "Environment variables"
|
|||||||
msgid "Copy env"
|
msgid "Copy env"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
|
msgctxt "Copy alerts from another system"
|
||||||
|
msgid "Copy from"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Copy host"
|
msgid "Copy host"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -482,7 +496,7 @@ msgstr ""
|
|||||||
#: src/components/systemd-table/systemd-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 "CPU"
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -599,12 +613,11 @@ msgstr "Unitate disc"
|
|||||||
|
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
#: src/components/routes/system/charts/extra-fs-charts.tsx
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Disk Usage"
|
msgid "Disk Usage"
|
||||||
msgstr "Utilizare Disc"
|
msgstr "Utilizare Disc"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/extra-fs-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
msgid "Disk usage of {extraFsName}"
|
msgid "Disk usage of {extraFsName}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -664,7 +677,7 @@ msgstr "Editează {foo}"
|
|||||||
#: 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 ""
|
msgstr "Email"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Email notifications"
|
msgid "Email notifications"
|
||||||
@@ -841,7 +854,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Global"
|
msgid "Global"
|
||||||
msgstr ""
|
msgstr "Global"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
@@ -898,6 +911,21 @@ msgstr ""
|
|||||||
msgid "HTTP method: POST, GET, or HEAD (default: POST)"
|
msgid "HTTP method: POST, GET, or HEAD (default: POST)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
|
msgctxt "Disk I/O average operation time (iostat await)"
|
||||||
|
msgid "I/O Await"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
|
msgctxt "Disk I/O total time spent on read/write"
|
||||||
|
msgid "I/O Time"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
|
msgctxt "Percent of time the disk is busy with I/O"
|
||||||
|
msgid "I/O Utilization"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. Context: Battery state
|
#. Context: Battery state
|
||||||
#: src/lib/i18n.ts
|
#: src/lib/i18n.ts
|
||||||
msgid "Idle"
|
msgid "Idle"
|
||||||
@@ -1046,7 +1074,7 @@ msgstr ""
|
|||||||
#. Device model
|
#. Device model
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Model"
|
msgid "Model"
|
||||||
msgstr ""
|
msgstr "Model"
|
||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
@@ -1207,6 +1235,10 @@ msgstr ""
|
|||||||
msgid "Per-core average utilization"
|
msgid "Per-core average utilization"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
|
msgid "Percent of time the disk is busy with I/O"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "Percentage of time spent in each state"
|
msgid "Percentage of time spent in each state"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1254,7 +1286,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr ""
|
msgstr "Port"
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Container ports"
|
msgctxt "Container ports"
|
||||||
@@ -1284,13 +1316,20 @@ msgstr ""
|
|||||||
msgid "Public Key"
|
msgid "Public Key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
|
msgctxt "Disk I/O average queue depth"
|
||||||
|
msgid "Queue Depth"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Quiet Hours"
|
msgid "Quiet Hours"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. Disk read
|
#. Disk read
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
#: src/components/routes/system/charts/extra-fs-charts.tsx
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
msgid "Read"
|
msgid "Read"
|
||||||
msgstr "Citit"
|
msgstr "Citit"
|
||||||
|
|
||||||
@@ -1602,7 +1641,7 @@ msgstr ""
|
|||||||
msgid "This will permanently delete all selected records from the database."
|
msgid "This will permanently delete all selected records from the database."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/extra-fs-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
msgid "Throughput of {extraFsName}"
|
msgid "Throughput of {extraFsName}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1645,7 +1684,7 @@ msgstr ""
|
|||||||
#: src/components/ui/chart.tsx
|
#: src/components/ui/chart.tsx
|
||||||
#: src/components/ui/chart.tsx
|
#: src/components/ui/chart.tsx
|
||||||
msgid "Total"
|
msgid "Total"
|
||||||
msgstr ""
|
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"
|
||||||
@@ -1655,10 +1694,15 @@ msgstr ""
|
|||||||
msgid "Total data sent for each interface"
|
msgid "Total data sent for each interface"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
|
msgctxt "Disk I/O"
|
||||||
|
msgid "Total time spent on read/write (can exceed 100%)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. placeholder {0}: data.length
|
#. placeholder {0}: data.length
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Total: {0}"
|
msgid "Total: {0}"
|
||||||
msgstr ""
|
msgstr "Total: {0}"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Triggered by"
|
msgid "Triggered by"
|
||||||
@@ -1775,7 +1819,7 @@ msgstr ""
|
|||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/extra-fs-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
@@ -1797,6 +1841,11 @@ msgstr "Utilizat"
|
|||||||
msgid "Users"
|
msgid "Users"
|
||||||
msgstr "Utilizatori"
|
msgstr "Utilizatori"
|
||||||
|
|
||||||
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
|
msgctxt "Disk I/O utilization"
|
||||||
|
msgid "Utilization"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Value"
|
msgid "Value"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1806,6 +1855,7 @@ msgid "View"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
#: src/components/routes/system/network-sheet.tsx
|
#: src/components/routes/system/network-sheet.tsx
|
||||||
msgid "View more"
|
msgid "View more"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1858,7 +1908,9 @@ msgstr ""
|
|||||||
|
|
||||||
#. Disk write
|
#. Disk write
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
#: src/components/routes/system/charts/extra-fs-charts.tsx
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
msgid "Write"
|
msgid "Write"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: ru\n"
|
"Language: ru\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-03-27 22:12\n"
|
"PO-Revision-Date: 2026-04-05 20:37\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Russian\n"
|
"Language-Team: Russian\n"
|
||||||
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
|
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
|
||||||
@@ -858,7 +858,7 @@ msgstr "Глобально"
|
|||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
msgstr ""
|
msgstr "GPU"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
msgid "GPU Engines"
|
msgid "GPU Engines"
|
||||||
@@ -883,7 +883,7 @@ msgstr "Здоровье"
|
|||||||
|
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Heartbeat"
|
msgid "Heartbeat"
|
||||||
msgstr ""
|
msgstr "Heartbeat"
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Heartbeat Monitoring"
|
msgid "Heartbeat Monitoring"
|
||||||
@@ -1931,3 +1931,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 "Ваши настройки пользователя были обновлены."
|
msgstr "Ваши настройки пользователя были обновлены."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: sl\n"
|
"Language: sl\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-04-05 18:27\n"
|
"PO-Revision-Date: 2026-04-05 20:37\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Slovenian\n"
|
"Language-Team: Slovenian\n"
|
||||||
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n"
|
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n"
|
||||||
@@ -57,7 +57,7 @@ msgstr "1 ura"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr ""
|
msgstr "1 min"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 minute"
|
msgid "1 minute"
|
||||||
@@ -74,7 +74,7 @@ msgstr "12 ur"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "15 min"
|
msgid "15 min"
|
||||||
msgstr ""
|
msgstr "15 min"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "24 hours"
|
msgid "24 hours"
|
||||||
@@ -87,7 +87,7 @@ msgstr "30 dni"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "5 min"
|
msgid "5 min"
|
||||||
msgstr ""
|
msgstr "5 min"
|
||||||
|
|
||||||
#. Table column
|
#. Table column
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
@@ -1598,7 +1598,7 @@ msgstr "Naloge"
|
|||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Temp"
|
msgid "Temp"
|
||||||
msgstr ""
|
msgstr "Temp"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
@@ -1931,3 +1931,4 @@ 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 uporabniške nastavitve so posodobljene."
|
msgstr "Vaše uporabniške nastavitve so posodobljene."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: sr\n"
|
"Language: sr\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-04-05 18:27\n"
|
"PO-Revision-Date: 2026-04-05 20:37\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Serbian (Cyrillic)\n"
|
"Language-Team: Serbian (Cyrillic)\n"
|
||||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
"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"
|
||||||
@@ -1384,7 +1384,7 @@ msgstr "Настави"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgctxt "Root disk label"
|
msgctxt "Root disk label"
|
||||||
msgid "Root"
|
msgid "Root"
|
||||||
msgstr ""
|
msgstr "Root"
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Rotate token"
|
msgid "Rotate token"
|
||||||
@@ -1931,3 +1931,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 "Ваша корисничка подешавања су ажурирана."
|
msgstr "Ваша корисничка подешавања су ажурирана."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: sv\n"
|
"Language: sv\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-04-05 18:27\n"
|
"PO-Revision-Date: 2026-04-05 20:37\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Swedish\n"
|
"Language-Team: Swedish\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@@ -57,7 +57,7 @@ msgstr "1 timme"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr ""
|
msgstr "1 min"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 minute"
|
msgid "1 minute"
|
||||||
@@ -74,7 +74,7 @@ msgstr "12 timmar"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "15 min"
|
msgid "15 min"
|
||||||
msgstr ""
|
msgstr "15 min"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "24 hours"
|
msgid "24 hours"
|
||||||
@@ -87,7 +87,7 @@ msgstr "30 dagar"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "5 min"
|
msgid "5 min"
|
||||||
msgstr ""
|
msgstr "5 min"
|
||||||
|
|
||||||
#. Table column
|
#. Table column
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
@@ -137,7 +137,7 @@ msgstr "Justera bredden på huvudlayouten"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr ""
|
msgstr "Admin"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "After"
|
msgid "After"
|
||||||
@@ -149,7 +149,7 @@ msgstr "Efter att du har ställt in miljövariablerna, starta om din Beszel-hubb
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Agent"
|
msgid "Agent"
|
||||||
msgstr ""
|
msgstr "Agent"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
@@ -336,7 +336,7 @@ msgstr "Varning - potentiell dataförlust"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr ""
|
msgstr "Celsius (°C)"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -496,7 +496,7 @@ msgstr "Kärna"
|
|||||||
#: src/components/systemd-table/systemd-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 "CPU"
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -601,7 +601,7 @@ msgstr "Urladdar"
|
|||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Disk"
|
msgid "Disk"
|
||||||
msgstr ""
|
msgstr "Disk"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
msgid "Disk I/O"
|
msgid "Disk I/O"
|
||||||
@@ -772,7 +772,7 @@ msgstr "Exportera din nuvarande systemkonfiguration."
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr ""
|
msgstr "Fahrenheit (°F)"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
@@ -844,7 +844,7 @@ msgstr "FreeBSD kommando"
|
|||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
#: src/lib/i18n.ts
|
#: src/lib/i18n.ts
|
||||||
msgid "Full"
|
msgid "Full"
|
||||||
msgstr ""
|
msgstr "Full"
|
||||||
|
|
||||||
#. Context: General settings
|
#. Context: General settings
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
@@ -854,7 +854,7 @@ msgstr "Allmänt"
|
|||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Global"
|
msgid "Global"
|
||||||
msgstr ""
|
msgstr "Global"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
@@ -959,7 +959,7 @@ msgstr "Språk"
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Layout"
|
msgid "Layout"
|
||||||
msgstr ""
|
msgstr "Layout"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Layout width"
|
msgid "Layout width"
|
||||||
@@ -1043,7 +1043,7 @@ msgstr "Manuella installationsinstruktioner"
|
|||||||
#. Chart select field. Please try to keep this short.
|
#. Chart select field. Please try to keep this short.
|
||||||
#: src/components/routes/system/chart-card.tsx
|
#: src/components/routes/system/chart-card.tsx
|
||||||
msgid "Max 1 min"
|
msgid "Max 1 min"
|
||||||
msgstr ""
|
msgstr "Max 1 min"
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
@@ -1286,7 +1286,7 @@ msgstr "Vänligen logga in på ditt konto"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr ""
|
msgstr "Port"
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Container ports"
|
msgctxt "Container ports"
|
||||||
@@ -1536,7 +1536,7 @@ msgstr "Tillstånd"
|
|||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr ""
|
msgstr "Status"
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
msgid "Sub State"
|
msgid "Sub State"
|
||||||
@@ -1563,7 +1563,7 @@ msgstr "Swap-användning"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "System"
|
msgid "System"
|
||||||
msgstr ""
|
msgstr "System"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "System load averages over time"
|
msgid "System load averages over time"
|
||||||
@@ -1598,7 +1598,7 @@ msgstr "Uppgifter"
|
|||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Temp"
|
msgid "Temp"
|
||||||
msgstr ""
|
msgstr "Temp"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
@@ -1684,7 +1684,7 @@ msgstr "Tokens och fingeravtryck används för att autentisera WebSocket-anslutn
|
|||||||
#: src/components/ui/chart.tsx
|
#: src/components/ui/chart.tsx
|
||||||
#: src/components/ui/chart.tsx
|
#: src/components/ui/chart.tsx
|
||||||
msgid "Total"
|
msgid "Total"
|
||||||
msgstr ""
|
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"
|
||||||
@@ -1931,3 +1931,4 @@ 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 "Dina användarinställningar har uppdaterats."
|
msgstr "Dina användarinställningar har uppdaterats."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: th\n"
|
"Language: th\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-04-05 18:28\n"
|
"PO-Revision-Date: 2026-04-05 20:37\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Thai\n"
|
"Language-Team: Thai\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
@@ -209,10 +209,19 @@ msgstr ""
|
|||||||
msgid "Average exceeds <0>{value}{0}</0>"
|
msgid "Average exceeds <0>{value}{0}</0>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
|
msgid "Average number of I/O operations waiting to be serviced"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
msgid "Average power consumption of GPUs"
|
msgid "Average power consumption of GPUs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
|
msgctxt "Disk I/O average operation time (iostat await)"
|
||||||
|
msgid "Average queue to completion time per operation"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/cpu-charts.tsx
|
#: src/components/routes/system/charts/cpu-charts.tsx
|
||||||
msgid "Average system-wide CPU utilization"
|
msgid "Average system-wide CPU utilization"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -444,6 +453,11 @@ msgctxt "Environment variables"
|
|||||||
msgid "Copy env"
|
msgid "Copy env"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
|
msgctxt "Copy alerts from another system"
|
||||||
|
msgid "Copy from"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Copy host"
|
msgid "Copy host"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -599,12 +613,11 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
#: src/components/routes/system/charts/extra-fs-charts.tsx
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Disk Usage"
|
msgid "Disk Usage"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/extra-fs-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
msgid "Disk usage of {extraFsName}"
|
msgid "Disk usage of {extraFsName}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -898,6 +911,21 @@ msgstr ""
|
|||||||
msgid "HTTP method: POST, GET, or HEAD (default: POST)"
|
msgid "HTTP method: POST, GET, or HEAD (default: POST)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
|
msgctxt "Disk I/O average operation time (iostat await)"
|
||||||
|
msgid "I/O Await"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
|
msgctxt "Disk I/O total time spent on read/write"
|
||||||
|
msgid "I/O Time"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
|
msgctxt "Percent of time the disk is busy with I/O"
|
||||||
|
msgid "I/O Utilization"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. Context: Battery state
|
#. Context: Battery state
|
||||||
#: src/lib/i18n.ts
|
#: src/lib/i18n.ts
|
||||||
msgid "Idle"
|
msgid "Idle"
|
||||||
@@ -1207,6 +1235,10 @@ msgstr ""
|
|||||||
msgid "Per-core average utilization"
|
msgid "Per-core average utilization"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
|
msgid "Percent of time the disk is busy with I/O"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "Percentage of time spent in each state"
|
msgid "Percentage of time spent in each state"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1284,13 +1316,20 @@ msgstr ""
|
|||||||
msgid "Public Key"
|
msgid "Public Key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
|
msgctxt "Disk I/O average queue depth"
|
||||||
|
msgid "Queue Depth"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Quiet Hours"
|
msgid "Quiet Hours"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. Disk read
|
#. Disk read
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
#: src/components/routes/system/charts/extra-fs-charts.tsx
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
msgid "Read"
|
msgid "Read"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1602,7 +1641,7 @@ msgstr ""
|
|||||||
msgid "This will permanently delete all selected records from the database."
|
msgid "This will permanently delete all selected records from the database."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/extra-fs-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
msgid "Throughput of {extraFsName}"
|
msgid "Throughput of {extraFsName}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1655,6 +1694,11 @@ msgstr ""
|
|||||||
msgid "Total data sent for each interface"
|
msgid "Total data sent for each interface"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
|
msgctxt "Disk I/O"
|
||||||
|
msgid "Total time spent on read/write (can exceed 100%)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. placeholder {0}: data.length
|
#. placeholder {0}: data.length
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Total: {0}"
|
msgid "Total: {0}"
|
||||||
@@ -1775,7 +1819,7 @@ msgstr ""
|
|||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/extra-fs-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
@@ -1797,6 +1841,11 @@ msgstr ""
|
|||||||
msgid "Users"
|
msgid "Users"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
|
msgctxt "Disk I/O utilization"
|
||||||
|
msgid "Utilization"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Value"
|
msgid "Value"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1806,6 +1855,7 @@ msgid "View"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
#: src/components/routes/system/network-sheet.tsx
|
#: src/components/routes/system/network-sheet.tsx
|
||||||
msgid "View more"
|
msgid "View more"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1858,7 +1908,9 @@ msgstr ""
|
|||||||
|
|
||||||
#. Disk write
|
#. Disk write
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
#: src/components/routes/system/charts/extra-fs-charts.tsx
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
|
#: src/components/routes/system/disk-io-sheet.tsx
|
||||||
msgid "Write"
|
msgid "Write"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: tr\n"
|
"Language: tr\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-04-05 18:27\n"
|
"PO-Revision-Date: 2026-04-05 20:37\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Turkish\n"
|
"Language-Team: Turkish\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@@ -1286,7 +1286,7 @@ msgstr "Lütfen hesabınıza giriş yapın"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr ""
|
msgstr "Port"
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Container ports"
|
msgctxt "Container ports"
|
||||||
@@ -1931,3 +1931,4 @@ msgstr "Evet"
|
|||||||
#: 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 "Kullanıcı ayarlarınız güncellendi."
|
msgstr "Kullanıcı ayarlarınız güncellendi."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: uk\n"
|
"Language: uk\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-04-05 18:27\n"
|
"PO-Revision-Date: 2026-04-05 20:37\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Ukrainian\n"
|
"Language-Team: Ukrainian\n"
|
||||||
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
|
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
|
||||||
@@ -1931,3 +1931,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 "Ваші налаштування користувача були оновлені."
|
msgstr "Ваші налаштування користувача були оновлені."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: vi\n"
|
"Language: vi\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-04-05 18:28\n"
|
"PO-Revision-Date: 2026-04-05 20:37\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Vietnamese\n"
|
"Language-Team: Vietnamese\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
@@ -1931,3 +1931,4 @@ msgstr "Có"
|
|||||||
#: 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 "Cài đặt người dùng của bạn đã được cập nhật."
|
msgstr "Cài đặt người dùng của bạn đã được cập nhật."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: zh\n"
|
"Language: zh\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-04-05 18:27\n"
|
"PO-Revision-Date: 2026-04-05 20:37\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Chinese Simplified\n"
|
"Language-Team: Chinese Simplified\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
@@ -496,7 +496,7 @@ msgstr "核心"
|
|||||||
#: src/components/systemd-table/systemd-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 "CPU"
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -1931,3 +1931,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 "您的用户设置已更新。"
|
msgstr "您的用户设置已更新。"
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: zh\n"
|
"Language: zh\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-04-05 18:28\n"
|
"PO-Revision-Date: 2026-04-05 20:37\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Chinese Traditional, Hong Kong\n"
|
"Language-Team: Chinese Traditional, Hong Kong\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
@@ -1931,3 +1931,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 "您的用戶設置已更新。"
|
msgstr "您的用戶設置已更新。"
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: zh\n"
|
"Language: zh\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-03-28 02:52\n"
|
"PO-Revision-Date: 2026-04-05 20:37\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Chinese Traditional\n"
|
"Language-Team: Chinese Traditional\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
@@ -149,7 +149,7 @@ msgstr "設定環境變數後,請重新啟動 Beszel Hub 以使變更生效。
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Agent"
|
msgid "Agent"
|
||||||
msgstr ""
|
msgstr "Agent"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
@@ -496,7 +496,7 @@ msgstr "核心指標"
|
|||||||
#: src/components/systemd-table/systemd-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 "CPU"
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -695,7 +695,7 @@ msgstr "結束時間"
|
|||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Endpoint URL"
|
msgid "Endpoint URL"
|
||||||
msgstr ""
|
msgstr "Endpoint URL"
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Endpoint URL to ping (required)"
|
msgid "Endpoint URL to ping (required)"
|
||||||
@@ -820,7 +820,7 @@ msgstr "篩選..."
|
|||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Fingerprint"
|
msgid "Fingerprint"
|
||||||
msgstr ""
|
msgstr "Fingerprint"
|
||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Firmware"
|
msgid "Firmware"
|
||||||
@@ -858,7 +858,7 @@ msgstr "全域"
|
|||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
msgstr ""
|
msgstr "GPU"
|
||||||
|
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
msgid "GPU Engines"
|
msgid "GPU Engines"
|
||||||
@@ -883,7 +883,7 @@ msgstr "健康狀態"
|
|||||||
|
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Heartbeat"
|
msgid "Heartbeat"
|
||||||
msgstr ""
|
msgstr "Heartbeat"
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Heartbeat Monitoring"
|
msgid "Heartbeat Monitoring"
|
||||||
@@ -901,7 +901,7 @@ msgstr "Homebrew 指令"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Host / IP"
|
msgid "Host / IP"
|
||||||
msgstr ""
|
msgstr "Host / IP"
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "HTTP Method"
|
msgid "HTTP Method"
|
||||||
@@ -1286,7 +1286,7 @@ msgstr "請登入您的帳號"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr ""
|
msgstr "Port"
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Container ports"
|
msgctxt "Container ports"
|
||||||
@@ -1384,7 +1384,7 @@ msgstr "繼續"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgctxt "Root disk label"
|
msgctxt "Root disk label"
|
||||||
msgid "Root"
|
msgid "Root"
|
||||||
msgstr ""
|
msgstr "Root"
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Rotate token"
|
msgid "Rotate token"
|
||||||
@@ -1665,7 +1665,7 @@ msgstr "切換主題"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr ""
|
msgstr "Token"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -1931,3 +1931,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 "已更新您的使用者設定"
|
msgstr "已更新您的使用者設定"
|
||||||
|
|
||||||
|
|||||||
232
internal/site/src/types.d.ts
vendored
232
internal/site/src/types.d.ts
vendored
@@ -124,10 +124,6 @@ export interface SystemStats {
|
|||||||
dio?: [number, number]
|
dio?: [number, number]
|
||||||
/** max disk I/O bytes [read, write] */
|
/** max disk I/O bytes [read, write] */
|
||||||
diom?: [number, number]
|
diom?: [number, number]
|
||||||
/** disk io stats [read time factor, write time factor, io utilization %, r_await ms, w_await ms, weighted io %] */
|
|
||||||
dios?: [number, number, number, number, number, number]
|
|
||||||
/** max disk io stats */
|
|
||||||
diosm?: [number, number, number, number, number, number]
|
|
||||||
/** network sent (mb) */
|
/** network sent (mb) */
|
||||||
ns: number
|
ns: number
|
||||||
/** network received (mb) */
|
/** network received (mb) */
|
||||||
@@ -190,10 +186,6 @@ export interface ExtraFsStats {
|
|||||||
rbm: number
|
rbm: number
|
||||||
/** max write per second (mb) */
|
/** max write per second (mb) */
|
||||||
wbm: number
|
wbm: number
|
||||||
/** disk io stats [read time factor, write time factor, io utilization %, r_await ms, w_await ms, weighted io %] */
|
|
||||||
dios?: [number, number, number, number, number, number]
|
|
||||||
/** max disk io stats */
|
|
||||||
diosm?: [number, number, number, number, number, number]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ContainerStatsRecord extends RecordModel {
|
export interface ContainerStatsRecord extends RecordModel {
|
||||||
@@ -421,118 +413,118 @@ export interface SystemdRecord extends RecordModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SystemdServiceDetails {
|
export interface SystemdServiceDetails {
|
||||||
AccessSELinuxContext: string
|
AccessSELinuxContext: string;
|
||||||
ActivationDetails: any[]
|
ActivationDetails: any[];
|
||||||
ActiveEnterTimestamp: number
|
ActiveEnterTimestamp: number;
|
||||||
ActiveEnterTimestampMonotonic: number
|
ActiveEnterTimestampMonotonic: number;
|
||||||
ActiveExitTimestamp: number
|
ActiveExitTimestamp: number;
|
||||||
ActiveExitTimestampMonotonic: number
|
ActiveExitTimestampMonotonic: number;
|
||||||
ActiveState: string
|
ActiveState: string;
|
||||||
After: string[]
|
After: string[];
|
||||||
AllowIsolate: boolean
|
AllowIsolate: boolean;
|
||||||
AssertResult: boolean
|
AssertResult: boolean;
|
||||||
AssertTimestamp: number
|
AssertTimestamp: number;
|
||||||
AssertTimestampMonotonic: number
|
AssertTimestampMonotonic: number;
|
||||||
Asserts: any[]
|
Asserts: any[];
|
||||||
Before: string[]
|
Before: string[];
|
||||||
BindsTo: any[]
|
BindsTo: any[];
|
||||||
BoundBy: any[]
|
BoundBy: any[];
|
||||||
CPUUsageNSec: number
|
CPUUsageNSec: number;
|
||||||
CanClean: any[]
|
CanClean: any[];
|
||||||
CanFreeze: boolean
|
CanFreeze: boolean;
|
||||||
CanIsolate: boolean
|
CanIsolate: boolean;
|
||||||
CanLiveMount: boolean
|
CanLiveMount: boolean;
|
||||||
CanReload: boolean
|
CanReload: boolean;
|
||||||
CanStart: boolean
|
CanStart: boolean;
|
||||||
CanStop: boolean
|
CanStop: boolean;
|
||||||
CollectMode: string
|
CollectMode: string;
|
||||||
ConditionResult: boolean
|
ConditionResult: boolean;
|
||||||
ConditionTimestamp: number
|
ConditionTimestamp: number;
|
||||||
ConditionTimestampMonotonic: number
|
ConditionTimestampMonotonic: number;
|
||||||
Conditions: any[]
|
Conditions: any[];
|
||||||
ConflictedBy: any[]
|
ConflictedBy: any[];
|
||||||
Conflicts: string[]
|
Conflicts: string[];
|
||||||
ConsistsOf: any[]
|
ConsistsOf: any[];
|
||||||
DebugInvocation: boolean
|
DebugInvocation: boolean;
|
||||||
DefaultDependencies: boolean
|
DefaultDependencies: boolean;
|
||||||
Description: string
|
Description: string;
|
||||||
Documentation: string[]
|
Documentation: string[];
|
||||||
DropInPaths: any[]
|
DropInPaths: any[];
|
||||||
ExecMainPID: number
|
ExecMainPID: number;
|
||||||
FailureAction: string
|
FailureAction: string;
|
||||||
FailureActionExitStatus: number
|
FailureActionExitStatus: number;
|
||||||
Following: string
|
Following: string;
|
||||||
FragmentPath: string
|
FragmentPath: string;
|
||||||
FreezerState: string
|
FreezerState: string;
|
||||||
Id: string
|
Id: string;
|
||||||
IgnoreOnIsolate: boolean
|
IgnoreOnIsolate: boolean;
|
||||||
InactiveEnterTimestamp: number
|
InactiveEnterTimestamp: number;
|
||||||
InactiveEnterTimestampMonotonic: number
|
InactiveEnterTimestampMonotonic: number;
|
||||||
InactiveExitTimestamp: number
|
InactiveExitTimestamp: number;
|
||||||
InactiveExitTimestampMonotonic: number
|
InactiveExitTimestampMonotonic: number;
|
||||||
InvocationID: string
|
InvocationID: string;
|
||||||
Job: Array<number | string>
|
Job: Array<number | string>;
|
||||||
JobRunningTimeoutUSec: number
|
JobRunningTimeoutUSec: number;
|
||||||
JobTimeoutAction: string
|
JobTimeoutAction: string;
|
||||||
JobTimeoutRebootArgument: string
|
JobTimeoutRebootArgument: string;
|
||||||
JobTimeoutUSec: number
|
JobTimeoutUSec: number;
|
||||||
JoinsNamespaceOf: any[]
|
JoinsNamespaceOf: any[];
|
||||||
LoadError: string[]
|
LoadError: string[];
|
||||||
LoadState: string
|
LoadState: string;
|
||||||
MainPID: number
|
MainPID: number;
|
||||||
Markers: any[]
|
Markers: any[];
|
||||||
MemoryCurrent: number
|
MemoryCurrent: number;
|
||||||
MemoryLimit: number
|
MemoryLimit: number;
|
||||||
MemoryPeak: number
|
MemoryPeak: number;
|
||||||
NRestarts: number
|
NRestarts: number;
|
||||||
Names: string[]
|
Names: string[];
|
||||||
NeedDaemonReload: boolean
|
NeedDaemonReload: boolean;
|
||||||
OnFailure: any[]
|
OnFailure: any[];
|
||||||
OnFailureJobMode: string
|
OnFailureJobMode: string;
|
||||||
OnFailureOf: any[]
|
OnFailureOf: any[];
|
||||||
OnSuccess: any[]
|
OnSuccess: any[];
|
||||||
OnSuccessJobMode: string
|
OnSuccessJobMode: string;
|
||||||
OnSuccessOf: any[]
|
OnSuccessOf: any[];
|
||||||
PartOf: any[]
|
PartOf: any[];
|
||||||
Perpetual: boolean
|
Perpetual: boolean;
|
||||||
PropagatesReloadTo: any[]
|
PropagatesReloadTo: any[];
|
||||||
PropagatesStopTo: any[]
|
PropagatesStopTo: any[];
|
||||||
RebootArgument: string
|
RebootArgument: string;
|
||||||
Refs: any[]
|
Refs: any[];
|
||||||
RefuseManualStart: boolean
|
RefuseManualStart: boolean;
|
||||||
RefuseManualStop: boolean
|
RefuseManualStop: boolean;
|
||||||
ReloadPropagatedFrom: any[]
|
ReloadPropagatedFrom: any[];
|
||||||
RequiredBy: any[]
|
RequiredBy: any[];
|
||||||
Requires: string[]
|
Requires: string[];
|
||||||
RequiresMountsFor: any[]
|
RequiresMountsFor: any[];
|
||||||
Requisite: any[]
|
Requisite: any[];
|
||||||
RequisiteOf: any[]
|
RequisiteOf: any[];
|
||||||
Result: string
|
Result: string;
|
||||||
SliceOf: any[]
|
SliceOf: any[];
|
||||||
SourcePath: string
|
SourcePath: string;
|
||||||
StartLimitAction: string
|
StartLimitAction: string;
|
||||||
StartLimitBurst: number
|
StartLimitBurst: number;
|
||||||
StartLimitIntervalUSec: number
|
StartLimitIntervalUSec: number;
|
||||||
StateChangeTimestamp: number
|
StateChangeTimestamp: number;
|
||||||
StateChangeTimestampMonotonic: number
|
StateChangeTimestampMonotonic: number;
|
||||||
StopPropagatedFrom: any[]
|
StopPropagatedFrom: any[];
|
||||||
StopWhenUnneeded: boolean
|
StopWhenUnneeded: boolean;
|
||||||
SubState: string
|
SubState: string;
|
||||||
SuccessAction: string
|
SuccessAction: string;
|
||||||
SuccessActionExitStatus: number
|
SuccessActionExitStatus: number;
|
||||||
SurviveFinalKillSignal: boolean
|
SurviveFinalKillSignal: boolean;
|
||||||
TasksCurrent: number
|
TasksCurrent: number;
|
||||||
TasksMax: number
|
TasksMax: number;
|
||||||
Transient: boolean
|
Transient: boolean;
|
||||||
TriggeredBy: string[]
|
TriggeredBy: string[];
|
||||||
Triggers: any[]
|
Triggers: any[];
|
||||||
UnitFilePreset: string
|
UnitFilePreset: string;
|
||||||
UnitFileState: string
|
UnitFileState: string;
|
||||||
UpheldBy: any[]
|
UpheldBy: any[];
|
||||||
Upholds: any[]
|
Upholds: any[];
|
||||||
WantedBy: any[]
|
WantedBy: any[];
|
||||||
Wants: string[]
|
Wants: string[];
|
||||||
WantsMountsFor: any[]
|
WantsMountsFor: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BeszelInfo {
|
export interface BeszelInfo {
|
||||||
|
|||||||
@@ -77,16 +77,6 @@ func CreateUser(app core.App, email string, password string) (*core.Record, erro
|
|||||||
return user, app.Save(user)
|
return user, app.Save(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to create a test superuser for config tests
|
|
||||||
func CreateSuperuser(app core.App, email string, password string) (*core.Record, error) {
|
|
||||||
superusersCollection, _ := app.FindCachedCollectionByNameOrId(core.CollectionNameSuperusers)
|
|
||||||
superuser := core.NewRecord(superusersCollection)
|
|
||||||
superuser.Set("email", email)
|
|
||||||
superuser.Set("password", password)
|
|
||||||
|
|
||||||
return superuser, app.Save(superuser)
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateUserWithRole(app core.App, email string, password string, roleName string) (*core.Record, error) {
|
func CreateUserWithRole(app core.App, email string, password string, roleName string) (*core.Record, error) {
|
||||||
user, err := CreateUser(app, email, password)
|
user, err := CreateUser(app, email, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,45 +1,3 @@
|
|||||||
## 0.18.7
|
|
||||||
|
|
||||||
- Add more disk I/O metrics (utilization, read/write time, await, queue depth) (#1866)
|
|
||||||
|
|
||||||
- Add ability to copy alerts between systems (#1853)
|
|
||||||
|
|
||||||
- Add `SENSORS_TIMEOUT` environment variable (#1871)
|
|
||||||
|
|
||||||
- Replace `distatus/battery` with an internal implementation (#1872)
|
|
||||||
|
|
||||||
- Restrict universal token API to non-superuser accounts (#1870)
|
|
||||||
|
|
||||||
- Fix macOS ARM64 crashes by upgrading `gopsutil` to v4.26.3 (#1881, #796)
|
|
||||||
|
|
||||||
- Fix text size for system names in grid view (#1860)
|
|
||||||
|
|
||||||
- Fix NVMe capacity reporting for Apple SSDs (#1873)
|
|
||||||
|
|
||||||
- Fix Windows root disk detection when the executable is not on the root disk (#1863)
|
|
||||||
|
|
||||||
- Fix nested virtual filesystem inclusion in Docker when mounting host root (#1859)
|
|
||||||
|
|
||||||
- Fix OPNsense installation persistence by using the daemon user (#1880)
|
|
||||||
|
|
||||||
- Upgrade JS dependencies with dependabot security alerts (#1882)
|
|
||||||
|
|
||||||
- Upgrade PocketBase to latest version
|
|
||||||
|
|
||||||
## 0.18.6
|
|
||||||
|
|
||||||
- Add apple-touch-icon link to index.html (#1850)
|
|
||||||
|
|
||||||
- Fix UI bug where charts did not display 1m max until next update
|
|
||||||
|
|
||||||
- Fix regression in partition discovery on Docker (#1847)
|
|
||||||
|
|
||||||
- Fix agent detection of Podman when using socket proxy (#1846)
|
|
||||||
|
|
||||||
- Fix NVML GPU collection being disabled when `nvidia-smi` is not in PATH (#1849)
|
|
||||||
|
|
||||||
- Reset SMART interval on agent reconnect if the agent hasn't collected SMART data, allowing config changes to take effect immediately
|
|
||||||
|
|
||||||
## 0.18.5
|
## 0.18.5
|
||||||
|
|
||||||
- Add "update available" notification in hub web UI with `CHECK_UPDATES=true` (#1830)
|
- Add "update available" notification in hub web UI with `CHECK_UPDATES=true` (#1830)
|
||||||
|
|||||||
@@ -12,10 +12,6 @@ is_freebsd() {
|
|||||||
[ "$(uname -s)" = "FreeBSD" ]
|
[ "$(uname -s)" = "FreeBSD" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
is_opnsense() {
|
|
||||||
[ -f /usr/local/sbin/opnsense-version ] || [ -f /usr/local/etc/opnsense-version ] || [ -f /etc/opnsense-release ]
|
|
||||||
}
|
|
||||||
|
|
||||||
is_glibc() {
|
is_glibc() {
|
||||||
# Prefer glibc-enabled agent (NVML via purego) on linux/amd64 glibc systems.
|
# Prefer glibc-enabled agent (NVML via purego) on linux/amd64 glibc systems.
|
||||||
# Check common dynamic loader paths first (fast + reliable).
|
# Check common dynamic loader paths first (fast + reliable).
|
||||||
@@ -553,7 +549,6 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Create a dedicated user for the service if it doesn't exist
|
# Create a dedicated user for the service if it doesn't exist
|
||||||
AGENT_USER="beszel"
|
|
||||||
echo "Configuring the dedicated user for the Beszel Agent service..."
|
echo "Configuring the dedicated user for the Beszel Agent service..."
|
||||||
if is_alpine; then
|
if is_alpine; then
|
||||||
if ! id -u beszel >/dev/null 2>&1; then
|
if ! id -u beszel >/dev/null 2>&1; then
|
||||||
@@ -595,10 +590,6 @@ elif is_openwrt; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
elif is_freebsd; then
|
elif is_freebsd; then
|
||||||
if is_opnsense; then
|
|
||||||
echo "OPNsense detected: skipping user creation (using daemon user instead)"
|
|
||||||
AGENT_USER="daemon"
|
|
||||||
else
|
|
||||||
if ! id -u beszel >/dev/null 2>&1; then
|
if ! id -u beszel >/dev/null 2>&1; then
|
||||||
pw user add beszel -d /nonexistent -s /usr/sbin/nologin -c "beszel user"
|
pw user add beszel -d /nonexistent -s /usr/sbin/nologin -c "beszel user"
|
||||||
fi
|
fi
|
||||||
@@ -607,7 +598,6 @@ elif is_freebsd; then
|
|||||||
echo "Adding beszel to wheel group for self-updates"
|
echo "Adding beszel to wheel group for self-updates"
|
||||||
pw group mod wheel -m beszel
|
pw group mod wheel -m beszel
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
|
|
||||||
else
|
else
|
||||||
if ! id -u beszel >/dev/null 2>&1; then
|
if ! id -u beszel >/dev/null 2>&1; then
|
||||||
@@ -630,7 +620,7 @@ fi
|
|||||||
if [ ! -d "$AGENT_DIR" ]; then
|
if [ ! -d "$AGENT_DIR" ]; then
|
||||||
echo "Creating the directory for the Beszel Agent..."
|
echo "Creating the directory for the Beszel Agent..."
|
||||||
mkdir -p "$AGENT_DIR"
|
mkdir -p "$AGENT_DIR"
|
||||||
chown "${AGENT_USER}:${AGENT_USER}" "$AGENT_DIR"
|
chown beszel:beszel "$AGENT_DIR"
|
||||||
chmod 755 "$AGENT_DIR"
|
chmod 755 "$AGENT_DIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -909,7 +899,7 @@ TOKEN=$TOKEN
|
|||||||
HUB_URL=$HUB_URL
|
HUB_URL=$HUB_URL
|
||||||
EOF
|
EOF
|
||||||
chmod 640 "$AGENT_DIR/env"
|
chmod 640 "$AGENT_DIR/env"
|
||||||
chown "root:${AGENT_USER}" "$AGENT_DIR/env"
|
chown root:beszel "$AGENT_DIR/env"
|
||||||
else
|
else
|
||||||
echo "FreeBSD environment file already exists. Skipping creation."
|
echo "FreeBSD environment file already exists. Skipping creation."
|
||||||
fi
|
fi
|
||||||
@@ -927,7 +917,6 @@ EOF
|
|||||||
# Enable and start the service
|
# Enable and start the service
|
||||||
echo "Enabling and starting the agent service..."
|
echo "Enabling and starting the agent service..."
|
||||||
sysrc beszel_agent_enable="YES"
|
sysrc beszel_agent_enable="YES"
|
||||||
sysrc beszel_agent_user="${AGENT_USER}"
|
|
||||||
service beszel-agent restart
|
service beszel-agent restart
|
||||||
|
|
||||||
# Check if service started successfully
|
# Check if service started successfully
|
||||||
|
|||||||
Reference in New Issue
Block a user