Compare commits

..

17 Commits

Author SHA1 Message Date
henrygd
2c69197d2d fix(install): improve handling of openwrt user account (#2370)
- Allocate unused UID/GIDs instead of using 999
- Detect existing ID collisions and repair missing shadow entries
- Support userdel and deluser during uninstall
- Fix Docker group membership handling
2026-09-20 18:04:07 -04:00
henrygd
97e6f64bdc fix(ui): swap usage chart shown twice in tabs view (#2356) 2026-09-19 13:27:00 -04:00
henrygd
4a5915b141 add SMART to readme features 2026-09-19 12:11:10 -04:00
henrygd
e68372dce4 update readme features 2026-09-19 12:09:26 -04:00
henrygd
c52f3acb94 fix(docker): enable Debian contrib for ZFS utilities in slim NVIDIA image 2026-09-19 12:03:05 -04:00
hank
c09eb8c6df chore(helm): update app version to 0.20.0 (#2355)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-09-19 11:39:13 -04:00
henrygd
a0dc19eacf fix(ui): sort containers alphabetically within systems 2026-09-19 11:18:17 -04:00
henrygd
912bc50874 docker: update alpine version and remove unnecessary GOGC 2026-09-19 00:19:02 -04:00
henrygd
c54dbfba7c test: wait for hub setup in universal token integration test 2026-09-19 00:14:19 -04:00
henrygd
dd3f7d58b5 test: use virtual time to eliminate MQTT notification timeout waits 2026-09-19 00:10:02 -04:00
henrygd
0509053a69 test: wait for hub system status in agent WebSocket integration test 2026-09-19 00:02:29 -04:00
henrygd
187dc886a9 fix: resync network monitors after SSH reconnect and retry failed syncs 2026-09-18 23:49:54 -04:00
henrygd
2784460621 fix(agent): avoid RAID health warnings during healthy scrubs (#2109) 2026-09-18 21:05:49 -04:00
henrygd
b0bc727411 update migration files for 0.20.0 2026-09-18 18:51:42 -04:00
henrygd
6937453282 chore: update changelog 2026-09-18 18:46:45 -04:00
henrygd
97de1471d7 fix: prevent variant Docker images from overwriting latest tags 2026-09-18 18:38:18 -04:00
hank
bd7e359dcd New Crowdin updates (#2352) 2026-09-18 18:28:28 -04:00
63 changed files with 1181 additions and 239 deletions

View File

@@ -29,6 +29,7 @@ jobs:
# henrygd/beszel-agent:alpine
- image: henrygd/beszel-agent
dockerfile: ./internal/dockerfile_agent_alpine
flavor: latest=false
registry: docker.io
username_secret: DOCKERHUB_USERNAME
password_secret: DOCKERHUB_TOKEN
@@ -55,6 +56,7 @@ jobs:
# henrygd/beszel-agent-nvidia:slim
- image: henrygd/beszel-agent-nvidia
dockerfile: ./internal/dockerfile_agent_nvidia_slim
flavor: latest=false
platforms: linux/amd64,linux/arm64
registry: docker.io
username_secret: DOCKERHUB_USERNAME
@@ -123,6 +125,7 @@ jobs:
# ghcr.io/henrygd/beszel-agent-nvidia:slim
- image: ghcr.io/${{ github.repository }}/beszel-agent-nvidia
dockerfile: ./internal/dockerfile_agent_nvidia_slim
flavor: latest=false
platforms: linux/amd64,linux/arm64
registry: ghcr.io
username: ${{ github.actor }}
@@ -150,6 +153,7 @@ jobs:
# ghcr.io/henrygd/beszel-agent:alpine
- image: ghcr.io/${{ github.repository }}/beszel-agent
dockerfile: ./internal/dockerfile_agent_alpine
flavor: latest=false
registry: ghcr.io
username: ${{ github.actor }}
password_secret: GITHUB_TOKEN
@@ -159,7 +163,7 @@ jobs:
type=semver,pattern={{major}}.{{minor}}-alpine
type=semver,pattern={{major}}-alpine
# henrygd/beszel-agent (keep at bottom so it gets built after :alpine and gets the latest tag)
# henrygd/beszel-agent
- image: henrygd/beszel-agent
dockerfile: ./internal/dockerfile_agent
registry: docker.io
@@ -200,6 +204,8 @@ jobs:
uses: docker/metadata-action@v6
with:
images: ${{ matrix.image }}
# Variant images must not overwrite the standard image's latest tag.
flavor: ${{ matrix.flavor || 'latest=auto' }}
tags: ${{ matrix.tags }}
# https://github.com/docker/login-action

View File

@@ -201,12 +201,9 @@ func mdraidSmartStatus(health mdraidHealth) string {
if health.mismatchCnt > 0 {
return "WARNING"
}
// "check" scans for consistency problems without repairing mismatches.
// With no mismatches, keep it green while reporting progress attributes.
switch syncAction {
case "repair":
return "WARNING"
}
// "check" and "repair" are requested consistency scans, not evidence of
// array failure. With no health issues above, keep scrubbing green while
// reporting the sync action and progress attributes.
switch state {
case "clean", "active", "active-idle", "write-pending", "read-auto", "readonly":
return "PASSED"

View File

@@ -174,8 +174,25 @@ func TestMdraidSmartStatus(t *testing.T) {
if got := mdraidSmartStatus(mdraidHealth{arrayState: "clean", mismatchCnt: 1}); got != "WARNING" {
t.Fatalf("mdraidSmartStatus(clean+mismatch) = %q, want WARNING", got)
}
if got := mdraidSmartStatus(mdraidHealth{arrayState: "clean", syncAction: "repair"}); got != "WARNING" {
t.Fatalf("mdraidSmartStatus(repair) = %q, want WARNING", got)
for _, tc := range []struct {
name string
health mdraidHealth
want string
}{
{"clean", mdraidHealth{arrayState: "clean"}, "PASSED"},
{"active", mdraidHealth{arrayState: "active"}, "PASSED"},
{"mismatch", mdraidHealth{arrayState: "active", mismatchCnt: 1}, "WARNING"},
{"degraded", mdraidHealth{arrayState: "active", degraded: 1}, "FAILED"},
{"faulty member", mdraidHealth{arrayState: "active", faultyDisks: 1}, "FAILED"},
{"inactive", mdraidHealth{arrayState: "inactive"}, "FAILED"},
{"unknown", mdraidHealth{arrayState: "unknown"}, "UNKNOWN"},
} {
t.Run("repair/"+tc.name, func(t *testing.T) {
tc.health.syncAction = "repair"
if got := mdraidSmartStatus(tc.health); got != tc.want {
t.Fatalf("mdraidSmartStatus(%+v) = %q, want %s", tc.health, got, tc.want)
}
})
}
if got := mdraidSmartStatus(mdraidHealth{arrayState: "clean"}); got != "PASSED" {
t.Fatalf("mdraidSmartStatus(clean) = %q, want PASSED", got)

View File

@@ -11,6 +11,7 @@ import (
"strings"
"sync/atomic"
"testing"
"testing/synctest"
beszelTests "github.com/henrygd/beszel/internal/tests"
pbTests "github.com/pocketbase/pocketbase/tests"
@@ -533,6 +534,20 @@ func TestSendTestNotification(t *testing.T) {
for _, url := range []string{localURL, "smtp://user:pass@127.0.0.1/?fromAddress=sender@example.com&toAddresses=recipient@example.com", "mqtt://127.0.0.1/topic"} {
scenarios = append(scenarios, beszelTests.ApiScenario{
BeforeTestFunc: func(tb testing.TB, _ *pbTests.TestApp, e *core.ServeEvent) {
if !strings.HasPrefix(url, "mqtt://") {
return
}
// Keep the real MQTT rejection path, but advance its library's
// fixed timeout using virtual time instead of waiting 10 seconds.
e.Router.BindFunc(func(re *core.RequestEvent) error {
var err error
synctest.Test(tb.(*testing.T), func(t *testing.T) {
err = re.Next()
})
return err
})
},
Name: "readonly cannot send to " + url,
Method: http.MethodPost,
URL: "/api/beszel/test-notification",

View File

@@ -11,6 +11,7 @@ import (
"strings"
"sync/atomic"
"testing"
"testing/synctest"
"github.com/nicholas-fedor/shoutrrr/pkg/types"
"golang.org/x/net/dns/dnsmessage"
@@ -178,39 +179,45 @@ func TestPublicNotificationTCP(t *testing.T) {
} {
t.Run(rawURL, func(t *testing.T) {
t.Parallel()
// MQTT waits for a fixed library timeout even after a dial failure.
// Virtual time preserves the full send/cleanup path without that delay.
t.Run("internal destination", func(t *testing.T) {
err := sendPublicNotification(strings.ReplaceAll(rawURL, "HOST", "127.0.0.1"), "test")
if !errors.Is(err, errInternalDestination) {
t.Fatalf("expected blocked destination, got %v", err)
}
synctest.Test(t, func(t *testing.T) {
err := sendPublicNotification(strings.ReplaceAll(rawURL, "HOST", "127.0.0.1"), "test")
if !errors.Is(err, errInternalDestination) {
t.Fatalf("expected blocked destination, got %v", err)
}
})
})
t.Run("public destination uses injected dialer", func(t *testing.T) {
var calls atomic.Int32
stopped := errors.New("test dial stopped")
service, err := newPublicNotificationService(strings.ReplaceAll(rawURL, "HOST", "8.8.8.8"), types.SenderOptions{
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
calls.Add(1)
if network != "tcp" || !strings.HasPrefix(address, "8.8.8.8:") {
t.Errorf("unexpected dial: %s %s", network, address)
}
if err := checkNotificationAddress(address); err != nil {
t.Error(err)
}
return nil, stopped
},
synctest.Test(t, func(t *testing.T) {
var calls atomic.Int32
stopped := errors.New("test dial stopped")
service, err := newPublicNotificationService(strings.ReplaceAll(rawURL, "HOST", "8.8.8.8"), types.SenderOptions{
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
calls.Add(1)
if network != "tcp" || !strings.HasPrefix(address, "8.8.8.8:") {
t.Errorf("unexpected dial: %s %s", network, address)
}
if err := checkNotificationAddress(address); err != nil {
t.Error(err)
}
return nil, stopped
},
})
if err != nil {
t.Fatal(err)
}
if closer, ok := service.(io.Closer); ok {
defer closer.Close()
}
if err := service.Send("test", &types.Params{}); err == nil {
t.Fatal("expected dial failure")
}
if calls.Load() == 0 {
t.Fatal("custom dialer was not used")
}
})
if err != nil {
t.Fatal(err)
}
if closer, ok := service.(io.Closer); ok {
defer closer.Close()
}
if err := service.Send("test", &types.Params{}); err == nil {
t.Fatal("expected dial failure")
}
if calls.Load() == 0 {
t.Fatal("custom dialer was not used")
}
})
})
}

View File

@@ -12,7 +12,7 @@ RUN apk add --no-cache ca-certificates && update-ca-certificates
# Build
ARG TARGETOS TARGETARCH
RUN CGO_ENABLED=0 GOGC=75 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags "-w -s" -o /agent ./internal/cmd/agent
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags "-w -s" -o /agent ./internal/cmd/agent
RUN rm -rf /tmp/*

View File

@@ -10,14 +10,14 @@ COPY . ./
# Build
ARG TARGETOS TARGETARCH
RUN CGO_ENABLED=0 GOGC=75 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags "-w -s" -o /agent ./internal/cmd/agent
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags "-w -s" -o /agent ./internal/cmd/agent
RUN rm -rf /tmp/*
# --------------------------
# Final image: default scratch-based agent
# --------------------------
FROM alpine:3.23
FROM alpine:3.24
COPY --from=builder /agent /agent
# AMD GPU name lookup (used by agent on Linux when /usr/share/libdrm/amdgpu.ids is read)
@@ -28,4 +28,4 @@ RUN apk add --no-cache smartmontools zfs
# Ensure data persistence across container recreations
VOLUME ["/var/lib/beszel-agent"]
ENTRYPOINT ["/agent"]
ENTRYPOINT ["/agent"]

View File

@@ -10,13 +10,13 @@ COPY . ./
# Build
ARG TARGETOS TARGETARCH
RUN CGO_ENABLED=0 GOGC=75 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags "-w -s" -o /agent ./internal/cmd/agent
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags "-w -s" -o /agent ./internal/cmd/agent
# --------------------------
# Final image
# Note: must cap_add: [CAP_PERFMON] and mount /dev/dri/ as volume
# --------------------------
FROM alpine:3.23
FROM alpine:3.24
COPY --from=builder /agent /agent

View File

@@ -10,7 +10,7 @@ COPY . ./
# Build
ARG TARGETOS TARGETARCH
RUN CGO_ENABLED=0 GOGC=75 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -tags glibc -ldflags "-w -s" -o /agent ./internal/cmd/agent
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -tags glibc -ldflags "-w -s" -o /agent ./internal/cmd/agent
# --------------------------
# Smartmontools builder stage

View File

@@ -17,7 +17,7 @@ RUN set -eux; \
if [ "$TARGETARCH" = "arm" ] && [ -n "$TARGETVARIANT" ]; then \
export GOARM="${TARGETVARIANT#v}"; \
fi; \
CGO_ENABLED=0 GOGC=75 GOOS=$TARGETOS GOARCH=$TARGETARCH \
CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \
go build -tags glibc -ldflags "-w -s" -o /agent ./internal/cmd/agent
# --------------------------
@@ -70,7 +70,9 @@ RUN set -eux; \
# --------------------------
FROM --platform=$TARGETPLATFORM debian:bookworm-slim AS zfsutils-builder
RUN apt-get update && apt-get install -y --no-install-recommends \
# zfsutils-linux is distributed in Debian's contrib component.
RUN sed -i 's/Components: main/Components: main contrib/' /etc/apt/sources.list.d/debian.sources \
&& apt-get update && apt-get install -y --no-install-recommends \
zfsutils-linux \
&& rm -rf /var/lib/apt/lists/*

View File

@@ -17,7 +17,7 @@ RUN update-ca-certificates
# Build
ARG TARGETOS TARGETARCH
RUN CGO_ENABLED=0 GOGC=75 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags "-w -s" -o /beszel ./internal/cmd/hub
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags "-w -s" -o /beszel ./internal/cmd/hub
# ? -------------------------
FROM scratch
@@ -31,4 +31,4 @@ VOLUME ["/beszel_data"]
EXPOSE 8090
ENTRYPOINT [ "/beszel" ]
CMD ["serve", "--http=0.0.0.0:8090"]
CMD ["serve", "--http=0.0.0.0:8090"]

View File

@@ -978,11 +978,18 @@ func TestAgentWebSocketIntegration(t *testing.T) {
}
}
// Verify system status
updatedSystemRecord, err := testApp.FindRecordById("systems", systemRecord.Id)
require.NoError(t, err)
status := updatedSystemRecord.GetString("status")
assert.Equal(t, tc.expectSystemStatus, status, "System status should match expected value")
// A connected WebSocket does not mean the hub has finished verifying
// the agent and updating the system. Wait for the database state rather
// than assuming that work completes within a fixed sleep under load.
var status string
require.EventuallyWithT(t, func(c *assert.CollectT) {
updatedSystemRecord, err := testApp.FindRecordById("systems", systemRecord.Id)
if !assert.NoError(c, err) {
return
}
status = updatedSystemRecord.GetString("status")
assert.Equal(c, tc.expectSystemStatus, status, "System status should match expected value")
}, 5*time.Second, 20*time.Millisecond)
t.Logf("%s - System status: %s, Fingerprint: %s", tc.description, status, finalFingerprint)
})
@@ -1142,42 +1149,43 @@ func TestMultipleSystemsWithSameUniversalToken(t *testing.T) {
// Verify system creation/reuse behavior
if tc.expectConnection {
// Count systems after connection
systemsAfter, err := testApp.FindRecordsByFilter("systems", "users ~ {:userId}", "", -1, 0, map[string]any{"userId": userRecord.Id})
require.NoError(t, err)
systemsAfterCount := len(systemsAfter)
expectedSystemsAfter := systemsBeforeCount
if tc.expectNewSystem {
// Should have created a new system
expectedSystemsAfter++
systemCount++
assert.Equal(t, systemsBeforeCount+1, systemsAfterCount, "Should have created a new system")
assert.Equal(t, systemCount, systemsAfterCount, "Total system count should match expected")
} else {
// Should have reused existing system
assert.Equal(t, systemsBeforeCount, systemsAfterCount, "Should not have created a new system")
assert.Equal(t, systemCount, systemsAfterCount, "Total system count should remain the same")
}
time.Sleep(20 * time.Millisecond)
// WebSocket connection precedes the hub's asynchronous system
// setup. Re-read all database state until setup is complete.
var systemId, status string
require.EventuallyWithT(t, func(c *assert.CollectT) {
systemsAfter, err := testApp.FindRecordsByFilter("systems", "users ~ {:userId}", "", -1, 0, map[string]any{"userId": userRecord.Id})
if !assert.NoError(c, err) {
return
}
assert.Len(c, systemsAfter, expectedSystemsAfter, "System creation/reuse should match expected behavior")
assert.Len(c, systemsAfter, systemCount, "Total system count should match expected")
// Verify that a fingerprint record exists for this fingerprint
fingerprints, err := testApp.FindRecordsByFilter("fingerprints", "token = {:token} && fingerprint = {:fingerprint}", "", -1, 0, map[string]any{
"token": universalToken,
"fingerprint": tc.agentFingerprint,
})
require.NoError(t, err)
require.Len(t, fingerprints, 1, "Should have exactly one fingerprint record for this token+fingerprint combination")
fingerprints, err := testApp.FindRecordsByFilter("fingerprints", "token = {:token} && fingerprint = {:fingerprint}", "", -1, 0, map[string]any{
"token": universalToken,
"fingerprint": tc.agentFingerprint,
})
if !assert.NoError(c, err) || !assert.Len(c, fingerprints, 1, "Should have exactly one fingerprint record for this token+fingerprint combination") {
return
}
fingerprint := fingerprints[0]
assert.Equal(t, universalToken, fingerprint.GetString("token"), "Fingerprint should have the universal token")
assert.Equal(t, tc.agentFingerprint, fingerprint.GetString("fingerprint"), "Fingerprint should match agent's fingerprint")
fingerprint := fingerprints[0]
assert.Equal(c, universalToken, fingerprint.GetString("token"), "Fingerprint should have the universal token")
assert.Equal(c, tc.agentFingerprint, fingerprint.GetString("fingerprint"), "Fingerprint should match agent's fingerprint")
// Verify system status
systemId := fingerprint.GetString("system")
system, err := testApp.FindRecordById("systems", systemId)
require.NoError(t, err)
status := system.GetString("status")
assert.Equal(t, tc.expectSystemStatus, status, "System status should match expected value")
systemId = fingerprint.GetString("system")
system, err := testApp.FindRecordById("systems", systemId)
if !assert.NoError(c, err) {
return
}
status = system.GetString("status")
assert.Equal(c, tc.expectSystemStatus, status, "System status should match expected value")
}, 5*time.Second, 20*time.Millisecond)
t.Logf("%s - System ID: %s, Status: %s, New System: %v", tc.description, systemId, status, tc.expectNewSystem)
}

View File

@@ -0,0 +1,159 @@
//go:build testing
package systems
import (
"context"
"crypto/ed25519"
"crypto/rand"
"net"
"sync/atomic"
"testing"
"time"
"github.com/fxamacker/cbor/v2"
"github.com/henrygd/beszel/internal/common"
"github.com/henrygd/beszel/internal/entities/monitor"
esystem "github.com/henrygd/beszel/internal/entities/system"
"github.com/henrygd/beszel/internal/hub/expirymap"
"github.com/pocketbase/pocketbase/core"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/ssh"
)
func TestSSHNetworkMonitorReconnectSync(t *testing.T) {
sys, app := newTestSystemWithHub(t)
sys.manager.zfsFetchMap = expirymap.New[zfsFetchState](time.Hour)
t.Cleanup(sys.manager.zfsFetchMap.StopCleaner)
sys.ctx = context.Background()
sys.Status = up
_, key, err := ed25519.GenerateKey(rand.Reader)
require.NoError(t, err)
signer, err := ssh.NewSignerFromKey(key)
require.NoError(t, err)
config := &ssh.ServerConfig{NoClientAuth: true, ServerVersion: "SSH-2.0-beszel_0.20.0"}
config.AddHostKey(signer)
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
t.Cleanup(func() { _ = listener.Close() })
sys.Host, sys.Port, err = net.SplitHostPort(listener.Addr().String())
require.NoError(t, err)
sys.manager.sshConfig = &ssh.ClientConfig{User: "test", HostKeyCallback: ssh.InsecureIgnoreHostKey(), Timeout: time.Second}
t.Cleanup(sys.closeSSHConnection)
requests := make(chan monitor.SyncRequest, 10)
var failSync atomic.Bool
go func() {
for {
conn, err := listener.Accept()
if err != nil {
return
}
go func() {
server, channels, reqs, err := ssh.NewServerConn(conn, config)
if err != nil {
_ = conn.Close()
return
}
defer server.Close()
go ssh.DiscardRequests(reqs)
for channel := range channels {
ch, reqs, err := channel.Accept()
if err != nil {
return
}
go func() {
defer ch.Close()
for req := range reqs {
if req.Type != "shell" {
_ = req.Reply(false, nil)
continue
}
_ = req.Reply(true, nil)
var request common.HubRequest[cbor.RawMessage]
if cbor.NewDecoder(ch).Decode(&request) != nil {
return
}
response := common.AgentResponse{}
switch request.Action {
case common.GetData:
response.SystemData = &esystem.CombinedData{}
case common.SyncNetworkMonitors:
var syncReq monitor.SyncRequest
if cbor.Unmarshal(request.Data, &syncReq) != nil {
return
}
requests <- syncReq
if failSync.Load() {
response.Error = "test sync failure"
} else {
response.Data, _ = cbor.Marshal(monitor.SyncResponse{})
}
}
_ = cbor.NewEncoder(ch).Encode(response)
_, _ = ch.SendRequest("exit-status", false, ssh.Marshal(struct{ Status uint32 }{0}))
return
}
}()
}
}()
}
}()
collection, err := app.FindCachedCollectionByNameOrId("network_monitors")
require.NoError(t, err)
probe := core.NewRecord(collection)
probe.Load(map[string]any{"system": sys.Id, "target": "localhost", "protocol": "tcp", "port": 80, "interval": 60, "enabled": true})
require.NoError(t, app.SaveNoValidate(probe))
fetch := func() {
t.Helper()
_, err := sys.fetchDataFromAgent(common.DataRequestOptions{})
require.NoError(t, err, "monitor sync failure must not fail stats fetching")
}
receive := func() monitor.SyncRequest {
t.Helper()
select {
case req := <-requests:
require.Equal(t, monitor.SyncActionReplace, req.Action)
return req
case <-time.After(time.Second):
t.Fatal("missing full monitor sync")
return monitor.SyncRequest{}
}
}
fetch()
require.Equal(t, probe.Id, receive().Configs[0].ID)
require.False(t, sys.monitorsNeedSync.Load())
fetch()
require.Empty(t, requests, "steady-state fetch must not resync")
// Simulate loss of the agent process/connection and its in-memory monitors.
require.NoError(t, sys.client.Load().Close())
fetch()
require.Equal(t, probe.Id, receive().Configs[0].ID)
require.False(t, sys.monitorsNeedSync.Load())
// Failed replacements are retried on the next successful stats fetch.
require.NoError(t, sys.client.Load().Close())
failSync.Store(true)
fetch()
receive()
require.True(t, sys.monitorsNeedSync.Load())
failSync.Store(false)
fetch()
receive()
require.False(t, sys.monitorsNeedSync.Load())
probe.Set("enabled", false)
require.NoError(t, app.SaveNoValidate(probe))
require.NoError(t, sys.client.Load().Close())
fetch()
require.Empty(t, receive().Configs, "empty replacement must clear stale monitors")
}
func TestPendingNetworkMonitorSyncQueryFailure(t *testing.T) {
sys, app := newTestSystemWithHub(t)
_, err := app.DB().NewQuery("DROP TABLE network_monitors").Execute()
require.NoError(t, err)
sys.monitorsNeedSync.Store(true)
sys.syncPendingNetworkMonitors()
require.True(t, sys.monitorsNeedSync.Load())
}

View File

@@ -6,6 +6,7 @@ import (
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"
"time"
@@ -13,6 +14,7 @@ import (
"github.com/fxamacker/cbor/v2"
"github.com/henrygd/beszel/internal/common"
"github.com/henrygd/beszel/internal/entities/monitor"
esystem "github.com/henrygd/beszel/internal/entities/system"
"github.com/henrygd/beszel/internal/hub/ws"
"github.com/lxzan/gws"
"github.com/pocketbase/pocketbase/core"
@@ -22,17 +24,31 @@ import (
type monitorSyncClient struct {
gws.BuiltinEventHandler
requests chan common.HubRequest[monitor.SyncRequest]
failSync atomic.Bool
}
func (c *monitorSyncClient) OnMessage(conn *gws.Conn, message *gws.Message) {
defer message.Close()
var req common.HubRequest[monitor.SyncRequest]
var req common.HubRequest[cbor.RawMessage]
if err := cbor.Unmarshal(message.Bytes(), &req); err != nil {
return
}
c.requests <- req
data, _ := cbor.Marshal(monitor.SyncResponse{})
response, _ := cbor.Marshal(common.AgentResponse{Id: req.Id, Data: data})
resp := common.AgentResponse{Id: req.Id}
if req.Action == common.GetData {
resp.SystemData = &esystem.CombinedData{}
} else {
var data monitor.SyncRequest
if err := cbor.Unmarshal(req.Data, &data); err != nil {
return
}
c.requests <- common.HubRequest[monitor.SyncRequest]{Id: req.Id, Action: req.Action, Data: data}
if c.failSync.Load() {
resp.Error = "test sync failure"
} else {
resp.Data, _ = cbor.Marshal(monitor.SyncResponse{})
}
}
response, _ := cbor.Marshal(resp)
_ = conn.WriteMessage(gws.OpcodeBinary, response)
}
@@ -57,7 +73,7 @@ func TestNetworkMonitorSyncSkipsOlderAgents(t *testing.T) {
}
func TestNetworkMonitorReconnectSync(t *testing.T) {
for _, change := range []string{"delete", "disable"} {
for _, change := range []string{"delete", "disable", "retry"} {
t.Run(change, func(t *testing.T) {
sys, app := newTestSystemWithHub(t)
record, err := app.FindRecordById("systems", sys.Id)
@@ -120,9 +136,33 @@ func TestNetworkMonitorReconnectSync(t *testing.T) {
}
}
client.failSync.Store(change == "retry")
initial := connect()
require.Len(t, initial.Configs, 1)
require.Equal(t, probe.Id, initial.Configs[0].ID)
if change == "retry" {
system, err := sm.GetSystem(sys.Id)
require.NoError(t, err)
require.Eventually(t, system.monitorsNeedSync.Load, time.Second, time.Millisecond)
// A second failed sync must not fail the stats fetch or clear pending state.
_, err = system.fetchDataFromAgent(common.DataRequestOptions{})
require.NoError(t, err)
require.True(t, system.monitorsNeedSync.Load())
require.Len(t, client.requests, 1)
<-client.requests
client.failSync.Store(false)
_, err = system.fetchDataFromAgent(common.DataRequestOptions{})
require.NoError(t, err)
require.False(t, system.monitorsNeedSync.Load())
require.Len(t, client.requests, 1)
retry := <-client.requests
require.Equal(t, monitor.SyncActionReplace, retry.Data.Action)
require.Equal(t, initial.Configs, retry.Data.Configs)
_, err = system.fetchDataFromAgent(common.DataRequestOptions{})
require.NoError(t, err)
require.Empty(t, client.requests, "successful sync must not repeat on every fetch")
return
}
require.NoError(t, sm.RemoveSystem(sys.Id))
if change == "delete" {
require.NoError(t, app.Delete(probe))

View File

@@ -2,6 +2,7 @@ package systems
import (
"context"
"fmt"
"time"
"github.com/henrygd/beszel"
@@ -9,6 +10,27 @@ import (
"github.com/henrygd/beszel/internal/entities/monitor"
)
// syncPendingNetworkMonitors runs on WebSocket connect and after successful stats
// fetches. Failed syncs retry on the next update without taking the system down.
func (sys *System) syncPendingNetworkMonitors() {
if !sys.monitorsNeedSync.Swap(false) {
return
}
if err := sys.syncAllNetworkMonitors(); err != nil {
sys.monitorsNeedSync.Store(true)
sys.manager.hub.Logger().Warn("failed to sync monitors to agent", "system", sys.Id, "err", err)
}
}
func (sys *System) syncAllNetworkMonitors() error {
configs, err := sys.manager.GetMonitorConfigsForSystem(sys.Id)
if err != nil {
return fmt.Errorf("failed to load monitors: %w", err)
}
// An empty set must also replace probes retained across a disconnect.
return sys.SyncNetworkMonitors(configs)
}
// SyncNetworkMonitors sends monitor configurations to the agent.
func (sys *System) SyncNetworkMonitors(configs []monitor.Config) error {
_, err := sys.syncNetworkMonitors(monitor.SyncRequest{Action: monitor.SyncActionReplace, Configs: configs})

View File

@@ -56,6 +56,9 @@ type System struct {
smartInterval time.Duration // Interval for periodic SMART data updates
zfsFetching atomic.Bool // True if ZFS pools are currently being fetched
zfsInterval time.Duration // Interval for periodic ZFS detail data updates
// A fresh connection needs a full monitor configuration sync.
monitorsNeedSync atomic.Bool
// Serialize persistence from scheduled updates and resumes through commit.
recordsMu sync.Mutex
// Protected by recordsMu; realtime reads don't consume probes.
@@ -630,7 +633,10 @@ func (sys *System) request(ctx context.Context, action common.WebSocketAction, r
err := sys.sshTransport.RequestWithRetry(ctx, action, req, dest, 1)
// Keep legacy SSH client/version fields in sync for other code paths.
if sys.sshTransport != nil {
sys.client.Store(sys.sshTransport.GetClient())
client := sys.sshTransport.GetClient()
if previous := sys.client.Swap(client); client != nil && client != previous {
sys.monitorsNeedSync.Store(true)
}
sys.agentVersion = sys.sshTransport.GetAgentVersion()
}
return err
@@ -688,6 +694,7 @@ func (sys *System) fetchDataFromAgent(options common.DataRequestOptions) (*syste
if sys.WsConn != nil && sys.WsConn.IsConnected() {
wsData, err := sys.fetchDataViaWebSocket(options)
if err == nil {
sys.syncPendingNetworkMonitors()
return wsData, nil
}
// close the WebSocket connection if error and try SSH
@@ -698,6 +705,7 @@ func (sys *System) fetchDataFromAgent(options common.DataRequestOptions) (*syste
if err != nil {
return nil, err
}
sys.syncPendingNetworkMonitors()
return sshData, nil
}
@@ -932,6 +940,7 @@ func (s *System) createSSHClient() error {
return err
}
s.agentVersion, _ = extractAgentVersion(string(client.Conn.ServerVersion()))
s.monitorsNeedSync.Store(true)
s.manager.resetFailedSmartFetchState(s.Id)
s.manager.resetFailedZfsFetchState(s.Id)
return nil

View File

@@ -349,23 +349,14 @@ func (sm *SystemManager) AddWebSocketSystem(systemId string, agentVersion semver
system := sm.NewSystem(systemId)
system.WsConn = wsConn
system.agentVersion = agentVersion
system.monitorsNeedSync.Store(true)
if err := sm.AddRecord(systemRecord, system); err != nil {
return err
}
// Sync network monitors to the newly connected agent
go func() {
configs, err := sm.GetMonitorConfigsForSystem(systemId)
if err != nil {
sm.hub.Logger().Warn("failed to load monitors for agent", "system", systemId, "err", err)
return
}
// An empty set must also replace any probes retained across a disconnect.
if err := system.SyncNetworkMonitors(configs); err != nil {
sm.hub.Logger().Warn("failed to sync monitors to agent", "system", systemId, "err", err)
}
}()
go system.syncPendingNetworkMonitors()
return nil
}

View File

@@ -1057,6 +1057,15 @@ func init() {
"required": true,
"system": false,
"type": "number"
},
{
"hidden": false,
"id": "bool2084032502",
"name": "updatable",
"presentable": false,
"required": false,
"system": false,
"type": "bool"
}
],
"indexes": [

View File

@@ -1,27 +0,0 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
c, err := app.FindCollectionByNameOrId("zfs_pools")
if err != nil {
return err
}
c.Fields.Add(&core.TextField{Name: "display_name"})
c.Fields.Add(&core.BoolField{Name: "raw"})
return app.Save(c)
}, func(app core.App) error {
c, err := app.FindCollectionByNameOrId("zfs_pools")
if err != nil {
return err
}
c.Fields.RemoveByName("display_name")
c.Fields.RemoveByName("raw")
return app.Save(c)
})
}

View File

@@ -1,24 +0,0 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("containers")
if err != nil {
return err
}
collection.Fields.Add(&core.BoolField{Name: "updatable"})
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("containers")
if err != nil {
return err
}
collection.Fields.RemoveByName("updatable")
return app.Save(collection)
})
}

View File

@@ -59,7 +59,11 @@ export const containerChartCols: ColumnDef<ContainerRecord>[] = [
const allSystems = $allSystemsById.get()
const systemNameA = allSystems[a.original.system]?.name ?? ""
const systemNameB = allSystems[b.original.system]?.name ?? ""
return systemNameA.localeCompare(systemNameB)
const primary = systemNameA.localeCompare(systemNameB)
if (primary !== 0) {
return primary
}
return a.original.name.localeCompare(b.original.name)
},
header: ({ column }) => <HeaderButton column={column} name={t`System`} Icon={ServerIcon} />,
cell: ({ getValue }) => {

View File

@@ -211,7 +211,6 @@ export default memo(function SystemDetail({ id }: { id: string }) {
<TemperatureChart {...coreProps} setPageBottomExtraMargin={setPageBottomExtraMargin} />
<FanChart {...coreProps} />
<BatteryChart system={system} {...coreProps} />
<SwapChart chartData={chartData} grid={grid} dataEmpty={dataEmpty} systemStats={systemStats} />
{pageBottomExtraMargin > 0 && <div style={{ marginBottom: pageBottomExtraMargin }}></div>}
</div>
</TabsContent>

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: ar\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:46\n"
"PO-Revision-Date: 2026-09-18 19:22\n"
"Last-Translator: \n"
"Language-Team: Arabic\n"
"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: bg\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:45\n"
"PO-Revision-Date: 2026-09-18 19:21\n"
"Last-Translator: \n"
"Language-Team: Bulgarian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: cs\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:45\n"
"PO-Revision-Date: 2026-09-18 19:21\n"
"Last-Translator: \n"
"Language-Team: Czech\n"
"Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: da\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:45\n"
"PO-Revision-Date: 2026-09-18 19:21\n"
"Last-Translator: \n"
"Language-Team: Danish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: de\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:46\n"
"PO-Revision-Date: 2026-09-18 19:22\n"
"Last-Translator: \n"
"Language-Team: German\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: el\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:45\n"
"PO-Revision-Date: 2026-09-18 19:21\n"
"Last-Translator: \n"
"Language-Team: Greek\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: es\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:45\n"
"PO-Revision-Date: 2026-09-18 19:21\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: fa\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:46\n"
"PO-Revision-Date: 2026-09-18 19:22\n"
"Last-Translator: \n"
"Language-Team: Persian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: fr\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:45\n"
"PO-Revision-Date: 2026-09-18 19:21\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: he\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:45\n"
"PO-Revision-Date: 2026-09-18 19:21\n"
"Last-Translator: \n"
"Language-Team: Hebrew\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n==2 ? 1 : 2);\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: hr\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:46\n"
"PO-Revision-Date: 2026-09-18 19:22\n"
"Last-Translator: \n"
"Language-Team: Croatian\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: hu\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:45\n"
"PO-Revision-Date: 2026-09-18 19:21\n"
"Last-Translator: \n"
"Language-Team: Hungarian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: id\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:46\n"
"PO-Revision-Date: 2026-09-18 19:22\n"
"Last-Translator: \n"
"Language-Team: Indonesian\n"
"Plural-Forms: nplurals=1; plural=0;\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: it\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:45\n"
"PO-Revision-Date: 2026-09-18 19:21\n"
"Last-Translator: \n"
"Language-Team: Italian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: ja\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:45\n"
"PO-Revision-Date: 2026-09-18 19:21\n"
"Last-Translator: \n"
"Language-Team: Japanese\n"
"Plural-Forms: nplurals=1; plural=0;\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: ko\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:45\n"
"PO-Revision-Date: 2026-09-18 19:21\n"
"Last-Translator: \n"
"Language-Team: Korean\n"
"Plural-Forms: nplurals=1; plural=0;\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: nl\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:45\n"
"PO-Revision-Date: 2026-09-18 19:21\n"
"Last-Translator: \n"
"Language-Team: Dutch\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: no\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:45\n"
"PO-Revision-Date: 2026-09-18 19:21\n"
"Last-Translator: \n"
"Language-Team: Norwegian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: pl\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:45\n"
"PO-Revision-Date: 2026-09-18 19:21\n"
"Last-Translator: \n"
"Language-Team: Polish\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: pt\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:45\n"
"PO-Revision-Date: 2026-09-18 19:21\n"
"Last-Translator: \n"
"Language-Team: Portuguese\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: ro\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"PO-Revision-Date: 2026-09-18 19:22\n"
"Last-Translator: \n"
"Language-Team: Romanian\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n"
@@ -30,6 +30,12 @@ msgstr ""
msgid "{0} of {1} row(s) selected."
msgstr "{0} din {1} rând(uri) selectat(e)."
#. placeholder {0}: selectedSystemIds.size
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "{0} selected"
msgstr ""
#: src/components/routes/system/info-bar.tsx
msgid "{cores, plural, one {# core} other {# cores}}"
msgstr ""
@@ -61,7 +67,7 @@ msgstr "1 oră"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr ""
msgstr "1 min"
#: src/lib/utils.ts
msgid "1 minute"
@@ -78,7 +84,7 @@ msgstr "12 ore"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr ""
msgstr "15 min"
#: src/lib/utils.ts
msgid "24 hours"
@@ -91,7 +97,7 @@ msgstr "30 zile"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr ""
msgstr "5 min"
#. Table column
#: src/components/routes/settings/quiet-hours.tsx
@@ -121,6 +127,10 @@ msgstr "Stare activă"
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
@@ -142,7 +152,7 @@ msgstr "Reglaţi lăţimea aspectului principal"
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr ""
msgstr "Admin"
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
@@ -154,7 +164,7 @@ msgstr "După setarea variabilelor de mediu, reporniţi centrul Beszel pentru ca
#: src/components/systems-table/systems-table-columns.tsx
msgid "Agent"
msgstr ""
msgstr "Agent"
#: src/components/command-palette.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -188,6 +198,7 @@ msgstr "Toate sistemele"
msgid "Are you sure you want to delete {name}?"
msgstr "Sigur vrei să ștergi {name}?"
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Are you sure?"
msgstr "Ești sigur?"
@@ -245,6 +256,14 @@ msgstr "Utilizarea medie a {0}"
msgid "Average utilization of GPU engines"
msgstr ""
#: src/components/routes/system/charts/monitors-charts.tsx
msgid "Average, minimum, and maximum response time"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Avg 1h"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Backups"
@@ -306,6 +325,19 @@ msgstr "Biți (Kbps, Mbps, Gbps)"
msgid "Boot state"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Bulk Add"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Bulk Add {foo}"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Bulk copy"
msgstr ""
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)"
@@ -327,6 +359,7 @@ msgstr ""
msgid "Can stop"
msgstr ""
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -348,7 +381,7 @@ msgstr "Atenție - posibilă pierdere de date"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr ""
msgstr "Celsius (°C)"
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -396,6 +429,7 @@ msgid "Checksum errors"
msgstr ""
#: src/components/containers-table/containers-table.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
@@ -403,6 +437,14 @@ msgstr ""
msgid "Clear"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Clear all"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Clear matches"
msgstr ""
#: src/components/containers-table/containers-table.tsx
msgid "Click on a container to view more information."
msgstr ""
@@ -432,6 +474,10 @@ msgstr ""
msgid "Configure how you receive alert notifications."
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Configure response monitoring from this agent."
msgstr ""
#: src/components/login/auth-form.tsx
#: src/components/login/auth-form.tsx
msgid "Confirm password"
@@ -457,6 +503,7 @@ msgstr ""
msgid "Containers"
msgstr ""
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -517,6 +564,10 @@ msgstr ""
msgid "Copy the<0>docker-compose.yml</0> content for the agent below, or register agents automatically with a <1>universal token</1>."
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Copy to system"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy YAML"
msgstr ""
@@ -530,7 +581,7 @@ msgstr ""
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr ""
msgstr "CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -630,6 +681,7 @@ msgstr ""
msgid "Default time period"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
@@ -718,12 +770,14 @@ msgstr "Descarcă"
msgid "Duration"
msgstr "Durată"
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Edit"
msgstr "Editează"
#: src/components/add-system.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Edit {foo}"
msgstr "Editează {foo}"
@@ -732,7 +786,7 @@ msgstr "Editează {foo}"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr ""
msgstr "Email"
#: src/components/routes/settings/notifications.tsx
msgid "Email notifications"
@@ -772,6 +826,11 @@ msgstr ""
msgid "Ephemeral"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/heartbeat.tsx
@@ -844,6 +903,11 @@ msgstr ""
msgid "Failed to authenticate"
msgstr ""
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
msgid "Failed to delete monitors."
msgstr ""
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -862,6 +926,10 @@ msgstr ""
msgid "Failed to update alert"
msgstr ""
#: src/components/network-monitors-table/network-monitors-table.tsx
msgid "Failed to update monitors."
msgstr ""
#. placeholder {0}: statusTotals[ServiceStatus.Failed]
#: src/components/systemd-table/systemd-table.tsx
msgid "Failed: {0}"
@@ -872,6 +940,7 @@ msgid "Fans"
msgstr ""
#: src/components/containers-table/containers-table.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
@@ -924,7 +993,7 @@ msgstr ""
#: src/components/routes/settings/quiet-hours.tsx
msgid "Global"
msgstr ""
msgstr "Global"
#: src/components/routes/system.tsx
msgid "GPU"
@@ -1011,14 +1080,25 @@ msgctxt "Docker image"
msgid "Image"
msgstr "Imagine"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Docker image"
msgid "Image update available"
msgstr ""
#: src/components/routes/settings/quiet-hours.tsx
msgid "Inactive"
msgstr "Inactiv"
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/routes/settings/heartbeat.tsx
msgid "Interval"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Interval (seconds)"
msgstr ""
#: src/components/login/auth-form.tsx
msgid "Invalid email address."
msgstr ""
@@ -1097,6 +1177,15 @@ msgstr "Jurnale"
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
msgstr ""
#: src/components/routes/system/charts/monitors-charts.tsx
msgctxt "Packet loss"
msgid "Loss"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Loss 1h"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Main PID"
msgstr ""
@@ -1115,6 +1204,10 @@ msgstr ""
msgid "Max 1 min"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Max 1h"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/info-bar.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
@@ -1141,9 +1234,23 @@ msgstr "Memorie Utilizată"
msgid "Memory usage of containers"
msgstr "Utilizarea memoriei de către containere"
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Min 1h"
msgstr ""
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Model"
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Monitor"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Monitors created"
msgstr ""
#: src/components/routes/system/storage-pools-table.tsx
@@ -1163,6 +1270,29 @@ msgstr "Nume"
msgid "Net"
msgstr "Rețea"
#: src/components/routes/system.tsx
msgid "Network"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Network Monitor"
msgstr ""
#: src/lib/alerts.ts
msgid "Network Monitor Loss"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/monitors.tsx
msgid "Network Monitors"
msgstr ""
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of containers"
msgstr "Traficul de rețea al containerelor"
@@ -1197,6 +1327,7 @@ msgstr ""
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
@@ -1207,6 +1338,7 @@ msgstr ""
msgid "No S.M.A.R.T. attributes available for this device."
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "No systems found."
@@ -1238,6 +1370,11 @@ msgstr ""
msgid "One or more containers are unhealthy"
msgstr ""
#. placeholder {0}: alert.value
#: src/components/active-alerts.tsx
msgid "One or more monitors exceed {0}% loss"
msgstr ""
#: src/lib/alerts.ts
msgid "One or more services are in a failed state"
msgstr ""
@@ -1251,6 +1388,7 @@ msgstr ""
msgid "One-time password"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
@@ -1272,6 +1410,11 @@ msgstr "Altele"
msgid "Overwrite existing alerts"
msgstr ""
#: src/components/routes/system/charts/monitors-charts.tsx
msgid "Packet loss (%)"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
@@ -1309,6 +1452,7 @@ msgstr ""
msgid "Past"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Pause"
msgstr "Suspendare"
@@ -1392,8 +1536,9 @@ msgid "Pool Usage"
msgstr ""
#: src/components/add-system.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Port"
msgstr ""
msgstr "Port"
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
@@ -1418,6 +1563,11 @@ msgstr ""
msgid "Process started"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Protocol"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Public key"
msgstr ""
@@ -1503,10 +1653,20 @@ msgstr ""
msgid "Resolved"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/routes/system/charts/monitors-charts.tsx
msgid "Response"
msgstr ""
#: src/components/network-monitors-table/network-monitors-table.tsx
msgid "Response time monitoring from agents."
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Restarts"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Resume"
msgstr ""
@@ -1539,6 +1699,7 @@ msgid "S.M.A.R.T. Self-Test"
msgstr ""
#: src/components/add-system.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Save {foo}"
msgstr ""
@@ -1575,6 +1736,11 @@ msgstr "Caută"
msgid "Search for systems or settings..."
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Search systems"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Seconds between pings (default: 60)"
msgstr ""
@@ -1587,6 +1753,27 @@ msgstr ""
msgid "Select {foo}"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Select a system"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Select all"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Select matches"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Select row"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Select systems"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Send a single heartbeat ping to verify your endpoint is working."
msgstr ""
@@ -1692,6 +1879,8 @@ msgstr ""
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1716,6 +1905,8 @@ msgid "Systemd Services"
msgstr ""
#: src/components/navbar.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Systems"
msgstr "Sisteme"
@@ -1732,6 +1923,11 @@ msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Target"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Sarcini"
@@ -1779,6 +1975,7 @@ msgstr ""
msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database."
msgstr ""
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
msgid "This will permanently delete all selected records from the database."
msgstr ""
@@ -1821,7 +2018,7 @@ msgstr ""
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr ""
msgstr "Total"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
@@ -1839,7 +2036,7 @@ msgstr ""
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
msgid "Total: {0}"
msgstr ""
msgstr "Total: {0}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggered by"
@@ -1901,6 +2098,10 @@ msgstr ""
msgid "Triggers when memory usage exceeds a threshold"
msgstr ""
#: src/lib/alerts.ts
msgid "Triggers when one hour loss exceeds a threshold"
msgstr ""
#: src/lib/alerts.ts
msgid "Triggers when status switches between up and down"
msgstr ""
@@ -1960,6 +2161,7 @@ msgid "Update"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: ru\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:45\n"
"PO-Revision-Date: 2026-09-18 19:21\n"
"Last-Translator: \n"
"Language-Team: Russian\n"
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: sl\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:45\n"
"PO-Revision-Date: 2026-09-18 19:22\n"
"Last-Translator: \n"
"Language-Team: Slovenian\n"
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: sr\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:45\n"
"PO-Revision-Date: 2026-09-18 19:22\n"
"Last-Translator: \n"
"Language-Team: Serbian (Cyrillic)\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: sv\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:45\n"
"PO-Revision-Date: 2026-09-18 19:22\n"
"Last-Translator: \n"
"Language-Team: Swedish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: th\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"PO-Revision-Date: 2026-09-18 19:22\n"
"Last-Translator: \n"
"Language-Team: Thai\n"
"Plural-Forms: nplurals=1; plural=0;\n"
@@ -30,6 +30,12 @@ msgstr ""
msgid "{0} of {1} row(s) selected."
msgstr ""
#. placeholder {0}: selectedSystemIds.size
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "{0} selected"
msgstr ""
#: src/components/routes/system/info-bar.tsx
msgid "{cores, plural, one {# core} other {# cores}}"
msgstr ""
@@ -121,6 +127,10 @@ msgstr ""
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
@@ -188,6 +198,7 @@ msgstr ""
msgid "Are you sure you want to delete {name}?"
msgstr ""
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Are you sure?"
msgstr ""
@@ -245,6 +256,14 @@ msgstr ""
msgid "Average utilization of GPU engines"
msgstr ""
#: src/components/routes/system/charts/monitors-charts.tsx
msgid "Average, minimum, and maximum response time"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Avg 1h"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Backups"
@@ -306,6 +325,19 @@ msgstr ""
msgid "Boot state"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Bulk Add"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Bulk Add {foo}"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Bulk copy"
msgstr ""
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)"
@@ -327,6 +359,7 @@ msgstr ""
msgid "Can stop"
msgstr ""
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -396,6 +429,7 @@ msgid "Checksum errors"
msgstr ""
#: src/components/containers-table/containers-table.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
@@ -403,6 +437,14 @@ msgstr ""
msgid "Clear"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Clear all"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Clear matches"
msgstr ""
#: src/components/containers-table/containers-table.tsx
msgid "Click on a container to view more information."
msgstr ""
@@ -432,6 +474,10 @@ msgstr ""
msgid "Configure how you receive alert notifications."
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Configure response monitoring from this agent."
msgstr ""
#: src/components/login/auth-form.tsx
#: src/components/login/auth-form.tsx
msgid "Confirm password"
@@ -457,6 +503,7 @@ msgstr ""
msgid "Containers"
msgstr ""
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -517,6 +564,10 @@ msgstr ""
msgid "Copy the<0>docker-compose.yml</0> content for the agent below, or register agents automatically with a <1>universal token</1>."
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Copy to system"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy YAML"
msgstr ""
@@ -630,6 +681,7 @@ msgstr ""
msgid "Default time period"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
@@ -718,12 +770,14 @@ msgstr ""
msgid "Duration"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Edit"
msgstr ""
#: src/components/add-system.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Edit {foo}"
msgstr ""
@@ -772,6 +826,11 @@ msgstr ""
msgid "Ephemeral"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/heartbeat.tsx
@@ -844,6 +903,11 @@ msgstr ""
msgid "Failed to authenticate"
msgstr ""
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
msgid "Failed to delete monitors."
msgstr ""
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -862,6 +926,10 @@ msgstr ""
msgid "Failed to update alert"
msgstr ""
#: src/components/network-monitors-table/network-monitors-table.tsx
msgid "Failed to update monitors."
msgstr ""
#. placeholder {0}: statusTotals[ServiceStatus.Failed]
#: src/components/systemd-table/systemd-table.tsx
msgid "Failed: {0}"
@@ -872,6 +940,7 @@ msgid "Fans"
msgstr ""
#: src/components/containers-table/containers-table.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
@@ -1011,14 +1080,25 @@ msgctxt "Docker image"
msgid "Image"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Docker image"
msgid "Image update available"
msgstr ""
#: src/components/routes/settings/quiet-hours.tsx
msgid "Inactive"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/routes/settings/heartbeat.tsx
msgid "Interval"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Interval (seconds)"
msgstr ""
#: src/components/login/auth-form.tsx
msgid "Invalid email address."
msgstr ""
@@ -1097,6 +1177,15 @@ msgstr ""
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
msgstr ""
#: src/components/routes/system/charts/monitors-charts.tsx
msgctxt "Packet loss"
msgid "Loss"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Loss 1h"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Main PID"
msgstr ""
@@ -1115,6 +1204,10 @@ msgstr ""
msgid "Max 1 min"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Max 1h"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/info-bar.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
@@ -1141,11 +1234,25 @@ msgstr ""
msgid "Memory usage of containers"
msgstr "การใช้หน่วยความจำของคอนเทนเนอร์"
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Min 1h"
msgstr ""
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Monitor"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Monitors created"
msgstr ""
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr ""
@@ -1163,6 +1270,29 @@ msgstr ""
msgid "Net"
msgstr ""
#: src/components/routes/system.tsx
msgid "Network"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Network Monitor"
msgstr ""
#: src/lib/alerts.ts
msgid "Network Monitor Loss"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/monitors.tsx
msgid "Network Monitors"
msgstr ""
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of containers"
msgstr "การรับส่งข้อมูลเครือข่ายของคอนเทนเนอร์"
@@ -1197,6 +1327,7 @@ msgstr ""
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
@@ -1207,6 +1338,7 @@ msgstr ""
msgid "No S.M.A.R.T. attributes available for this device."
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "No systems found."
@@ -1238,6 +1370,11 @@ msgstr ""
msgid "One or more containers are unhealthy"
msgstr ""
#. placeholder {0}: alert.value
#: src/components/active-alerts.tsx
msgid "One or more monitors exceed {0}% loss"
msgstr ""
#: src/lib/alerts.ts
msgid "One or more services are in a failed state"
msgstr ""
@@ -1251,6 +1388,7 @@ msgstr ""
msgid "One-time password"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
@@ -1272,6 +1410,11 @@ msgstr ""
msgid "Overwrite existing alerts"
msgstr ""
#: src/components/routes/system/charts/monitors-charts.tsx
msgid "Packet loss (%)"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
@@ -1309,6 +1452,7 @@ msgstr ""
msgid "Past"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Pause"
msgstr ""
@@ -1392,6 +1536,7 @@ msgid "Pool Usage"
msgstr ""
#: src/components/add-system.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Port"
msgstr ""
@@ -1418,6 +1563,11 @@ msgstr ""
msgid "Process started"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Protocol"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Public key"
msgstr ""
@@ -1503,10 +1653,20 @@ msgstr ""
msgid "Resolved"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/routes/system/charts/monitors-charts.tsx
msgid "Response"
msgstr ""
#: src/components/network-monitors-table/network-monitors-table.tsx
msgid "Response time monitoring from agents."
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Restarts"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Resume"
msgstr ""
@@ -1539,6 +1699,7 @@ msgid "S.M.A.R.T. Self-Test"
msgstr ""
#: src/components/add-system.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Save {foo}"
msgstr ""
@@ -1575,6 +1736,11 @@ msgstr ""
msgid "Search for systems or settings..."
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Search systems"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Seconds between pings (default: 60)"
msgstr ""
@@ -1587,6 +1753,27 @@ msgstr ""
msgid "Select {foo}"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Select a system"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Select all"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Select matches"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Select row"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Select systems"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Send a single heartbeat ping to verify your endpoint is working."
msgstr ""
@@ -1692,6 +1879,8 @@ msgstr ""
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1716,6 +1905,8 @@ msgid "Systemd Services"
msgstr ""
#: src/components/navbar.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Systems"
msgstr ""
@@ -1732,6 +1923,11 @@ msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Target"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr ""
@@ -1779,6 +1975,7 @@ msgstr ""
msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database."
msgstr ""
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
msgid "This will permanently delete all selected records from the database."
msgstr ""
@@ -1901,6 +2098,10 @@ msgstr ""
msgid "Triggers when memory usage exceeds a threshold"
msgstr ""
#: src/lib/alerts.ts
msgid "Triggers when one hour loss exceeds a threshold"
msgstr ""
#: src/lib/alerts.ts
msgid "Triggers when status switches between up and down"
msgstr ""
@@ -1960,6 +2161,7 @@ msgid "Update"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: tr\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:45\n"
"PO-Revision-Date: 2026-09-18 19:22\n"
"Last-Translator: \n"
"Language-Team: Turkish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: ug\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"PO-Revision-Date: 2026-09-18 19:22\n"
"Last-Translator: \n"
"Language-Team: Uyghur\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -30,6 +30,12 @@ msgstr ""
msgid "{0} of {1} row(s) selected."
msgstr ""
#. placeholder {0}: selectedSystemIds.size
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "{0} selected"
msgstr ""
#: src/components/routes/system/info-bar.tsx
msgid "{cores, plural, one {# core} other {# cores}}"
msgstr ""
@@ -121,6 +127,10 @@ msgstr ""
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
@@ -188,6 +198,7 @@ msgstr ""
msgid "Are you sure you want to delete {name}?"
msgstr ""
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Are you sure?"
msgstr ""
@@ -245,6 +256,14 @@ msgstr ""
msgid "Average utilization of GPU engines"
msgstr ""
#: src/components/routes/system/charts/monitors-charts.tsx
msgid "Average, minimum, and maximum response time"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Avg 1h"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Backups"
@@ -306,6 +325,19 @@ msgstr ""
msgid "Boot state"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Bulk Add"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Bulk Add {foo}"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Bulk copy"
msgstr ""
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)"
@@ -327,6 +359,7 @@ msgstr ""
msgid "Can stop"
msgstr ""
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -396,6 +429,7 @@ msgid "Checksum errors"
msgstr ""
#: src/components/containers-table/containers-table.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
@@ -403,6 +437,14 @@ msgstr ""
msgid "Clear"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Clear all"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Clear matches"
msgstr ""
#: src/components/containers-table/containers-table.tsx
msgid "Click on a container to view more information."
msgstr ""
@@ -432,6 +474,10 @@ msgstr ""
msgid "Configure how you receive alert notifications."
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Configure response monitoring from this agent."
msgstr ""
#: src/components/login/auth-form.tsx
#: src/components/login/auth-form.tsx
msgid "Confirm password"
@@ -457,6 +503,7 @@ msgstr ""
msgid "Containers"
msgstr ""
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -517,6 +564,10 @@ msgstr ""
msgid "Copy the<0>docker-compose.yml</0> content for the agent below, or register agents automatically with a <1>universal token</1>."
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Copy to system"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy YAML"
msgstr ""
@@ -630,6 +681,7 @@ msgstr ""
msgid "Default time period"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
@@ -718,12 +770,14 @@ msgstr ""
msgid "Duration"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Edit"
msgstr ""
#: src/components/add-system.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Edit {foo}"
msgstr ""
@@ -772,6 +826,11 @@ msgstr ""
msgid "Ephemeral"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/heartbeat.tsx
@@ -844,6 +903,11 @@ msgstr ""
msgid "Failed to authenticate"
msgstr ""
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
msgid "Failed to delete monitors."
msgstr ""
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -862,6 +926,10 @@ msgstr ""
msgid "Failed to update alert"
msgstr ""
#: src/components/network-monitors-table/network-monitors-table.tsx
msgid "Failed to update monitors."
msgstr ""
#. placeholder {0}: statusTotals[ServiceStatus.Failed]
#: src/components/systemd-table/systemd-table.tsx
msgid "Failed: {0}"
@@ -872,6 +940,7 @@ msgid "Fans"
msgstr ""
#: src/components/containers-table/containers-table.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
@@ -1011,14 +1080,25 @@ msgctxt "Docker image"
msgid "Image"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Docker image"
msgid "Image update available"
msgstr ""
#: src/components/routes/settings/quiet-hours.tsx
msgid "Inactive"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/routes/settings/heartbeat.tsx
msgid "Interval"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Interval (seconds)"
msgstr ""
#: src/components/login/auth-form.tsx
msgid "Invalid email address."
msgstr ""
@@ -1097,6 +1177,15 @@ msgstr ""
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
msgstr ""
#: src/components/routes/system/charts/monitors-charts.tsx
msgctxt "Packet loss"
msgid "Loss"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Loss 1h"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Main PID"
msgstr ""
@@ -1115,6 +1204,10 @@ msgstr ""
msgid "Max 1 min"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Max 1h"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/info-bar.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
@@ -1141,11 +1234,25 @@ msgstr ""
msgid "Memory usage of containers"
msgstr "كونتېينېرلارنىڭ ئەسلەك ئىشلىتىشى"
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Min 1h"
msgstr ""
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Monitor"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Monitors created"
msgstr ""
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr ""
@@ -1163,6 +1270,29 @@ msgstr ""
msgid "Net"
msgstr ""
#: src/components/routes/system.tsx
msgid "Network"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Network Monitor"
msgstr ""
#: src/lib/alerts.ts
msgid "Network Monitor Loss"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/monitors.tsx
msgid "Network Monitors"
msgstr ""
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of containers"
msgstr "كونتېينېرلارنىڭ تور ئېقىمى"
@@ -1197,6 +1327,7 @@ msgstr ""
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
@@ -1207,6 +1338,7 @@ msgstr ""
msgid "No S.M.A.R.T. attributes available for this device."
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "No systems found."
@@ -1238,6 +1370,11 @@ msgstr ""
msgid "One or more containers are unhealthy"
msgstr ""
#. placeholder {0}: alert.value
#: src/components/active-alerts.tsx
msgid "One or more monitors exceed {0}% loss"
msgstr ""
#: src/lib/alerts.ts
msgid "One or more services are in a failed state"
msgstr ""
@@ -1251,6 +1388,7 @@ msgstr ""
msgid "One-time password"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
@@ -1272,6 +1410,11 @@ msgstr ""
msgid "Overwrite existing alerts"
msgstr ""
#: src/components/routes/system/charts/monitors-charts.tsx
msgid "Packet loss (%)"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
@@ -1309,6 +1452,7 @@ msgstr ""
msgid "Past"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Pause"
msgstr ""
@@ -1392,6 +1536,7 @@ msgid "Pool Usage"
msgstr ""
#: src/components/add-system.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Port"
msgstr ""
@@ -1418,6 +1563,11 @@ msgstr ""
msgid "Process started"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Protocol"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Public key"
msgstr ""
@@ -1503,10 +1653,20 @@ msgstr ""
msgid "Resolved"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/routes/system/charts/monitors-charts.tsx
msgid "Response"
msgstr ""
#: src/components/network-monitors-table/network-monitors-table.tsx
msgid "Response time monitoring from agents."
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Restarts"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Resume"
msgstr ""
@@ -1539,6 +1699,7 @@ msgid "S.M.A.R.T. Self-Test"
msgstr ""
#: src/components/add-system.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Save {foo}"
msgstr ""
@@ -1575,6 +1736,11 @@ msgstr ""
msgid "Search for systems or settings..."
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Search systems"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Seconds between pings (default: 60)"
msgstr ""
@@ -1587,6 +1753,27 @@ msgstr ""
msgid "Select {foo}"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Select a system"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Select all"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Select matches"
msgstr ""
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Select row"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Select systems"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Send a single heartbeat ping to verify your endpoint is working."
msgstr ""
@@ -1692,6 +1879,8 @@ msgstr ""
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1716,6 +1905,8 @@ msgid "Systemd Services"
msgstr ""
#: src/components/navbar.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/monitor-dialog.tsx
msgid "Systems"
msgstr ""
@@ -1732,6 +1923,11 @@ msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr ""
#: src/components/network-monitors-table/monitor-dialog.tsx
#: src/components/network-monitors-table/network-monitors-columns.tsx
msgid "Target"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr ""
@@ -1779,6 +1975,7 @@ msgstr ""
msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database."
msgstr ""
#: src/components/network-monitors-table/network-monitors-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
msgid "This will permanently delete all selected records from the database."
msgstr ""
@@ -1901,6 +2098,10 @@ msgstr ""
msgid "Triggers when memory usage exceeds a threshold"
msgstr ""
#: src/lib/alerts.ts
msgid "Triggers when one hour loss exceeds a threshold"
msgstr ""
#: src/lib/alerts.ts
msgid "Triggers when status switches between up and down"
msgstr ""
@@ -1960,6 +2161,7 @@ msgid "Update"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/network-monitors-table/network-monitors-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: uk\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:45\n"
"PO-Revision-Date: 2026-09-18 19:22\n"
"Last-Translator: \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"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: uz\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:46\n"
"PO-Revision-Date: 2026-09-18 19:22\n"
"Last-Translator: \n"
"Language-Team: Uzbek\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: vi\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-16 10:11\n"
"PO-Revision-Date: 2026-09-18 19:22\n"
"Last-Translator: \n"
"Language-Team: Vietnamese\n"
"Plural-Forms: nplurals=1; plural=0;\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: zh\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:45\n"
"PO-Revision-Date: 2026-09-18 19:22\n"
"Last-Translator: \n"
"Language-Team: Chinese Simplified\n"
"Plural-Forms: nplurals=1; plural=0;\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: zh\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:46\n"
"PO-Revision-Date: 2026-09-18 19:22\n"
"Last-Translator: \n"
"Language-Team: Chinese Traditional, Hong Kong\n"
"Plural-Forms: nplurals=1; plural=0;\n"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: zh\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-10 01:45\n"
"PO-Revision-Date: 2026-09-18 19:22\n"
"Last-Translator: \n"
"Language-Team: Chinese Traditional\n"
"Plural-Forms: nplurals=1; plural=0;\n"

View File

@@ -15,9 +15,10 @@ It has a friendly web interface, simple configuration, and is ready to use out o
- **Lightweight**: Smaller and less resource-intensive than leading solutions.
- **Simple**: Easy setup with little manual configuration required.
- **Alerts**: Configurable alerts for most metrics. Supports many notification services.
- **Docker stats**: Tracks CPU, memory, and network usage history for each container.
- **ZFS**: Tracks pool capacity, health, and I/O, plus per-dataset usage.
- **Alerts**: Configurable alerts for CPU, memory, disk, bandwidth, temperature, fan speed, load average, and status.
- **Network monitoring**: Monitor response time and interruptions directly from agents.
- **S.M.A.R.T.**: Disk health data and notifications on drive failure.
- **Multi-user**: Users manage their own systems. Admins can share systems across users.
- **OAuth / OIDC**: Supports many OAuth2 providers. Password auth can be disabled.
- **Automatic backups**: Save to and restore from disk or S3-compatible storage.
@@ -51,7 +52,7 @@ The [quick start guide](https://beszel.dev/guide/getting-started) and other docu
- **Temperature** - Host system sensors.
- **Fan speed** - Host system sensors (Linux, via `/sys/class/hwmon`).
- **GPU usage / power draw** - Nvidia, AMD, and Intel.
- **Battery** - Host system battery charge.
- **Battery charge** - Host system and some peripherals.
- **Containers** - Status and metrics of all running Docker / Podman containers.
- **S.M.A.R.T.** - Host system disk health (includes eMMC wear/EOL and Linux mdraid array health via sysfs when available).
- **ZFS** - Pool capacity, usage, health, I/O throughput, scrub status, and per-dataset usage.

View File

@@ -1,3 +1,39 @@
## 0.20.0
- Add network monitoring from agents (#2266, #1911)
- Add Docker image update available flag (#2211)
- Add btrfs filesystem reporting as storage pools (#2315)
- Add persistence of view preferences and language to user settings (#1831)
- Add `TRUSTED_PROXY_IPS` allowlist for `TRUSTED_AUTH_HEADER` (#2327)
- Add ZFS utilities to Intel and NVIDIA agent images (#2288, #2311)
- Revert SMART warnings for certain attributes (#2296, #2308, #2347)
- Improve NVMe data units display as human-readable GB/TB (#2303)
- Fix agent disconnects during slow collections by extending WebSocket deadline (#2294)
- Fix missing root CA certificates in base agent image (#2291)
- Fix false RAID health warnings during healthy data scrubbing (#2109)
- Fix ZFS monitoring when /dev/zfs is unavailable (#2325)
- Fix spurious `HUB_URL` warning in SSH-only mode (#2316)
- Fix idle GPU utilization display in systems table (#2312)
- Fix session handling to clear auth store after token expiry (#2310)
- Fix chart history handling when switching to live charts (#2333)
- Update Go dependencies
## 0.19.0
- **Potential breaking change:** Agents now verify HTTPS certificates. If an agent connects to a hub using a self-signed or otherwise untrusted certificate, configure `CA_CERT_FILE` with the appropriate CA certificate or the connection will be rejected.

View File

@@ -2,9 +2,9 @@ apiVersion: v1
description: Installs beszel-agent in kubernetes
home: https://github.com/henrygd/beszel/tree/main/supplemental/helm/beszel-agent
name: beszel-agent
appVersion: "0.19.0"
appVersion: "0.20.0"
# Bump this version when publishing chart changes.
version: 0.1.6
version: 0.1.7
sources:
- https://github.com/henrygd/beszel/tree/main/supplemental/helm/beszel-agent
- https://www.beszel.dev/

View File

@@ -80,7 +80,7 @@ Essential parameters to configure:
| `secret.sshKey` | `ssh-key` | Key name in the secret for the SSH public key |
| `secret.tokenKey` | `token` | Key name in the secret for the authentication token |
| `image.repository` | `henrygd/beszel-agent` | Container image |
| `image.tag` | Chart AppVersion (0.19.0) | Image version |
| `image.tag` | Chart AppVersion (0.20.0) | Image version |
| `hostNetwork` | `false` | Use host network for network monitoring |
| `tolerations` | Allows all taints | Tolerations for running on tainted nodes |
@@ -385,7 +385,7 @@ helm upgrade beszel-agent ./beszel-agent \
# Change image version
helm upgrade beszel-agent ./beszel-agent \
--set image.tag="0.19.0"
--set image.tag="0.20.0"
```
### Restart All Agents
@@ -522,7 +522,7 @@ kubectl get secret beszel-agent -o jsonpath='{.data.ssh-key}' | base64 -d
## Chart Information
- **Chart Version**: 0.1.0
- **App Version**: 0.19.0
- **App Version**: 0.20.0
- **Kubernetes Version**: 1.19+
- **Maintainer**: cloudwithdan (nikoloskid@pm.me)

View File

@@ -2,9 +2,9 @@ apiVersion: v1
description: Installs beszel-hub in kubernetes
home: https://github.com/henrygd/beszel/tree/main/supplemental/helm/beszel-hub
name: beszel-hub
appVersion: "0.19.0"
appVersion: "0.20.0"
# Bump this version when publishing chart changes.
version: 0.1.6
version: 0.1.7
sources:
- https://github.com/henrygd/beszel/tree/main/supplemental/helm/beszel-hub
- https://www.beszel.dev/

View File

@@ -47,7 +47,7 @@ Key configuration options in `values.yaml`:
|-----------|---------|-------------|
| `replicaCount` | `1` | Number of Beszel Hub replicas |
| `image.repository` | `henrygd/beszel` | Container image repository |
| `image.tag` | Chart AppVersion (0.19.0) | Container image tag |
| `image.tag` | Chart AppVersion (0.20.0) | Container image tag |
| `image.pullPolicy` | `IfNotPresent` | Image pull policy |
| `service.port` | `8090` | Service port |
| `persistentVolumeClaim.enabled` | `true` | Enable persistent volume |
@@ -169,7 +169,7 @@ tolerations:
```yaml
replicaCount: 3
image:
tag: "0.19.0"
tag: "0.20.0"
service:
type: LoadBalancer
ingress:
@@ -330,7 +330,7 @@ By default, Beszel Hub uses a PersistentVolumeClaim for data storage. Ensure you
## Chart Information
- **Chart Version**: 0.1.0
- **App Version**: 0.19.0
- **App Version**: 0.20.0
- **Kubernetes Version**: 1.19+
- **Maintainer**: cloudwithdan (nikoloskid@pm.me)

View File

@@ -298,6 +298,94 @@ warn() {
echo "Warning: $*" >&2
}
# Keep IDs within 16 bits, including on older OpenWrt versions whose account
# helpers start automatic allocation at 65536.
openwrt_unused_id() {
awk -F: '
{ used[$3] = 1 }
END {
for (id = 32768; id < 65534; id++) {
if (!(id in used)) { print id; exit }
}
exit 1
}
' "$1"
}
validate_openwrt_account() {
# Duplicate numeric IDs share permissions even when the names differ.
for account_file in /etc/passwd /etc/group; do
if ! awk -F: '
$1 == "beszel" { id = $3; count++ }
{ ids[$3]++ }
END { if (count && (count != 1 || id == 0 || ids[id] != 1)) exit 1 }
' "$account_file"; then
fail "The beszel account has a duplicate or root ID in $account_file. Stop beszel-agent and assign beszel an unused UID/GID before reinstalling. Update ownership only in $AGENT_DIR; do not change all files owned by the shared ID."
fi
done
if grep -q '^beszel:' /etc/passwd; then
account_gid=$(awk -F: '$1 == "beszel" { print $4 }' /etc/passwd)
group_gid=$(awk -F: '$1 == "beszel" { print $3 }' /etc/group)
[ "$account_gid" = "$group_gid" ] || fail "The beszel user's primary group is not the dedicated beszel group. Repair the account before reinstalling."
fi
}
configure_openwrt_account() (
validate_openwrt_account
[ -r /lib/functions.sh ] || fail "OpenWrt account helpers (/lib/functions.sh) are required."
# OpenWrt's library expects unset variables and defines generic functions.
# Source it in a subshell so neither affects the rest of the installer.
set +u
. /lib/functions.sh
IPKG_INSTROOT=""
if ! grep -q '^beszel:' /etc/group; then
account_gid=$(openwrt_unused_id /etc/group) || fail "No unused service GID available."
group_add beszel "$account_gid" || fail "Could not create the beszel group."
fi
account_gid=$(awk -F: '$1 == "beszel" { print $3 }' /etc/group)
[ -n "$account_gid" ] || fail "The beszel group was not created."
if ! grep -q '^beszel:' /etc/passwd; then
account_uid=$(openwrt_unused_id /etc/passwd) || fail "No unused service UID available."
user_add beszel "$account_uid" "$account_gid" "Beszel agent" /nonexistent /bin/false || fail "Could not create the beszel user."
fi
grep -q '^beszel:' /etc/passwd || fail "The beszel account is incomplete."
validate_openwrt_account
# Previous installers omitted the shadow entry. Repair it with a locked
# password, without rewriting an existing account or changing its IDs.
if ! grep -q '^beszel:' /etc/shadow; then
lock /var/lock/passwd || fail "Could not lock the account database."
shadow_status=0
if ! grep -q '^beszel:' /etc/shadow; then
printf 'beszel:!:0:0:99999:7:::\n' >> /etc/shadow || shadow_status=$?
fi
lock -u /var/lock/passwd || fail "Could not unlock the account database."
[ "$shadow_status" -eq 0 ] || fail "Could not create the beszel shadow entry."
fi
if grep -q '^docker:' /etc/group; then
# Match complete member names and avoid a leading comma for empty groups.
if ! awk -F: '$1 == "docker" { n = split($4, members, ","); for (i = 1; i <= n; i++) if (members[i] == "beszel") found = 1 } END { exit !found }' /etc/group; then
echo "Adding beszel to docker group"
if grep -q '^docker:[^:]*:[^:]*:$' /etc/group; then
sed -i '/^docker:/s/$/beszel/' /etc/group
else
sed -i '/^docker:/s/$/,beszel/' /etc/group
fi
fi
fi
)
remove_openwrt_account() {
if command -v userdel >/dev/null 2>&1; then
userdel beszel || fail "Could not remove the beszel user."
elif command -v deluser >/dev/null 2>&1; then
deluser beszel || fail "Could not remove the beszel user."
else
warn "Neither userdel nor deluser is available; the beszel account has been retained."
fi
}
require_value() {
[ "$#" -ge 2 ] && [ -n "$2" ] || fail "Option $1 requires a value."
}
@@ -672,7 +760,9 @@ if [ "$UNINSTALL" = true ]; then
echo "Removing the dedicated user for the agent service..."
killall beszel-agent 2>/dev/null || true # Usually already stopped by the service manager.
if id -u beszel >/dev/null 2>&1; then
if is_alpine || is_openwrt; then
if is_openwrt; then
remove_openwrt_account
elif is_alpine; then
deluser beszel || fail "Could not remove the beszel user."
elif is_freebsd; then
pw user del beszel || fail "Could not remove the beszel user."
@@ -772,32 +862,7 @@ if is_alpine; then
fi
elif is_openwrt; then
# Create beszel group first if it doesn't exist (check /etc/group directly)
if ! grep -q "^beszel:" /etc/group >/dev/null 2>&1; then
echo "beszel:x:999:" >> /etc/group
fi
# Create beszel user if it doesn't exist (double-check to prevent duplicates)
if ! id -u beszel >/dev/null 2>&1 && ! grep -q "^beszel:" /etc/passwd >/dev/null 2>&1; then
echo "beszel:x:999:999::/nonexistent:/bin/false" >> /etc/passwd
fi
# Add the user to the docker group if docker group exists and user is not already in it
if grep -q "^docker:" /etc/group >/dev/null 2>&1; then
echo "Adding beszel to docker group"
# Check if beszel is already in docker group
if ! grep "^docker:" /etc/group | grep -q "beszel"; then
# Add beszel to docker group by modifying /etc/group
# Handle both cases: group with existing members and group without members
if grep "^docker:" /etc/group | grep -q ":.*:.*$"; then
# Group has existing members, append with comma
sed -i 's/^docker:\([^:]*:[^:]*:\)\(.*\)$/docker:\1\2,beszel/' /etc/group
else
# Group has no members, just append
sed -i 's/^docker:\([^:]*:[^:]*:\)$/docker:\1beszel/' /etc/group
fi
fi
fi
configure_openwrt_account
elif is_freebsd; then
if is_opnsense || is_pfsense; then