mirror of
https://github.com/henrygd/beszel.git
synced 2026-09-17 05:24:31 +00:00
Compare commits
9
Commits
l10n_main_2
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0f1f7985c | ||
|
|
50f6fc075d | ||
|
|
a0bf338796 | ||
|
|
982101743e | ||
|
|
6a7b2772d9 | ||
|
|
086091a0fe | ||
|
|
f204dc17e6 | ||
|
|
5fe1583655 | ||
|
|
6d82ee70b1 |
+6
-1
@@ -30,6 +30,11 @@ const (
|
|||||||
wsDeadline = 120 * time.Second
|
wsDeadline = 120 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// errNoHubURL is returned when HUB_URL is unset. This is not a failure
|
||||||
|
// condition: an agent configured with only a public key runs in SSH-only mode,
|
||||||
|
// where the hub dials the agent and no outbound WebSocket client is expected.
|
||||||
|
var errNoHubURL = errors.New("HUB_URL environment variable not set")
|
||||||
|
|
||||||
type caCertFileError struct {
|
type caCertFileError struct {
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
@@ -63,7 +68,7 @@ type WebSocketClient struct {
|
|||||||
func newWebSocketClient(agent *Agent) (client *WebSocketClient, err error) {
|
func newWebSocketClient(agent *Agent) (client *WebSocketClient, err error) {
|
||||||
hubURLStr, exists := utils.GetEnv("HUB_URL")
|
hubURLStr, exists := utils.GetEnv("HUB_URL")
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, errors.New("HUB_URL environment variable not set")
|
return nil, errNoHubURL
|
||||||
}
|
}
|
||||||
|
|
||||||
client = &WebSocketClient{}
|
client = &WebSocketClient{}
|
||||||
|
|||||||
@@ -32,6 +32,28 @@ import (
|
|||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TestNewWebSocketClientNoHubURL verifies that an unset HUB_URL returns the
|
||||||
|
// errNoHubURL sentinel rather than an opaque error. Callers rely on this to
|
||||||
|
// distinguish SSH-only mode -- a supported configuration in which the hub dials
|
||||||
|
// the agent -- from an actual misconfiguration.
|
||||||
|
func TestNewWebSocketClientNoHubURL(t *testing.T) {
|
||||||
|
agent := createTestAgent(t)
|
||||||
|
|
||||||
|
// t.Setenv registers restoration of the original value; unset afterwards so
|
||||||
|
// GetEnv's LookupEnv reports the variable as absent rather than empty.
|
||||||
|
t.Setenv("BESZEL_AGENT_HUB_URL", "")
|
||||||
|
os.Unsetenv("BESZEL_AGENT_HUB_URL")
|
||||||
|
t.Setenv("HUB_URL", "")
|
||||||
|
os.Unsetenv("HUB_URL")
|
||||||
|
t.Setenv("BESZEL_AGENT_TOKEN", "test-token")
|
||||||
|
|
||||||
|
client, err := newWebSocketClient(agent)
|
||||||
|
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Nil(t, client)
|
||||||
|
assert.ErrorIs(t, err, errNoHubURL)
|
||||||
|
}
|
||||||
|
|
||||||
// TestNewWebSocketClient tests WebSocket client creation
|
// TestNewWebSocketClient tests WebSocket client creation
|
||||||
func TestNewWebSocketClient(t *testing.T) {
|
func TestNewWebSocketClient(t *testing.T) {
|
||||||
agent := createTestAgent(t)
|
agent := createTestAgent(t)
|
||||||
|
|||||||
@@ -91,7 +91,15 @@ func (c *ConnectionManager) Start(serverOptions ServerOptions) error {
|
|||||||
if errors.As(err, &caCertErr) {
|
if errors.As(err, &caCertErr) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
slog.Warn("Error creating WebSocket client", "err", err)
|
disableSSH, _ := utils.GetEnv("DISABLE_SSH")
|
||||||
|
if errors.Is(err, errNoHubURL) && disableSSH != "true" {
|
||||||
|
// SSH-only mode: the hub dials the agent, so there is nothing to warn
|
||||||
|
// about. With SSH also disabled there is no connection method at all,
|
||||||
|
// so that case still warns.
|
||||||
|
slog.Debug("WebSocket client not configured", "err", err)
|
||||||
|
} else {
|
||||||
|
slog.Warn("Error creating WebSocket client", "err", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
c.wsClient = wsClient
|
c.wsClient = wsClient
|
||||||
|
|
||||||
|
|||||||
+22
-8
@@ -65,10 +65,14 @@ type dockerManager struct {
|
|||||||
dockerVersionChecked bool // Whether a version probe has completed successfully
|
dockerVersionChecked bool // Whether a version probe has completed successfully
|
||||||
isWindows bool // Whether the Docker Engine API is running on Windows
|
isWindows bool // Whether the Docker Engine API is running on Windows
|
||||||
buf *bytes.Buffer // Buffer to store and read response bodies
|
buf *bytes.Buffer // Buffer to store and read response bodies
|
||||||
apiStats *container.ApiStats // Reusable API stats object
|
|
||||||
excludeContainers []string // Patterns to exclude containers by name
|
excludeContainers []string // Patterns to exclude containers by name
|
||||||
usingPodman bool // Whether the Docker Engine API is running on Podman
|
usingPodman bool // Whether the Docker Engine API is running on Podman
|
||||||
|
|
||||||
|
registryClient *http.Client // Client for registry requests; nil uses a client with a 10-second timeout
|
||||||
|
imageUpdatesMutex sync.RWMutex // Protects imageUpdates, its entries, and imageUpdatesRunning
|
||||||
|
imageUpdates map[string]*imageUpdateStatus // Shared update status keyed by normalized image reference
|
||||||
|
imageUpdatesRunning bool // Whether a background image-update batch is in progress
|
||||||
|
|
||||||
// Cache-time-aware tracking for CPU stats (similar to cpu.go)
|
// Cache-time-aware tracking for CPU stats (similar to cpu.go)
|
||||||
// Maps cache time intervals to container-specific CPU usage tracking
|
// Maps cache time intervals to container-specific CPU usage tracking
|
||||||
lastCpuContainer map[uint16]map[string]uint64 // cacheTimeMs -> containerId -> last cpu container usage
|
lastCpuContainer map[uint16]map[string]uint64 // cacheTimeMs -> containerId -> last cpu container usage
|
||||||
@@ -161,6 +165,9 @@ func (dm *dockerManager) getDockerStats(cacheTimeMs uint16) ([]*container.Stats,
|
|||||||
clear(dm.validIds)
|
clear(dm.validIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only schedule auxiliary work here; metrics never wait for image discovery.
|
||||||
|
dm.refreshImageUpdates(dm.apiContainerList, time.Now())
|
||||||
|
|
||||||
var failedContainers []*container.ApiInfo
|
var failedContainers []*container.ApiInfo
|
||||||
|
|
||||||
for _, ctr := range dm.apiContainerList {
|
for _, ctr := range dm.apiContainerList {
|
||||||
@@ -506,6 +513,17 @@ func (dm *dockerManager) updateContainerStats(ctr *container.ApiInfo, cacheTimeM
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read and decode the response before locking shared stats to avoid blocking
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("container stats request failed: %s", resp.Status)
|
||||||
|
}
|
||||||
|
res := &container.ApiStats{}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(res); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
updateAvailable := dm.cachedImageUpdate(ctr.Image)
|
||||||
|
|
||||||
dm.containerStatsMutex.Lock()
|
dm.containerStatsMutex.Lock()
|
||||||
defer dm.containerStatsMutex.Unlock()
|
defer dm.containerStatsMutex.Unlock()
|
||||||
|
|
||||||
@@ -520,6 +538,9 @@ func (dm *dockerManager) updateContainerStats(ctr *container.ApiInfo, cacheTimeM
|
|||||||
stats.Status = statusText
|
stats.Status = statusText
|
||||||
stats.Health = health
|
stats.Health = health
|
||||||
|
|
||||||
|
stats.Image = ctr.Image
|
||||||
|
stats.UpdateAvailable = updateAvailable
|
||||||
|
|
||||||
if len(ctr.Ports) > 0 {
|
if len(ctr.Ports) > 0 {
|
||||||
stats.Ports = convertContainerPortsToString(ctr)
|
stats.Ports = convertContainerPortsToString(ctr)
|
||||||
}
|
}
|
||||||
@@ -532,12 +553,6 @@ func (dm *dockerManager) updateContainerStats(ctr *container.ApiInfo, cacheTimeM
|
|||||||
stats.NetworkSent = 0
|
stats.NetworkSent = 0
|
||||||
stats.NetworkRecv = 0
|
stats.NetworkRecv = 0
|
||||||
|
|
||||||
res := dm.apiStats
|
|
||||||
res.Networks = nil
|
|
||||||
if err := dm.decode(resp, res); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize CPU tracking for this cache time interval
|
// Initialize CPU tracking for this cache time interval
|
||||||
dm.initializeCpuTracking(cacheTimeMs)
|
dm.initializeCpuTracking(cacheTimeMs)
|
||||||
|
|
||||||
@@ -695,7 +710,6 @@ func newDockerManager(agent *Agent) *dockerManager {
|
|||||||
containerStatsMap: make(map[string]*container.Stats),
|
containerStatsMap: make(map[string]*container.Stats),
|
||||||
sem: make(chan struct{}, 5),
|
sem: make(chan struct{}, 5),
|
||||||
apiContainerList: []*container.ApiInfo{},
|
apiContainerList: []*container.ApiInfo{},
|
||||||
apiStats: &container.ApiStats{},
|
|
||||||
excludeContainers: excludeContainers,
|
excludeContainers: excludeContainers,
|
||||||
|
|
||||||
// Initialize cache-time-aware tracking structures
|
// Initialize cache-time-aware tracking structures
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
|
"github.com/henrygd/beszel/internal/entities/container"
|
||||||
|
)
|
||||||
|
|
||||||
|
const imageUpdateInterval = time.Hour
|
||||||
|
|
||||||
|
type imageUpdateStatus struct {
|
||||||
|
available bool
|
||||||
|
checkedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizedImageReference(image string) string {
|
||||||
|
named, err := reference.ParseNormalizedNamed(image)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
// Digest-pinned references cannot move to a new version.
|
||||||
|
if _, pinned := named.(reference.Digested); pinned {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return reference.TagNameOnly(named).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// refreshImageUpdates starts at most one background batch. Neither its network
|
||||||
|
// work nor its completion is part of the container metrics wait group.
|
||||||
|
func (dm *dockerManager) refreshImageUpdates(containers []*container.ApiInfo, now time.Time) {
|
||||||
|
dm.imageUpdatesMutex.Lock()
|
||||||
|
defer dm.imageUpdatesMutex.Unlock()
|
||||||
|
if dm.imageUpdatesRunning {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if dm.imageUpdates == nil {
|
||||||
|
dm.imageUpdates = make(map[string]*imageUpdateStatus)
|
||||||
|
}
|
||||||
|
active := make(map[string]struct{}, len(containers))
|
||||||
|
pending := make(map[string]*imageUpdateStatus)
|
||||||
|
for _, ctr := range containers {
|
||||||
|
if len(ctr.Names) > 0 && dm.shouldExcludeContainer(ctr.Names[0][1:]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := normalizedImageReference(ctr.Image)
|
||||||
|
if key == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
active[key] = struct{}{}
|
||||||
|
entry := dm.imageUpdates[key]
|
||||||
|
if entry == nil {
|
||||||
|
entry = &imageUpdateStatus{}
|
||||||
|
dm.imageUpdates[key] = entry
|
||||||
|
}
|
||||||
|
if entry.checkedAt.IsZero() || now.Sub(entry.checkedAt) >= imageUpdateInterval {
|
||||||
|
pending[key] = entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for key := range dm.imageUpdates {
|
||||||
|
if _, ok := active[key]; !ok {
|
||||||
|
delete(dm.imageUpdates, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(pending) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dm.imageUpdatesRunning = true
|
||||||
|
go func() {
|
||||||
|
// Limit auxiliary requests even on hosts running many different images.
|
||||||
|
sem := make(chan struct{}, 2)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for key, entry := range pending {
|
||||||
|
sem <- struct{}{}
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
defer func() { <-sem }()
|
||||||
|
available, err := dm.checkImageUpdate(key)
|
||||||
|
if err != nil {
|
||||||
|
available = false
|
||||||
|
slog.Debug("Image update check failed", "image", key, "err", err)
|
||||||
|
}
|
||||||
|
dm.imageUpdatesMutex.Lock()
|
||||||
|
entry.available = available
|
||||||
|
entry.checkedAt = time.Now()
|
||||||
|
dm.imageUpdatesMutex.Unlock()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
dm.imageUpdatesMutex.Lock()
|
||||||
|
dm.imageUpdatesRunning = false
|
||||||
|
dm.imageUpdatesMutex.Unlock()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dm *dockerManager) cachedImageUpdate(image string) bool {
|
||||||
|
key := normalizedImageReference(image)
|
||||||
|
dm.imageUpdatesMutex.RLock()
|
||||||
|
defer dm.imageUpdatesMutex.RUnlock()
|
||||||
|
entry := dm.imageUpdates[key]
|
||||||
|
return entry != nil && entry.available
|
||||||
|
}
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
//go:build testing
|
||||||
|
|
||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/fxamacker/cbor/v2"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/henrygd/beszel/internal/entities/container"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func waitForImageUpdates(t *testing.T, dm *dockerManager) {
|
||||||
|
t.Helper()
|
||||||
|
require.Eventually(t, func() bool {
|
||||||
|
dm.imageUpdatesMutex.RLock()
|
||||||
|
defer dm.imageUpdatesMutex.RUnlock()
|
||||||
|
return !dm.imageUpdatesRunning
|
||||||
|
}, time.Second*3, time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImageUpdateCacheAndStats(t *testing.T) {
|
||||||
|
local := "sha256:" + strings.Repeat("a", 64)
|
||||||
|
remote := "sha256:" + strings.Repeat("b", 64)
|
||||||
|
var inspections, lookups atomic.Int32
|
||||||
|
var fail atomic.Bool
|
||||||
|
var upToDate atomic.Bool
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(r.URL.Path, "/images/"):
|
||||||
|
inspections.Add(1)
|
||||||
|
fmt.Fprintf(w, `{"RepoDigests":["docker.io/library/nginx@%s"]}`, local)
|
||||||
|
case r.URL.Path == "/containers/json":
|
||||||
|
fmt.Fprint(w, `[{"Id":"aaaaaaaaaaaa","Names":["/one"],"Image":"nginx","Status":"Up 2 hours"},{"Id":"bbbbbbbbbbbb","Names":["/two"],"Image":"docker.io/library/nginx:latest","Status":"Up 2 hours"}]`)
|
||||||
|
case strings.Contains(r.URL.Path, "/stats"):
|
||||||
|
fmt.Fprint(w, `{"memory_stats":{"usage":1048576},"cpu_stats":{},"networks":{}}`)
|
||||||
|
default:
|
||||||
|
http.NotFound(w, r)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
dm := newDockerManagerForVersionTest(server)
|
||||||
|
dm.dockerVersionChecked = true
|
||||||
|
dm.registryClient = &http.Client{Timeout: time.Second, Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||||
|
if fail.Load() {
|
||||||
|
return nil, fmt.Errorf("registry unavailable")
|
||||||
|
}
|
||||||
|
response := &http.Response{StatusCode: 200, Header: make(http.Header), Body: io.NopCloser(strings.NewReader(`{"token":"test"}`))}
|
||||||
|
if r.Method == http.MethodHead {
|
||||||
|
lookups.Add(1)
|
||||||
|
digest := remote
|
||||||
|
if upToDate.Load() {
|
||||||
|
digest = local
|
||||||
|
}
|
||||||
|
response.Header.Set("Docker-Content-Digest", digest)
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
})}
|
||||||
|
stats, err := dm.getDockerStats(defaultCacheTimeMs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, stats, 2)
|
||||||
|
waitForImageUpdates(t, dm)
|
||||||
|
require.EqualValues(t, 1, lookups.Load())
|
||||||
|
require.EqualValues(t, 1, inspections.Load())
|
||||||
|
stats, err = dm.getDockerStats(defaultCacheTimeMs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
for _, stat := range stats {
|
||||||
|
require.True(t, stat.UpdateAvailable)
|
||||||
|
if stat.Id == "aaaaaaaaaaaa" {
|
||||||
|
require.Equal(t, "nginx", stat.Image)
|
||||||
|
} else {
|
||||||
|
require.Equal(t, "docker.io/library/nginx:latest", stat.Image)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.EqualValues(t, 1, lookups.Load())
|
||||||
|
|
||||||
|
expire := func() {
|
||||||
|
dm.imageUpdatesMutex.Lock()
|
||||||
|
dm.imageUpdates["docker.io/library/nginx:latest"].checkedAt = time.Now().Add(-imageUpdateInterval)
|
||||||
|
dm.imageUpdatesMutex.Unlock()
|
||||||
|
}
|
||||||
|
upToDate.Store(true)
|
||||||
|
expire()
|
||||||
|
_, err = dm.getDockerStats(defaultCacheTimeMs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
waitForImageUpdates(t, dm)
|
||||||
|
require.EqualValues(t, 2, lookups.Load())
|
||||||
|
require.False(t, dm.cachedImageUpdate("nginx:latest"))
|
||||||
|
|
||||||
|
// An expired positive result is cleared on failure, and the failure itself
|
||||||
|
// is cached so realtime stats do not retry a broken registry every second.
|
||||||
|
dm.imageUpdatesMutex.Lock()
|
||||||
|
dm.imageUpdates["docker.io/library/nginx:latest"].available = true
|
||||||
|
dm.imageUpdatesMutex.Unlock()
|
||||||
|
fail.Store(true)
|
||||||
|
expire()
|
||||||
|
_, err = dm.getDockerStats(defaultCacheTimeMs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
waitForImageUpdates(t, dm)
|
||||||
|
failedInspections := inspections.Load()
|
||||||
|
stats, err = dm.getDockerStats(defaultCacheTimeMs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, stats, 2)
|
||||||
|
require.Equal(t, failedInspections, inspections.Load())
|
||||||
|
for _, stat := range stats {
|
||||||
|
require.False(t, stat.UpdateAvailable)
|
||||||
|
require.Equal(t, 1.0, stat.Mem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImageDiscoveryDoesNotBlockStats(t *testing.T) {
|
||||||
|
started := make(chan struct{}, 1)
|
||||||
|
release := make(chan struct{})
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if strings.HasPrefix(r.URL.Path, "/images/") {
|
||||||
|
fmt.Fprintf(w, `{"RepoDigests":["example.com/app@sha256:%s"]}`, strings.Repeat("a", 64))
|
||||||
|
} else {
|
||||||
|
fmt.Fprint(w, `{"memory_stats":{"usage":1048576}}`)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
dm := newDockerManagerForVersionTest(server)
|
||||||
|
defer func() { close(release); waitForImageUpdates(t, dm) }()
|
||||||
|
dm.registryClient = &http.Client{Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||||
|
started <- struct{}{}
|
||||||
|
<-release
|
||||||
|
return nil, fmt.Errorf("timeout")
|
||||||
|
})}
|
||||||
|
ctr := &container.ApiInfo{IdShort: "aaaaaaaaaaaa", Image: "example.com/app", Names: []string{"/one"}}
|
||||||
|
dm.refreshImageUpdates([]*container.ApiInfo{ctr}, time.Now())
|
||||||
|
select {
|
||||||
|
case <-started:
|
||||||
|
case <-time.After(3 * time.Second):
|
||||||
|
t.Fatal("check did not start")
|
||||||
|
}
|
||||||
|
done := make(chan error, 1)
|
||||||
|
go func() { done <- dm.updateContainerStats(ctr, defaultCacheTimeMs) }()
|
||||||
|
select {
|
||||||
|
case err := <-done:
|
||||||
|
require.NoError(t, err)
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("registry blocked stats")
|
||||||
|
}
|
||||||
|
dm.imageUpdatesMutex.RLock()
|
||||||
|
require.True(t, dm.imageUpdatesRunning)
|
||||||
|
dm.imageUpdatesMutex.RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizeImageUpdateReferences(t *testing.T) {
|
||||||
|
require.Equal(t, normalizedImageReference("nginx"), normalizedImageReference("docker.io/library/nginx:latest"))
|
||||||
|
require.Empty(t, normalizedImageReference("bad reference"))
|
||||||
|
require.Empty(t, normalizedImageReference("nginx@sha256:"+strings.Repeat("a", 64)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// A stats request can return headers promptly and then stall while reading its
|
||||||
|
// body. The stats-map mutex must remain available during that read.
|
||||||
|
func TestStatsResponseBodyDoesNotHoldStatsLock(t *testing.T) {
|
||||||
|
started := make(chan struct{})
|
||||||
|
release := make(chan struct{})
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
close(started)
|
||||||
|
<-release
|
||||||
|
fmt.Fprint(w, `{"memory_stats":{"usage":1048576}}`)
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
dm := newDockerManagerForVersionTest(server)
|
||||||
|
done := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
done <- dm.updateContainerStats(&container.ApiInfo{IdShort: "aaaaaaaaaaaa", Names: []string{"/one"}, Image: "nginx"}, defaultCacheTimeMs)
|
||||||
|
}()
|
||||||
|
<-started
|
||||||
|
locked := make(chan struct{})
|
||||||
|
go func() { dm.containerStatsMutex.Lock(); dm.containerStatsMutex.Unlock(); close(locked) }()
|
||||||
|
select {
|
||||||
|
case <-locked:
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
close(release)
|
||||||
|
<-done
|
||||||
|
t.Fatal("Docker response body held the stats mutex")
|
||||||
|
}
|
||||||
|
close(release)
|
||||||
|
require.NoError(t, <-done)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImageUpdateStatsEncoding(t *testing.T) {
|
||||||
|
original := container.Stats{Image: "nginx:latest", UpdateAvailable: true}
|
||||||
|
encoded, err := cbor.Marshal(original)
|
||||||
|
require.NoError(t, err)
|
||||||
|
var fields map[int]any
|
||||||
|
require.NoError(t, cbor.Unmarshal(encoded, &fields))
|
||||||
|
require.Equal(t, true, fields[11])
|
||||||
|
require.Equal(t, "nginx:latest", fields[8])
|
||||||
|
var decoded container.Stats
|
||||||
|
require.NoError(t, cbor.Unmarshal(encoded, &decoded))
|
||||||
|
require.True(t, decoded.UpdateAvailable)
|
||||||
|
require.Equal(t, original.Image, decoded.Image)
|
||||||
|
encoded, err = json.Marshal(original)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Contains(t, string(encoded), `"u":true`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImageUpdateCacheExpiryBoundaryAndPruning(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
key := normalizedImageReference("nginx")
|
||||||
|
dm := &dockerManager{imageUpdates: map[string]*imageUpdateStatus{
|
||||||
|
key: {available: true, checkedAt: now},
|
||||||
|
"unused.example/image:latest": {checkedAt: now},
|
||||||
|
}}
|
||||||
|
dm.refreshImageUpdates([]*container.ApiInfo{{Image: "nginx"}}, now.Add(imageUpdateInterval-time.Nanosecond))
|
||||||
|
require.False(t, dm.imageUpdatesRunning)
|
||||||
|
require.Len(t, dm.imageUpdates, 1)
|
||||||
|
require.True(t, dm.cachedImageUpdate("nginx:latest"))
|
||||||
|
dm.refreshImageUpdates(nil, now)
|
||||||
|
require.Empty(t, dm.imageUpdates)
|
||||||
|
}
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "crypto/sha256"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
|
)
|
||||||
|
|
||||||
|
const imageRegistryTimeout = 10 * time.Second
|
||||||
|
|
||||||
|
const imageManifestAccept = "application/vnd.docker.distribution.manifest.list.v2+json, " +
|
||||||
|
"application/vnd.docker.distribution.manifest.v2+json, " +
|
||||||
|
"application/vnd.oci.image.manifest.v1+json, " +
|
||||||
|
"application/vnd.oci.image.index.v1+json"
|
||||||
|
|
||||||
|
// checkImageUpdate compares the digest recorded by Docker for image with the
|
||||||
|
// digest currently advertised by its registry. A digest-pinned reference is
|
||||||
|
// immutable and therefore never has an update available.
|
||||||
|
func (dm *dockerManager) checkImageUpdate(image string) (bool, error) {
|
||||||
|
named, err := reference.ParseNormalizedNamed(image)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("parse image reference %q: %w", image, err)
|
||||||
|
}
|
||||||
|
if _, pinned := named.(reference.Digested); pinned {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
named = reference.TagNameOnly(named)
|
||||||
|
|
||||||
|
registry := reference.Domain(named)
|
||||||
|
repository := reference.Path(named)
|
||||||
|
tag := named.(reference.Tagged).Tag()
|
||||||
|
|
||||||
|
localDigest, err := dm.inspectImageDigest(image, registry, repository)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteDigest, err := dm.registryImageDigest(registry, repository, tag)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return remoteDigest != localDigest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// inspectImageDigest reads Docker's image metadata without using dm.decode.
|
||||||
|
// The checker runs in the image-discovery goroutine, so it must not hold any
|
||||||
|
// of the container statistics locks while waiting on the Docker API.
|
||||||
|
func (dm *dockerManager) inspectImageDigest(image, registry, repository string) (string, error) {
|
||||||
|
if dm.client == nil {
|
||||||
|
return "", fmt.Errorf("inspect image %q: Docker client is unavailable", image)
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint := "http://localhost/images/" + url.PathEscape(image) + "/json"
|
||||||
|
resp, err := dm.client.Get(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("inspect image %q: %w", image, err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return "", fmt.Errorf("inspect image %q failed: %s", image, responseStatus(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
var inspect struct {
|
||||||
|
RepoDigests []string `json:"RepoDigests"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&inspect); err != nil {
|
||||||
|
return "", fmt.Errorf("decode image inspect %q: %w", image, err)
|
||||||
|
}
|
||||||
|
if len(inspect.RepoDigests) == 0 {
|
||||||
|
return "", fmt.Errorf("inspect image %q returned no repository digests", image)
|
||||||
|
}
|
||||||
|
|
||||||
|
localDigest, ok := matchingRepositoryDigest(inspect.RepoDigests, registry, repository)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("inspect image %q returned no valid digest for %s/%s", image, registry, repository)
|
||||||
|
}
|
||||||
|
return localDigest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchingRepositoryDigest returns a valid digest belonging to the requested
|
||||||
|
// repository. Docker can return multiple RepoDigests for one local image; an
|
||||||
|
// unrelated first entry must never be used for the comparison.
|
||||||
|
func matchingRepositoryDigest(repoDigests []string, registry, repository string) (string, bool) {
|
||||||
|
for _, repoDigest := range repoDigests {
|
||||||
|
repoDigest = strings.TrimSpace(repoDigest)
|
||||||
|
at := strings.LastIndexByte(repoDigest, '@')
|
||||||
|
if at <= 0 || at == len(repoDigest)-1 || strings.Contains(repoDigest[:at], "@") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
repoRef, err := reference.ParseNormalizedNamed(repoDigest[:at])
|
||||||
|
if err != nil || reference.Path(repoRef) != repository || !sameRegistry(reference.Domain(repoRef), registry) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, hasTag := repoRef.(reference.Tagged); hasTag {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := digest.Parse(repoDigest[at+1:])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return d.String(), true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func sameRegistry(left, right string) bool {
|
||||||
|
left = canonicalRegistry(left)
|
||||||
|
right = canonicalRegistry(right)
|
||||||
|
return left == right ||
|
||||||
|
(left == "ghcr.io" && right == "lscr.io") ||
|
||||||
|
(left == "lscr.io" && right == "ghcr.io")
|
||||||
|
}
|
||||||
|
|
||||||
|
func canonicalRegistry(registry string) string {
|
||||||
|
if registry == "index.docker.io" {
|
||||||
|
return "docker.io"
|
||||||
|
}
|
||||||
|
return registry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dm *dockerManager) registryImageDigest(registry, repository, tag string) (string, error) {
|
||||||
|
client := dm.registryClient
|
||||||
|
if client == nil {
|
||||||
|
client = &http.Client{Timeout: imageRegistryTimeout}
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := dm.registryToken(client, registry, repository)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
host := registry
|
||||||
|
if registry == "docker.io" {
|
||||||
|
host = "registry-1.docker.io"
|
||||||
|
}
|
||||||
|
manifestURL := "https://" + host + "/v2/" + repository + "/manifests/" + url.PathEscape(tag)
|
||||||
|
req, err := http.NewRequest(http.MethodHead, manifestURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("create manifest request: %w", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Accept", imageManifestAccept)
|
||||||
|
if token != "" {
|
||||||
|
req.Header.Set("Authorization", "Bearer "+token)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("fetch manifest %s:%s: %w", registry, repository, err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return "", fmt.Errorf("manifest request for %s:%s failed: %s", repository, tag, responseStatus(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
remote := strings.TrimSpace(resp.Header.Get("Docker-Content-Digest"))
|
||||||
|
d, err := digest.Parse(remote)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("manifest request for %s:%s returned invalid digest: %w", repository, tag, err)
|
||||||
|
}
|
||||||
|
return d.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dm *dockerManager) registryToken(client *http.Client, registry, repository string) (string, error) {
|
||||||
|
var authURL string
|
||||||
|
switch registry {
|
||||||
|
case "docker.io":
|
||||||
|
authURL = "https://auth.docker.io/token?service=registry.docker.io&scope=" + url.QueryEscape("repository:"+repository+":pull")
|
||||||
|
case "ghcr.io", "lscr.io":
|
||||||
|
// lscr.io is the LinuxServer alias for its GHCR-backed images.
|
||||||
|
authURL = "https://ghcr.io/token?service=ghcr.io&scope=" + url.QueryEscape("repository:"+repository+":pull")
|
||||||
|
default:
|
||||||
|
// Anonymous registries remain supported, as they were before the
|
||||||
|
// authenticated Docker Hub and GHCR paths were added.
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, authURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("create registry auth request: %w", err)
|
||||||
|
}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("fetch registry auth token for %s: %w", repository, err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return "", fmt.Errorf("registry auth request for %s failed: %s", repository, responseStatus(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokenResponse struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&tokenResponse); err != nil {
|
||||||
|
return "", fmt.Errorf("decode registry auth response for %s: %w", repository, err)
|
||||||
|
}
|
||||||
|
token := strings.TrimSpace(tokenResponse.Token)
|
||||||
|
if token == "" {
|
||||||
|
token = strings.TrimSpace(tokenResponse.AccessToken)
|
||||||
|
}
|
||||||
|
if token == "" {
|
||||||
|
return "", fmt.Errorf("registry auth response for %s contained no token", repository)
|
||||||
|
}
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func responseStatus(resp *http.Response) string {
|
||||||
|
if resp.Status != "" {
|
||||||
|
return resp.Status
|
||||||
|
}
|
||||||
|
return http.StatusText(resp.StatusCode)
|
||||||
|
}
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
//go:build testing
|
||||||
|
|
||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type registryTransportFunc func(*http.Request) (*http.Response, error)
|
||||||
|
|
||||||
|
func (fn registryTransportFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
return fn(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func registryResponse(status int, body string) *http.Response {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: status,
|
||||||
|
Status: fmt.Sprintf("%d %s", status, http.StatusText(status)),
|
||||||
|
Header: make(http.Header),
|
||||||
|
Body: io.NopCloser(strings.NewReader(body)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func registryDigest(fill byte) string {
|
||||||
|
return "sha256:" + strings.Repeat(string(fill), 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRegistryChecker(t *testing.T, inspectBody string, transport http.RoundTripper) *dockerManager {
|
||||||
|
t.Helper()
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if strings.HasPrefix(r.URL.Path, "/images/") {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = io.WriteString(w, inspectBody)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.NotFound(w, r)
|
||||||
|
}))
|
||||||
|
t.Cleanup(server.Close)
|
||||||
|
|
||||||
|
return &dockerManager{
|
||||||
|
client: newDockerManagerForVersionTest(server).client,
|
||||||
|
registryClient: &http.Client{Transport: transport},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckImageUpdateUsesInspectAndManifestDigests(t *testing.T) {
|
||||||
|
local := registryDigest('a')
|
||||||
|
remote := registryDigest('b')
|
||||||
|
var authCalls, manifestCalls atomic.Int32
|
||||||
|
dm := newRegistryChecker(t, fmt.Sprintf(`{"RepoDigests":["docker.io/library/alpine@%s"]}`, local), registryTransportFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
|
switch {
|
||||||
|
case req.Method == http.MethodGet && req.URL.Host == "auth.docker.io":
|
||||||
|
authCalls.Add(1)
|
||||||
|
require.Equal(t, "/token", req.URL.Path)
|
||||||
|
return registryResponse(http.StatusOK, `{"token":"test-token"}`), nil
|
||||||
|
case req.Method == http.MethodHead && req.URL.Host == "registry-1.docker.io":
|
||||||
|
manifestCalls.Add(1)
|
||||||
|
require.Equal(t, "/v2/library/alpine/manifests/latest", req.URL.Path)
|
||||||
|
require.Equal(t, "Bearer test-token", req.Header.Get("Authorization"))
|
||||||
|
resp := registryResponse(http.StatusOK, "")
|
||||||
|
resp.Header.Set("Docker-Content-Digest", remote)
|
||||||
|
return resp, nil
|
||||||
|
default:
|
||||||
|
return registryResponse(http.StatusNotFound, ""), nil
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
available, err := dm.checkImageUpdate("alpine")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, available)
|
||||||
|
require.EqualValues(t, 1, authCalls.Load())
|
||||||
|
require.EqualValues(t, 1, manifestCalls.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckImageUpdateReportsUnknownInspectState(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
body string
|
||||||
|
}{
|
||||||
|
{name: "missing field", body: `{}`},
|
||||||
|
{name: "empty field", body: `{"RepoDigests":[]}`},
|
||||||
|
{name: "malformed reference", body: `{"RepoDigests":["not-a-repo-digest"]}`},
|
||||||
|
{name: "wrong repository", body: `{"RepoDigests":["docker.io/library/busybox@` + registryDigest('a') + `"]}`},
|
||||||
|
{name: "malformed digest", body: `{"RepoDigests":["docker.io/library/alpine@sha256:not-a-digest"]}`},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
var registryCalls atomic.Int32
|
||||||
|
dm := newRegistryChecker(t, test.body, registryTransportFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
|
registryCalls.Add(1)
|
||||||
|
return registryResponse(http.StatusOK, `{"token":"unexpected"}`), nil
|
||||||
|
}))
|
||||||
|
|
||||||
|
available, err := dm.checkImageUpdate("alpine")
|
||||||
|
require.Error(t, err)
|
||||||
|
require.False(t, available)
|
||||||
|
require.EqualValues(t, 0, registryCalls.Load(), "invalid local state must not query a registry")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckImageUpdateChecksInspectAuthAndManifestStatuses(t *testing.T) {
|
||||||
|
local := registryDigest('a')
|
||||||
|
validInspect := fmt.Sprintf(`{"RepoDigests":["docker.io/library/alpine@%s"]}`, local)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
inspectCode int
|
||||||
|
authCode int
|
||||||
|
manifestCode int
|
||||||
|
remote string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{name: "inspect status", inspectCode: http.StatusNotFound, want: "inspect image"},
|
||||||
|
{name: "auth status", inspectCode: http.StatusOK, authCode: http.StatusUnauthorized, want: "registry auth"},
|
||||||
|
{name: "manifest status", inspectCode: http.StatusOK, authCode: http.StatusOK, manifestCode: http.StatusNotFound, remote: local, want: "manifest request"},
|
||||||
|
{name: "missing digest", inspectCode: http.StatusOK, authCode: http.StatusOK, manifestCode: http.StatusOK, want: "invalid digest"},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if test.inspectCode != http.StatusOK && strings.HasPrefix(r.URL.Path, "/images/") {
|
||||||
|
w.WriteHeader(test.inspectCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, _ = io.WriteString(w, validInspect)
|
||||||
|
}))
|
||||||
|
t.Cleanup(server.Close)
|
||||||
|
|
||||||
|
calls := 0
|
||||||
|
dm := &dockerManager{client: newDockerManagerForVersionTest(server).client, registryClient: &http.Client{Transport: registryTransportFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
|
calls++
|
||||||
|
if req.Method == http.MethodGet {
|
||||||
|
return registryResponse(test.authCode, `{"token":"test"}`), nil
|
||||||
|
}
|
||||||
|
response := registryResponse(test.manifestCode, "")
|
||||||
|
response.Header.Set("Docker-Content-Digest", test.remote)
|
||||||
|
return response, nil
|
||||||
|
})}}
|
||||||
|
|
||||||
|
_, err := dm.checkImageUpdate("alpine")
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), test.want)
|
||||||
|
if test.inspectCode != http.StatusOK {
|
||||||
|
require.Zero(t, calls)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckImageUpdateSupportsAnonymousAndLSCRRegistries(t *testing.T) {
|
||||||
|
t.Run("anonymous registry", func(t *testing.T) {
|
||||||
|
local := registryDigest('a')
|
||||||
|
var calls atomic.Int32
|
||||||
|
dm := newRegistryChecker(t, fmt.Sprintf(`{"RepoDigests":["example.com/app@%s"]}`, local), registryTransportFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
|
calls.Add(1)
|
||||||
|
require.Equal(t, http.MethodHead, req.Method)
|
||||||
|
require.Equal(t, "example.com", req.URL.Host)
|
||||||
|
resp := registryResponse(http.StatusOK, "")
|
||||||
|
resp.Header.Set("Docker-Content-Digest", local)
|
||||||
|
return resp, nil
|
||||||
|
}))
|
||||||
|
available, err := dm.checkImageUpdate("example.com/app")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, available)
|
||||||
|
require.EqualValues(t, 1, calls.Load())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("lscr ghcr alias", func(t *testing.T) {
|
||||||
|
local := registryDigest('a')
|
||||||
|
var authCalls, manifestCalls atomic.Int32
|
||||||
|
dm := newRegistryChecker(t, fmt.Sprintf(`{"RepoDigests":["ghcr.io/linuxserver/app@%s"]}`, local), registryTransportFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
|
if req.Method == http.MethodGet {
|
||||||
|
authCalls.Add(1)
|
||||||
|
return registryResponse(http.StatusOK, `{"token":"test"}`), nil
|
||||||
|
}
|
||||||
|
manifestCalls.Add(1)
|
||||||
|
require.Equal(t, "lscr.io", req.URL.Host)
|
||||||
|
resp := registryResponse(http.StatusOK, "")
|
||||||
|
resp.Header.Set("Docker-Content-Digest", local)
|
||||||
|
return resp, nil
|
||||||
|
}))
|
||||||
|
available, err := dm.checkImageUpdate("lscr.io/linuxserver/app")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, available)
|
||||||
|
require.EqualValues(t, 1, authCalls.Load())
|
||||||
|
require.EqualValues(t, 1, manifestCalls.Load())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckImageUpdateSkipsPinnedDigest(t *testing.T) {
|
||||||
|
image := "docker.io/library/alpine@" + registryDigest('a')
|
||||||
|
dm := &dockerManager{}
|
||||||
|
available, err := dm.checkImageUpdate(image)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, available)
|
||||||
|
}
|
||||||
@@ -1184,7 +1184,6 @@ func TestUpdateContainerStatsPodmanCpuCalculation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})},
|
})},
|
||||||
containerStatsMap: make(map[string]*container.Stats),
|
containerStatsMap: make(map[string]*container.Stats),
|
||||||
apiStats: &container.ApiStats{},
|
|
||||||
usingPodman: true,
|
usingPodman: true,
|
||||||
lastCpuContainer: map[uint16]map[string]uint64{
|
lastCpuContainer: map[uint16]map[string]uint64{
|
||||||
defaultCacheTimeMs: {"0123456789ab": prevCpuUsage},
|
defaultCacheTimeMs: {"0123456789ab": prevCpuUsage},
|
||||||
@@ -1676,7 +1675,6 @@ func TestUpdateContainerStatsUsesPodmanInspectHealthFallback(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})},
|
})},
|
||||||
containerStatsMap: make(map[string]*container.Stats),
|
containerStatsMap: make(map[string]*container.Stats),
|
||||||
apiStats: &container.ApiStats{},
|
|
||||||
usingPodman: true,
|
usingPodman: true,
|
||||||
lastCpuContainer: make(map[uint16]map[string]uint64),
|
lastCpuContainer: make(map[uint16]map[string]uint64),
|
||||||
lastCpuSystem: make(map[uint16]map[string]uint64),
|
lastCpuSystem: make(map[uint16]map[string]uint64),
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ func newZfsBackend() *poolBackend {
|
|||||||
return &poolBackend{
|
return &poolBackend{
|
||||||
name: "zfs",
|
name: "zfs",
|
||||||
poolStatsFn: optionalPoolSource(zfs.PoolStats),
|
poolStatsFn: optionalPoolSource(zfs.PoolStats),
|
||||||
datasetsFn: zfs.Datasets,
|
datasetsFn: optionalPoolSource(zfs.Datasets),
|
||||||
kernelStatsFn: optionalPoolSource(zfs.PoolKernelStats),
|
kernelStatsFn: optionalPoolSource(zfs.PoolKernelStats),
|
||||||
poolStatusesFn: optionalPoolSource(zfs.PoolStatuses),
|
poolStatusesFn: optionalPoolSource(zfs.PoolStatuses),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -323,6 +323,21 @@ func TestDatasetUsageRefreshOnErrorKeepsPrevious(t *testing.T) {
|
|||||||
assert.Len(t, usage, 1, "previous usage should be retained on error")
|
assert.Len(t, usage, 1, "previous usage should be retained on error")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDatasetUsageClearsAbsentBackend(t *testing.T) {
|
||||||
|
b := newZfsBackend()
|
||||||
|
b.datasetUsage = map[string]zfsDatasetUsage{"/tank": {used: 1, avail: 1}}
|
||||||
|
b.datasetsFn = optionalPoolSource(func() ([]zfs.Dataset, error) {
|
||||||
|
return nil, zfs.ErrNoZfs
|
||||||
|
})
|
||||||
|
|
||||||
|
datasets, err := b.datasets()
|
||||||
|
require.NoError(t, err, "an absent backend must not produce an error to log")
|
||||||
|
assert.Empty(t, datasets)
|
||||||
|
b.refreshDatasetUsage()
|
||||||
|
assert.Empty(t, b.datasetUsage)
|
||||||
|
assert.False(t, b.lastUsageRefresh.IsZero())
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetDetailForceRefresh(t *testing.T) {
|
func TestGetDetailForceRefresh(t *testing.T) {
|
||||||
zm := &StoragePoolManager{detailInterval: time.Hour, backends: []*poolBackend{{name: "zfs"}}}
|
zm := &StoragePoolManager{detailInterval: time.Hour, backends: []*poolBackend{{name: "zfs"}}}
|
||||||
poolCalls := 0
|
poolCalls := 0
|
||||||
|
|||||||
@@ -70,6 +70,9 @@ type Dataset struct {
|
|||||||
// PoolStats returns capacity and health for all pools on the system using
|
// PoolStats returns capacity and health for all pools on the system using
|
||||||
// `zpool list`. Frequent health and I/O sampling uses PoolKernelStats instead.
|
// `zpool list`. Frequent health and I/O sampling uses PoolKernelStats instead.
|
||||||
func PoolStats() ([]PoolStat, error) {
|
func PoolStats() ([]PoolStat, error) {
|
||||||
|
if err := checkZfsDevice(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
out, err := commandOutput("zpool", "list", "-Hp", "-o", "name,size,alloc,free,health")
|
out, err := commandOutput("zpool", "list", "-Hp", "-o", "name,size,alloc,free,health")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var exitErr *exec.ExitError
|
var exitErr *exec.ExitError
|
||||||
@@ -84,6 +87,9 @@ func PoolStats() ([]PoolStat, error) {
|
|||||||
// Datasets returns all datasets on the system with usage and mountpoint
|
// Datasets returns all datasets on the system with usage and mountpoint
|
||||||
// information using `zfs list` (recursive by default).
|
// information using `zfs list` (recursive by default).
|
||||||
func Datasets() ([]Dataset, error) {
|
func Datasets() ([]Dataset, error) {
|
||||||
|
if err := checkZfsDevice(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
out, err := commandOutput("zfs", "list", "-Hp", "-o", "name,used,avail,mountpoint")
|
out, err := commandOutput("zfs", "list", "-Hp", "-o", "name,used,avail,mountpoint")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("zfs list: %w", err)
|
return nil, fmt.Errorf("zfs list: %w", err)
|
||||||
|
|||||||
+17
-1
@@ -13,7 +13,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var procZfsPath = "/proc/spl/kstat/zfs"
|
var (
|
||||||
|
procZfsPath = "/proc/spl/kstat/zfs"
|
||||||
|
devZfsPath = "/dev/zfs"
|
||||||
|
)
|
||||||
|
|
||||||
func ARCSize() (uint64, error) {
|
func ARCSize() (uint64, error) {
|
||||||
file, err := os.Open(filepath.Join(procZfsPath, "arcstats"))
|
file, err := os.Open(filepath.Join(procZfsPath, "arcstats"))
|
||||||
@@ -40,6 +43,19 @@ func ARCSize() (uint64, error) {
|
|||||||
return 0, fmt.Errorf("size field not found in arcstats")
|
return 0, fmt.Errorf("size field not found in arcstats")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkZfsDevice lets containers without /dev/zfs fail fast instead of
|
||||||
|
// waiting for ZFS utility commands to time out.
|
||||||
|
func checkZfsDevice() error {
|
||||||
|
_, err := os.Stat(devZfsPath)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return ErrNoZfs
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// PoolKernelStats reads pool state and cumulative I/O counters directly from
|
// PoolKernelStats reads pool state and cumulative I/O counters directly from
|
||||||
// procfs. These kstats are the same interfaces used by node_exporter's Linux
|
// procfs. These kstats are the same interfaces used by node_exporter's Linux
|
||||||
// ZFS collector and avoid keeping a `zpool iostat` subprocess alive.
|
// ZFS collector and avoid keeping a `zpool iostat` subprocess alive.
|
||||||
|
|||||||
@@ -88,3 +88,66 @@ func TestReadObjsetIORequiresAllCounters(t *testing.T) {
|
|||||||
_, _, err := readObjsetIO(path)
|
_, _, err := readObjsetIO(path)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCollectorsSkipCommandsWhenDevZfsMissing(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
oldDevZfsPath := devZfsPath
|
||||||
|
devZfsPath = filepath.Join(root, "missing")
|
||||||
|
t.Cleanup(func() { devZfsPath = oldDevZfsPath })
|
||||||
|
|
||||||
|
oldCommandOutput := commandOutput
|
||||||
|
commandOutput = func(name string, args ...string) ([]byte, error) {
|
||||||
|
t.Fatalf("unexpected %s call with %v", name, args)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { commandOutput = oldCommandOutput })
|
||||||
|
|
||||||
|
_, err := PoolStats()
|
||||||
|
assert.ErrorIs(t, err, ErrNoZfs)
|
||||||
|
_, err = Datasets()
|
||||||
|
assert.ErrorIs(t, err, ErrNoZfs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDatasetsDelegatesWhenDevZfsPresent(t *testing.T) {
|
||||||
|
oldDevZfsPath := devZfsPath
|
||||||
|
devZfsPath = filepath.Join(t.TempDir(), "zfs")
|
||||||
|
require.NoError(t, os.WriteFile(devZfsPath, nil, 0o644))
|
||||||
|
t.Cleanup(func() { devZfsPath = oldDevZfsPath })
|
||||||
|
|
||||||
|
oldCommandOutput := commandOutput
|
||||||
|
commandOutput = func(name string, args ...string) ([]byte, error) {
|
||||||
|
assert.Equal(t, "zfs", name)
|
||||||
|
assert.Equal(t, []string{"list", "-Hp", "-o", "name,used,avail,mountpoint"}, args)
|
||||||
|
return []byte("tank\t50\t50\t/tank\n"), nil
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { commandOutput = oldCommandOutput })
|
||||||
|
|
||||||
|
datasets, err := Datasets()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, []Dataset{{Name: "tank", Used: 50, Avail: 50, Mountpoint: "/tank"}}, datasets)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPoolStatsDelegatesToZpoolWhenDevZfsPresent(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
devFile := filepath.Join(root, "zfs")
|
||||||
|
require.NoError(t, os.WriteFile(devFile, []byte(""), 0o644))
|
||||||
|
|
||||||
|
oldDevZfsPath := devZfsPath
|
||||||
|
devZfsPath = devFile
|
||||||
|
t.Cleanup(func() { devZfsPath = oldDevZfsPath })
|
||||||
|
|
||||||
|
oldCommandOutput := commandOutput
|
||||||
|
called := false
|
||||||
|
commandOutput = func(name string, args ...string) ([]byte, error) {
|
||||||
|
called = true
|
||||||
|
assert.Equal(t, "zpool", name)
|
||||||
|
assert.Equal(t, []string{"list", "-Hp", "-o", "name,size,alloc,free,health"}, args)
|
||||||
|
return []byte("tank\t100\t50\t50\tONLINE\n"), nil
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { commandOutput = oldCommandOutput })
|
||||||
|
|
||||||
|
pools, err := PoolStats()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.True(t, called)
|
||||||
|
assert.Equal(t, []PoolStat{{Name: "tank", Size: 100, Alloc: 50, Free: 50, Health: "ONLINE"}}, pools)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
//go:build !linux
|
||||||
|
|
||||||
|
package zfs
|
||||||
|
|
||||||
|
// The /dev/zfs probe is Linux-specific. Other platforms detect availability
|
||||||
|
// through the ZFS utilities themselves.
|
||||||
|
func checkZfsDevice() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
//go:build testing && !linux
|
||||||
|
|
||||||
|
package zfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCollectorsUseUtilitiesOnNonLinux(t *testing.T) {
|
||||||
|
oldCommandOutput := commandOutput
|
||||||
|
commandOutput = func(name string, args ...string) ([]byte, error) {
|
||||||
|
switch name {
|
||||||
|
case "zpool":
|
||||||
|
return []byte("tank\t100\t50\t50\tONLINE\n"), nil
|
||||||
|
case "zfs":
|
||||||
|
return []byte("tank\t50\t50\t/tank\n"), nil
|
||||||
|
default:
|
||||||
|
t.Fatalf("unexpected command %s", name)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { commandOutput = oldCommandOutput })
|
||||||
|
|
||||||
|
pools, err := PoolStats()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, []PoolStat{{Name: "tank", Size: 100, Alloc: 50, Free: 50, Health: "ONLINE"}}, pools)
|
||||||
|
datasets, err := Datasets()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, []Dataset{{Name: "tank", Used: 50, Avail: 50, Mountpoint: "/tank"}}, datasets)
|
||||||
|
}
|
||||||
@@ -5,12 +5,13 @@ go 1.27.1
|
|||||||
require (
|
require (
|
||||||
github.com/blang/semver v3.5.1+incompatible
|
github.com/blang/semver v3.5.1+incompatible
|
||||||
github.com/coreos/go-systemd/v22 v22.7.0
|
github.com/coreos/go-systemd/v22 v22.7.0
|
||||||
|
github.com/distribution/reference v0.6.0
|
||||||
github.com/ebitengine/purego v0.11.0
|
github.com/ebitengine/purego v0.11.0
|
||||||
github.com/fxamacker/cbor/v2 v2.9.3
|
github.com/fxamacker/cbor/v2 v2.9.3
|
||||||
github.com/gliderlabs/ssh v0.3.8
|
github.com/gliderlabs/ssh v0.3.8
|
||||||
github.com/google/uuid v1.6.0
|
|
||||||
github.com/lxzan/gws v1.10.1
|
github.com/lxzan/gws v1.10.1
|
||||||
github.com/nicholas-fedor/shoutrrr v0.20.0
|
github.com/nicholas-fedor/shoutrrr v0.20.0
|
||||||
|
github.com/opencontainers/go-digest v1.0.0
|
||||||
github.com/pocketbase/dbx v1.12.0
|
github.com/pocketbase/dbx v1.12.0
|
||||||
github.com/pocketbase/pocketbase v0.40.2
|
github.com/pocketbase/pocketbase v0.40.2
|
||||||
github.com/shirou/gopsutil/v4 v4.26.8
|
github.com/shirou/gopsutil/v4 v4.26.8
|
||||||
@@ -41,6 +42,7 @@ require (
|
|||||||
github.com/go-sql-driver/mysql v1.9.1 // indirect
|
github.com/go-sql-driver/mysql v1.9.1 // indirect
|
||||||
github.com/godbus/dbus/v5 v5.2.2 // indirect
|
github.com/godbus/dbus/v5 v5.2.2 // indirect
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.20.0 // indirect
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8=
|
github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8=
|
||||||
github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c=
|
github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
@@ -89,6 +91,8 @@ github.com/onsi/ginkgo/v2 v2.32.1 h1:6tlvcDm/3sE8lGJbZ4+d4mO3RLy24/tQWOFzVSQNIfw
|
|||||||
github.com/onsi/ginkgo/v2 v2.32.1/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44=
|
github.com/onsi/ginkgo/v2 v2.32.1/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44=
|
||||||
github.com/onsi/gomega v1.43.0 h1:VlG/1FxqNxhSO+lq/OHBNaaqwiBK/mO8JbVkX9Y+FeU=
|
github.com/onsi/gomega v1.43.0 h1:VlG/1FxqNxhSO+lq/OHBNaaqwiBK/mO8JbVkX9Y+FeU=
|
||||||
github.com/onsi/gomega v1.43.0/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg=
|
github.com/onsi/gomega v1.43.0/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pocketbase/dbx v1.12.0 h1:/oLErM+A0b4xI0PWTGPqSDVjzix48PqI/bng2l0PzoA=
|
github.com/pocketbase/dbx v1.12.0 h1:/oLErM+A0b4xI0PWTGPqSDVjzix48PqI/bng2l0PzoA=
|
||||||
github.com/pocketbase/dbx v1.12.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
|
github.com/pocketbase/dbx v1.12.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
|
||||||
|
|||||||
@@ -186,11 +186,12 @@ type Stats struct {
|
|||||||
NetworkRecv float64 `json:"nr,omitzero" cbor:"4,keyasint,omitzero"` // deprecated 0.18.3 (MB) - keep field for old agents/records
|
NetworkRecv float64 `json:"nr,omitzero" cbor:"4,keyasint,omitzero"` // deprecated 0.18.3 (MB) - keep field for old agents/records
|
||||||
Bandwidth [2]uint64 `json:"b,omitzero" cbor:"9,keyasint,omitzero"` // [sent bytes, recv bytes]
|
Bandwidth [2]uint64 `json:"b,omitzero" cbor:"9,keyasint,omitzero"` // [sent bytes, recv bytes]
|
||||||
|
|
||||||
Health DockerHealth `json:"-" cbor:"5,keyasint"`
|
Health DockerHealth `json:"-" cbor:"5,keyasint"`
|
||||||
Status string `json:"-" cbor:"6,keyasint"`
|
Status string `json:"-" cbor:"6,keyasint"`
|
||||||
Id string `json:"-" cbor:"7,keyasint"`
|
Id string `json:"-" cbor:"7,keyasint"`
|
||||||
Image string `json:"-" cbor:"8,keyasint"`
|
Image string `json:"-" cbor:"8,keyasint"`
|
||||||
Ports string `json:"-" cbor:"10,keyasint"`
|
Ports string `json:"-" cbor:"10,keyasint"`
|
||||||
|
UpdateAvailable bool `json:"u,omitzero" cbor:"11,keyasint,omitzero"`
|
||||||
// PrevCpu [2]uint64 `json:"-"`
|
// PrevCpu [2]uint64 `json:"-"`
|
||||||
CpuSystem uint64 `json:"-"`
|
CpuSystem uint64 `json:"-"`
|
||||||
CpuContainer uint64 `json:"-"`
|
CpuContainer uint64 `json:"-"`
|
||||||
|
|||||||
+1
-1
@@ -6,9 +6,9 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"uuid"
|
||||||
|
|
||||||
"github.com/blang/semver"
|
"github.com/blang/semver"
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/henrygd/beszel"
|
"github.com/henrygd/beszel"
|
||||||
"github.com/henrygd/beszel/internal/alerts"
|
"github.com/henrygd/beszel/internal/alerts"
|
||||||
"github.com/henrygd/beszel/internal/ghupdate"
|
"github.com/henrygd/beszel/internal/ghupdate"
|
||||||
|
|||||||
@@ -6,12 +6,16 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
beszelTests "github.com/henrygd/beszel/internal/tests"
|
beszelTests "github.com/henrygd/beszel/internal/tests"
|
||||||
|
|
||||||
"github.com/henrygd/beszel/internal/migrations"
|
"github.com/henrygd/beszel/internal/migrations"
|
||||||
"github.com/pocketbase/dbx"
|
"github.com/pocketbase/dbx"
|
||||||
|
"github.com/pocketbase/pocketbase/apis"
|
||||||
"github.com/pocketbase/pocketbase/core"
|
"github.com/pocketbase/pocketbase/core"
|
||||||
pbTests "github.com/pocketbase/pocketbase/tests"
|
pbTests "github.com/pocketbase/pocketbase/tests"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -26,6 +30,59 @@ func jsonReader(v any) io.Reader {
|
|||||||
return bytes.NewReader(data)
|
return bytes.NewReader(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type gatedReader struct {
|
||||||
|
data []byte
|
||||||
|
started chan struct{}
|
||||||
|
release chan struct{}
|
||||||
|
offset int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *gatedReader) Read(p []byte) (int, error) {
|
||||||
|
if r.offset == 0 {
|
||||||
|
close(r.started)
|
||||||
|
<-r.release
|
||||||
|
}
|
||||||
|
if r.offset >= len(r.data) {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
n := copy(p, r.data[r.offset:])
|
||||||
|
r.offset += n
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstUserTestMux(t *testing.T) (*beszelTests.TestHub, http.Handler) {
|
||||||
|
t.Helper()
|
||||||
|
hub, err := beszelTests.NewTestHub(t.TempDir())
|
||||||
|
require.NoError(t, err)
|
||||||
|
_ = hub.StartHub()
|
||||||
|
|
||||||
|
router, err := apis.NewRouter(hub.TestApp)
|
||||||
|
require.NoError(t, err)
|
||||||
|
serveEvent := &core.ServeEvent{App: hub.TestApp, Router: router}
|
||||||
|
|
||||||
|
var handler http.Handler
|
||||||
|
err = hub.TestApp.OnServe().Trigger(serveEvent, func(e *core.ServeEvent) error {
|
||||||
|
var buildErr error
|
||||||
|
handler, buildErr = e.Router.BuildMux()
|
||||||
|
return buildErr
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, handler)
|
||||||
|
return hub, handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func postFirstUser(handler http.Handler, email string) *httptest.ResponseRecorder {
|
||||||
|
body, _ := json.Marshal(map[string]string{
|
||||||
|
"email": email,
|
||||||
|
"password": "password123",
|
||||||
|
})
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/api/beszel/create-user", bytes.NewReader(body))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(recorder, req)
|
||||||
|
return recorder
|
||||||
|
}
|
||||||
|
|
||||||
func TestApiRoutesAuthentication(t *testing.T) {
|
func TestApiRoutesAuthentication(t *testing.T) {
|
||||||
hub, user := beszelTests.GetHubWithUser(t)
|
hub, user := beszelTests.GetHubWithUser(t)
|
||||||
defer hub.Cleanup()
|
defer hub.Cleanup()
|
||||||
@@ -789,6 +846,87 @@ func TestFirstUserCreation(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFirstUserBootstrapAtomicity(t *testing.T) {
|
||||||
|
t.Run("concurrent complete requests produce exactly one winner", func(t *testing.T) {
|
||||||
|
hub, handler := firstUserTestMux(t)
|
||||||
|
defer hub.Cleanup()
|
||||||
|
|
||||||
|
start := make(chan struct{})
|
||||||
|
statuses := make(chan int, 2)
|
||||||
|
for _, email := range []string{"first@example.com", "second@example.com"} {
|
||||||
|
go func(email string) {
|
||||||
|
<-start
|
||||||
|
statuses <- postFirstUser(handler, email).Code
|
||||||
|
}(email)
|
||||||
|
}
|
||||||
|
close(start)
|
||||||
|
|
||||||
|
got := []int{<-statuses, <-statuses}
|
||||||
|
sort.Ints(got)
|
||||||
|
require.Equal(t, []int{http.StatusOK, http.StatusForbidden}, got)
|
||||||
|
|
||||||
|
users, err := hub.FindAllRecords("users")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, users, 1)
|
||||||
|
superusers, err := hub.FindAllRecords(core.CollectionNameSuperusers)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, superusers, 1)
|
||||||
|
require.NotEqual(t, migrations.TempAdminEmail, superusers[0].Email())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("partial body cannot retain stale bootstrap authorization", func(t *testing.T) {
|
||||||
|
hub, handler := firstUserTestMux(t)
|
||||||
|
defer hub.Cleanup()
|
||||||
|
|
||||||
|
body, err := json.Marshal(map[string]string{
|
||||||
|
"email": "parked@example.com",
|
||||||
|
"password": "password123",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
gated := &gatedReader{
|
||||||
|
data: body,
|
||||||
|
started: make(chan struct{}),
|
||||||
|
release: make(chan struct{}),
|
||||||
|
}
|
||||||
|
parkedRequest := httptest.NewRequest(http.MethodPost, "/api/beszel/create-user", gated)
|
||||||
|
parkedRequest.Header.Set("Content-Type", "application/json")
|
||||||
|
parkedRecorder := httptest.NewRecorder()
|
||||||
|
parkedDone := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
handler.ServeHTTP(parkedRecorder, parkedRequest)
|
||||||
|
close(parkedDone)
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-gated.started:
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatal("parked request did not begin reading its body")
|
||||||
|
}
|
||||||
|
|
||||||
|
operatorRecorder := postFirstUser(handler, "operator@example.com")
|
||||||
|
require.Equal(t, http.StatusOK, operatorRecorder.Code)
|
||||||
|
lateRecorder := postFirstUser(handler, "late@example.com")
|
||||||
|
require.Equal(t, http.StatusForbidden, lateRecorder.Code)
|
||||||
|
|
||||||
|
close(gated.release)
|
||||||
|
select {
|
||||||
|
case <-parkedDone:
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatal("parked request did not finish")
|
||||||
|
}
|
||||||
|
require.Equal(t, http.StatusForbidden, parkedRecorder.Code)
|
||||||
|
|
||||||
|
users, err := hub.FindAllRecords("users")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, users, 1)
|
||||||
|
require.Equal(t, "operator@example.com", users[0].Email())
|
||||||
|
superusers, err := hub.FindAllRecords(core.CollectionNameSuperusers)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, superusers, 1)
|
||||||
|
require.Equal(t, "operator@example.com", superusers[0].Email())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestCreateUserEndpointAvailability(t *testing.T) {
|
func TestCreateUserEndpointAvailability(t *testing.T) {
|
||||||
t.Run("CreateUserEndpoint available when no users exist", func(t *testing.T) {
|
t.Run("CreateUserEndpoint available when no users exist", func(t *testing.T) {
|
||||||
hub, _ := beszelTests.NewTestHub(t.TempDir())
|
hub, _ := beszelTests.NewTestHub(t.TempDir())
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"uuid"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/henrygd/beszel/internal/entities/system"
|
"github.com/henrygd/beszel/internal/entities/system"
|
||||||
|
|
||||||
"github.com/pocketbase/dbx"
|
"github.com/pocketbase/dbx"
|
||||||
|
|||||||
@@ -122,6 +122,8 @@ func (h *Hub) initialize(app core.App) error {
|
|||||||
settings := app.Settings()
|
settings := app.Settings()
|
||||||
// batch requests (for alerts)
|
// batch requests (for alerts)
|
||||||
settings.Batch.Enabled = true
|
settings.Batch.Enabled = true
|
||||||
|
settings.Batch.MaxRequests = 100
|
||||||
|
settings.Batch.MaxBodySize = 1 << 20 // 1 MiB
|
||||||
// set URL if APP_URL env is set
|
// set URL if APP_URL env is set
|
||||||
if appURL, isSet := utils.GetEnv("APP_URL"); isSet {
|
if appURL, isSet := utils.GetEnv("APP_URL"); isSet {
|
||||||
h.appURL = appURL
|
h.appURL = appURL
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
//go:build testing
|
||||||
|
|
||||||
|
package systems
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/henrygd/beszel/internal/entities/container"
|
||||||
|
"github.com/pocketbase/dbx"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateContainerRecordsPersistsImageUpdateAvailability(t *testing.T) {
|
||||||
|
_, app := newTestSystemWithHub(t)
|
||||||
|
|
||||||
|
const (
|
||||||
|
systemID = "system123"
|
||||||
|
containerID = "abcdef123456"
|
||||||
|
image = "nginx:latest"
|
||||||
|
)
|
||||||
|
|
||||||
|
data := &container.Stats{
|
||||||
|
Id: containerID,
|
||||||
|
Name: "web",
|
||||||
|
Image: image,
|
||||||
|
UpdateAvailable: true,
|
||||||
|
}
|
||||||
|
require.NoError(t, createContainerRecords(app, []*container.Stats{data}, systemID))
|
||||||
|
|
||||||
|
var record struct {
|
||||||
|
Image string `db:"image"`
|
||||||
|
UpdateAvailable bool `db:"updatable"`
|
||||||
|
}
|
||||||
|
require.NoError(t, app.DB().Select("image", "updatable").From("containers").
|
||||||
|
Where(dbx.HashExp{"id": containerID}).One(&record))
|
||||||
|
assert.Equal(t, image, record.Image)
|
||||||
|
assert.True(t, record.UpdateAvailable)
|
||||||
|
|
||||||
|
data.UpdateAvailable = false
|
||||||
|
require.NoError(t, createContainerRecords(app, []*container.Stats{data}, systemID))
|
||||||
|
require.NoError(t, app.DB().Select("image", "updatable").From("containers").
|
||||||
|
Where(dbx.HashExp{"id": containerID}).One(&record))
|
||||||
|
assert.Equal(t, image, record.Image)
|
||||||
|
assert.False(t, record.UpdateAvailable)
|
||||||
|
}
|
||||||
@@ -376,7 +376,7 @@ func createContainerRecords(app core.App, data []*container.Stats, systemId stri
|
|||||||
valueStrings := make([]string, 0, len(data))
|
valueStrings := make([]string, 0, len(data))
|
||||||
for i, container := range data {
|
for i, container := range data {
|
||||||
suffix := fmt.Sprintf("%d", i)
|
suffix := fmt.Sprintf("%d", i)
|
||||||
valueStrings = append(valueStrings, fmt.Sprintf("({:id%[1]s}, {:system}, {:name%[1]s}, {:image%[1]s}, {:ports%[1]s}, {:status%[1]s}, {:health%[1]s}, {:cpu%[1]s}, {:memory%[1]s}, {:net%[1]s}, {:updated})", suffix))
|
valueStrings = append(valueStrings, fmt.Sprintf("({:id%[1]s}, {:system}, {:name%[1]s}, {:image%[1]s}, {:ports%[1]s}, {:status%[1]s}, {:health%[1]s}, {:cpu%[1]s}, {:memory%[1]s}, {:net%[1]s}, {:updateAvailable%[1]s}, {:updated})", suffix))
|
||||||
params["id"+suffix] = container.Id
|
params["id"+suffix] = container.Id
|
||||||
params["name"+suffix] = container.Name
|
params["name"+suffix] = container.Name
|
||||||
params["image"+suffix] = container.Image
|
params["image"+suffix] = container.Image
|
||||||
@@ -390,9 +390,10 @@ func createContainerRecords(app core.App, data []*container.Stats, systemId stri
|
|||||||
netBytes = uint64((container.NetworkSent + container.NetworkRecv) * 1024 * 1024)
|
netBytes = uint64((container.NetworkSent + container.NetworkRecv) * 1024 * 1024)
|
||||||
}
|
}
|
||||||
params["net"+suffix] = netBytes
|
params["net"+suffix] = netBytes
|
||||||
|
params["updateAvailable"+suffix] = container.UpdateAvailable
|
||||||
}
|
}
|
||||||
queryString := fmt.Sprintf(
|
queryString := fmt.Sprintf(
|
||||||
"INSERT INTO containers (id, system, name, image, ports, status, health, cpu, memory, net, updated) VALUES %s ON CONFLICT(id) DO UPDATE SET system = excluded.system, name = excluded.name, image = excluded.image, ports = excluded.ports, status = excluded.status, health = excluded.health, cpu = excluded.cpu, memory = excluded.memory, net = excluded.net, updated = excluded.updated",
|
"INSERT INTO containers (id, system, name, image, ports, status, health, cpu, memory, net, updatable, updated) VALUES %s ON CONFLICT(id) DO UPDATE SET system = excluded.system, name = excluded.name, image = excluded.image, ports = excluded.ports, status = excluded.status, health = excluded.health, cpu = excluded.cpu, memory = excluded.memory, net = excluded.net, updatable = excluded.updatable, updated = excluded.updated",
|
||||||
strings.Join(valueStrings, ","),
|
strings.Join(valueStrings, ","),
|
||||||
)
|
)
|
||||||
_, err := app.DB().NewQuery(queryString).Bind(params).Execute()
|
_, err := app.DB().NewQuery(queryString).Bind(params).Execute()
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pocketbase/pocketbase/core"
|
||||||
|
m "github.com/pocketbase/pocketbase/migrations"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
m.Register(func(app core.App) error {
|
||||||
|
collection, err := app.FindCollectionByNameOrId("containers")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Fields.Add(&core.BoolField{Name: "updatable"})
|
||||||
|
return app.Save(collection)
|
||||||
|
}, func(app core.App) error {
|
||||||
|
collection, err := app.FindCollectionByNameOrId("containers")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Fields.RemoveByName("updatable")
|
||||||
|
return app.Save(collection)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { cn, decimalString, formatBytes, hourWithSeconds } from "@/lib/utils"
|
|||||||
import type { ContainerRecord } from "@/types"
|
import type { ContainerRecord } from "@/types"
|
||||||
import { ContainerHealth, ContainerHealthLabels } from "@/lib/enums"
|
import { ContainerHealth, ContainerHealthLabels } from "@/lib/enums"
|
||||||
import {
|
import {
|
||||||
|
CircleArrowUpIcon,
|
||||||
ClockIcon,
|
ClockIcon,
|
||||||
ContainerIcon,
|
ContainerIcon,
|
||||||
CpuIcon,
|
CpuIcon,
|
||||||
@@ -177,11 +178,25 @@ export const containerChartCols: ColumnDef<ContainerRecord>[] = [
|
|||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<HeaderButton column={column} name={t({ message: "Image", context: "Docker image" })} Icon={LayersIcon} />
|
<HeaderButton column={column} name={t({ message: "Image", context: "Docker image" })} Icon={LayersIcon} />
|
||||||
),
|
),
|
||||||
cell: ({ getValue }) => {
|
cell: ({ getValue, row }) => {
|
||||||
const val = getValue() as string
|
const val = getValue() as string
|
||||||
return (
|
return (
|
||||||
<div className="ms-1 xl:w-40 truncate" title={val}>
|
<div className="ms-1 xl:w-40 flex items-center gap-2">
|
||||||
{val}
|
<span className="truncate" title={val}>
|
||||||
|
{val}
|
||||||
|
</span>
|
||||||
|
{row.original.updatable && (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger
|
||||||
|
className="shrink-0 rounded-sm text-emerald-600 dark:text-emerald-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
|
aria-label={t`Image update available`}
|
||||||
|
onClick={(event) => event.stopPropagation()}
|
||||||
|
>
|
||||||
|
<CircleArrowUpIcon className="size-4" aria-hidden="true" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>{t`Image update available`}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export default function ContainersTable({ systemId }: { systemId?: string }) {
|
|||||||
function fetchData(systemId?: string) {
|
function fetchData(systemId?: string) {
|
||||||
pb.collection<ContainerRecord>("containers")
|
pb.collection<ContainerRecord>("containers")
|
||||||
.getList(0, 2000, {
|
.getList(0, 2000, {
|
||||||
fields: "id,name,image,ports,cpu,memory,net,health,status,system,updated",
|
fields: "id,name,image,updatable,ports,cpu,memory,net,health,status,system,updated",
|
||||||
filter: systemId ? pb.filter("system={:system}", { system: systemId }) : undefined,
|
filter: systemId ? pb.filter("system={:system}", { system: systemId }) : undefined,
|
||||||
})
|
})
|
||||||
.then(({ items }) => {
|
.then(({ items }) => {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export default function () {
|
|||||||
const page = useStore($router)
|
const page = useStore($router)
|
||||||
const [isFirstRun, setFirstRun] = useState(false)
|
const [isFirstRun, setFirstRun] = useState(false)
|
||||||
const [authMethods, setAuthMethods] = useState<AuthMethodsList>()
|
const [authMethods, setAuthMethods] = useState<AuthMethodsList>()
|
||||||
const { theme } = useTheme()
|
const { resolvedTheme } = useTheme()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = t`Login` + " / Beszel"
|
document.title = t`Login` + " / Beszel"
|
||||||
@@ -54,7 +54,7 @@ export default function () {
|
|||||||
<div
|
<div
|
||||||
className="grid gap-5 w-full px-4 mx-auto"
|
className="grid gap-5 w-full px-4 mx-auto"
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
style={{ maxWidth: "21.5em", "--border": theme == "light" ? "hsl(30, 8%, 70%)" : "hsl(220, 3%, 25%)" }}
|
style={{ maxWidth: "21.5em", "--border": resolvedTheme == "light" ? "hsl(30, 8%, 70%)" : "hsl(220, 3%, 25%)" }}
|
||||||
>
|
>
|
||||||
<div className="absolute top-3 right-3">
|
<div className="absolute top-3 right-3">
|
||||||
<ModeToggle />
|
<ModeToggle />
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
|
|||||||
<Label className="block" htmlFor="lang">
|
<Label className="block" htmlFor="lang">
|
||||||
<Trans>Preferred Language</Trans>
|
<Trans>Preferred Language</Trans>
|
||||||
</Label>
|
</Label>
|
||||||
<Select value={i18n.locale} onValueChange={(lang: string) => dynamicActivate(lang)}>
|
<Select name="lang" value={i18n.locale} onValueChange={(lang: string) => dynamicActivate(lang)}>
|
||||||
<SelectTrigger id="lang">
|
<SelectTrigger id="lang">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { lazy, useEffect } from "react"
|
|||||||
import { $router } from "@/components/router.tsx"
|
import { $router } from "@/components/router.tsx"
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card.tsx"
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card.tsx"
|
||||||
import { toast } from "@/components/ui/use-toast.ts"
|
import { toast } from "@/components/ui/use-toast.ts"
|
||||||
import { pb } from "@/lib/api"
|
import { saveUserSettings } from "@/lib/api"
|
||||||
import { $userSettings } from "@/lib/stores.ts"
|
import { $userSettings } from "@/lib/stores.ts"
|
||||||
import type { UserSettings } from "@/types"
|
import type { UserSettings } from "@/types"
|
||||||
import { Separator } from "../../ui/separator"
|
import { Separator } from "../../ui/separator"
|
||||||
@@ -36,24 +36,13 @@ const HeartbeatSettings = lazy(heartbeatSettingsImport)
|
|||||||
|
|
||||||
export async function saveSettings(newSettings: Partial<UserSettings>) {
|
export async function saveSettings(newSettings: Partial<UserSettings>) {
|
||||||
try {
|
try {
|
||||||
// get fresh copy of settings
|
await saveUserSettings(newSettings)
|
||||||
const req = await pb.collection("user_settings").getFirstListItem("", {
|
|
||||||
fields: "id,settings",
|
|
||||||
})
|
|
||||||
// update user settings
|
|
||||||
const updatedSettings = await pb.collection("user_settings").update(req.id, {
|
|
||||||
settings: {
|
|
||||||
...req.settings,
|
|
||||||
...newSettings,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
$userSettings.set(updatedSettings.settings)
|
|
||||||
toast({
|
toast({
|
||||||
title: t`Settings saved`,
|
title: t`Settings saved`,
|
||||||
description: t`Your user settings have been updated.`,
|
description: t`Your user settings have been updated.`,
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// console.error('update settings', e)
|
console.error("save settings", e)
|
||||||
toast({
|
toast({
|
||||||
title: t`Failed to save settings`,
|
title: t`Failed to save settings`,
|
||||||
description: t`Check logs for more details.`,
|
description: t`Check logs for more details.`,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import { getPagePath } from "@nanostores/router"
|
import { getPagePath } from "@nanostores/router"
|
||||||
import { subscribeKeys } from "nanostores"
|
import { subscribeKeys } from "nanostores"
|
||||||
import { useEffect, useMemo, useRef, useState } from "react"
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||||
import { useContainerChartConfigs } from "@/components/charts/hooks"
|
import { useContainerChartConfigs } from "@/components/charts/hooks"
|
||||||
import { pb } from "@/lib/api"
|
import { pb, queueUserSettings } from "@/lib/api"
|
||||||
import { SystemStatus } from "@/lib/enums"
|
import { SystemStatus } from "@/lib/enums"
|
||||||
import {
|
import {
|
||||||
$allSystemsById,
|
$allSystemsById,
|
||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
$systems,
|
$systems,
|
||||||
$userSettings,
|
$userSettings,
|
||||||
} from "@/lib/stores"
|
} from "@/lib/stores"
|
||||||
import { chartTimeData, listen, parseSemVer, useBrowserStorage } from "@/lib/utils"
|
import { chartTimeData, listen, parseSemVer } from "@/lib/utils"
|
||||||
import type {
|
import type {
|
||||||
ChartData,
|
ChartData,
|
||||||
ContainerStatsRecord,
|
ContainerStatsRecord,
|
||||||
@@ -35,8 +35,42 @@ export function useSystemData(id: string) {
|
|||||||
const systems = useStore($systems)
|
const systems = useStore($systems)
|
||||||
const chartTime = useStore($chartTime)
|
const chartTime = useStore($chartTime)
|
||||||
const maxValues = useStore($maxValues)
|
const maxValues = useStore($maxValues)
|
||||||
const [grid, setGrid] = useBrowserStorage("grid", true)
|
const [grid, _setGrid] = useState<boolean>(
|
||||||
const [displayMode, setDisplayMode] = useBrowserStorage<"default" | "tabs">("displayMode", "default")
|
() => $userSettings.get().grid ?? JSON.parse(localStorage.getItem("besz-grid") ?? "null") ?? true
|
||||||
|
)
|
||||||
|
const [displayMode, _setDisplayMode] = useState<"default" | "tabs">(
|
||||||
|
() =>
|
||||||
|
$userSettings.get().displayMode ??
|
||||||
|
(JSON.parse(localStorage.getItem("besz-displayMode") || "null") as "default" | "tabs" | null) ??
|
||||||
|
"default"
|
||||||
|
)
|
||||||
|
|
||||||
|
const applied = useRef(new Set<string>())
|
||||||
|
useEffect(() => {
|
||||||
|
return subscribeKeys($userSettings, ["grid", "displayMode"], (vals) => {
|
||||||
|
if (!applied.current.has("grid") && vals.grid !== undefined) {
|
||||||
|
applied.current.add("grid")
|
||||||
|
_setGrid(vals.grid)
|
||||||
|
}
|
||||||
|
if (!applied.current.has("displayMode") && vals.displayMode !== undefined) {
|
||||||
|
applied.current.add("displayMode")
|
||||||
|
_setDisplayMode(vals.displayMode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const setGrid = useCallback((v: boolean) => {
|
||||||
|
_setGrid(v)
|
||||||
|
localStorage.setItem("besz-grid", JSON.stringify(v))
|
||||||
|
$userSettings.setKey("grid", v)
|
||||||
|
queueUserSettings({ grid: v })
|
||||||
|
}, [])
|
||||||
|
const setDisplayMode = useCallback((v: "default" | "tabs") => {
|
||||||
|
_setDisplayMode(v)
|
||||||
|
localStorage.setItem("besz-displayMode", JSON.stringify(v))
|
||||||
|
$userSettings.setKey("displayMode", v)
|
||||||
|
queueUserSettings({ displayMode: v })
|
||||||
|
}, [])
|
||||||
const [activeTab, setActiveTabRaw] = useState("core")
|
const [activeTab, setActiveTabRaw] = useState("core")
|
||||||
const [mountedTabs, setMountedTabs] = useState(() => new Set<string>(["core"]))
|
const [mountedTabs, setMountedTabs] = useState(() => new Set<string>(["core"]))
|
||||||
const tabsRef = useRef<string[]>(["core", "disk"])
|
const tabsRef = useRef<string[]>(["core", "disk"])
|
||||||
@@ -171,6 +205,7 @@ export function useSystemData(id: string) {
|
|||||||
// get stats when system "changes." (Not just system to system,
|
// get stats when system "changes." (Not just system to system,
|
||||||
// also when new info comes in via systemManager realtime connection, indicating an update)
|
// also when new info comes in via systemManager realtime connection, indicating an update)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const requestId = ++statsRequestId.current
|
||||||
if (!system.id || !chartTime || chartTime === "1m") {
|
if (!system.id || !chartTime || chartTime === "1m") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -179,7 +214,6 @@ export function useSystemData(id: string) {
|
|||||||
const { expectedInterval } = chartTimeData[chartTime]
|
const { expectedInterval } = chartTimeData[chartTime]
|
||||||
const ss_cache_key = `${systemId}_${chartTime}_system_stats`
|
const ss_cache_key = `${systemId}_${chartTime}_system_stats`
|
||||||
const cs_cache_key = `${systemId}_${chartTime}_container_stats`
|
const cs_cache_key = `${systemId}_${chartTime}_container_stats`
|
||||||
const requestId = ++statsRequestId.current
|
|
||||||
|
|
||||||
const cachedSystemStats = cache.get(ss_cache_key) as SystemStatsRecord[] | undefined
|
const cachedSystemStats = cache.get(ss_cache_key) as SystemStatsRecord[] | undefined
|
||||||
const cachedContainerData = cache.get(cs_cache_key) as ChartData["containerData"] | undefined
|
const cachedContainerData = cache.get(cs_cache_key) as ChartData["containerData"] | undefined
|
||||||
@@ -203,7 +237,7 @@ export function useSystemData(id: string) {
|
|||||||
getStats<SystemStatsRecord>("system_stats", systemId, chartTime),
|
getStats<SystemStatsRecord>("system_stats", systemId, chartTime),
|
||||||
getStats<ContainerStatsRecord>("container_stats", systemId, chartTime),
|
getStats<ContainerStatsRecord>("container_stats", systemId, chartTime),
|
||||||
]).then(([systemStats, containerStats]) => {
|
]).then(([systemStats, containerStats]) => {
|
||||||
// If another request has been made since this one, ignore the results
|
// Ignore responses for a previous system or chart time
|
||||||
if (requestId !== statsRequestId.current) {
|
if (requestId !== statsRequestId.current) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Trans, useLingui } from "@lingui/react/macro"
|
import { Trans, useLingui } from "@lingui/react/macro"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
|
import { subscribeKeys } from "nanostores"
|
||||||
import { getPagePath } from "@nanostores/router"
|
import { getPagePath } from "@nanostores/router"
|
||||||
import {
|
import {
|
||||||
type ColumnDef,
|
type ColumnDef,
|
||||||
@@ -26,7 +27,7 @@ import {
|
|||||||
Settings2Icon,
|
Settings2Icon,
|
||||||
XIcon,
|
XIcon,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { memo, useEffect, useMemo, useRef, useState } from "react"
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@@ -42,8 +43,9 @@ import {
|
|||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
import { TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||||
import { SystemStatus } from "@/lib/enums"
|
import { SystemStatus } from "@/lib/enums"
|
||||||
import { $downSystems, $pausedSystems, $systems, $upSystems } from "@/lib/stores"
|
import { queueUserSettings } from "@/lib/api"
|
||||||
import { cn, runOnce, useBrowserStorage } from "@/lib/utils"
|
import { $downSystems, $pausedSystems, $systems, $upSystems, $userSettings } from "@/lib/stores"
|
||||||
|
import { cn, runOnce } from "@/lib/utils"
|
||||||
import type { SystemRecord } from "@/types"
|
import type { SystemRecord } from "@/types"
|
||||||
import AlertButton from "../alerts/alert-button"
|
import AlertButton from "../alerts/alert-button"
|
||||||
import { $router, Link } from "../router"
|
import { $router, Link } from "../router"
|
||||||
@@ -62,14 +64,83 @@ export default function SystemsTable() {
|
|||||||
const pausedSystems = $pausedSystems.get()
|
const pausedSystems = $pausedSystems.get()
|
||||||
const { i18n, t } = useLingui()
|
const { i18n, t } = useLingui()
|
||||||
const [filter, setFilter] = useState<string>("")
|
const [filter, setFilter] = useState<string>("")
|
||||||
const [statusFilter, setStatusFilter] = useState<StatusFilter>("all")
|
const [statusFilter, setStatusFilter] = useState<StatusFilter>(
|
||||||
const [sorting, setSorting] = useBrowserStorage<SortingState>(
|
() =>
|
||||||
"sortMode",
|
$userSettings.get().statusFilter ??
|
||||||
[{ id: "system", desc: false }],
|
(JSON.parse(localStorage.getItem("besz-statusFilter") || "null") as StatusFilter | null) ??
|
||||||
sessionStorage
|
"all"
|
||||||
|
)
|
||||||
|
const [sorting, setSorting] = useState<SortingState>(
|
||||||
|
() =>
|
||||||
|
$userSettings.get().sortMode ??
|
||||||
|
JSON.parse(sessionStorage.getItem("besz-sortMode") || "null") ?? [{ id: "system", desc: false }]
|
||||||
)
|
)
|
||||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
||||||
const [columnVisibility, setColumnVisibility] = useBrowserStorage<VisibilityState>("cols", {})
|
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>(
|
||||||
|
() => $userSettings.get().cols ?? JSON.parse(localStorage.getItem("besz-cols") || "{}")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Apply settings from server once they load (handles incognito / new devices)
|
||||||
|
const applied = useRef(new Set<string>())
|
||||||
|
useEffect(() => {
|
||||||
|
return subscribeKeys($userSettings, ["cols", "statusFilter", "viewMode", "sortMode"], (vals) => {
|
||||||
|
if (!applied.current.has("cols") && vals.cols !== undefined) {
|
||||||
|
applied.current.add("cols")
|
||||||
|
setColumnVisibility(vals.cols)
|
||||||
|
}
|
||||||
|
if (!applied.current.has("statusFilter") && vals.statusFilter !== undefined) {
|
||||||
|
applied.current.add("statusFilter")
|
||||||
|
setStatusFilter(vals.statusFilter)
|
||||||
|
}
|
||||||
|
if (!applied.current.has("viewMode") && vals.viewMode !== undefined) {
|
||||||
|
applied.current.add("viewMode")
|
||||||
|
setViewMode(vals.viewMode)
|
||||||
|
}
|
||||||
|
if (!applied.current.has("sortMode") && vals.sortMode !== undefined) {
|
||||||
|
applied.current.add("sortMode")
|
||||||
|
setSorting(vals.sortMode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleColumnVisibilityChange = useCallback(
|
||||||
|
(updater: VisibilityState | ((prev: VisibilityState) => VisibilityState)) => {
|
||||||
|
setColumnVisibility((prev) => {
|
||||||
|
const next = typeof updater === "function" ? updater(prev) : updater
|
||||||
|
localStorage.setItem("besz-cols", JSON.stringify(next))
|
||||||
|
$userSettings.setKey("cols", next)
|
||||||
|
queueUserSettings({ cols: next })
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleStatusFilterChange = useCallback((value: string) => {
|
||||||
|
const next = value as StatusFilter
|
||||||
|
setStatusFilter(next)
|
||||||
|
localStorage.setItem("besz-statusFilter", JSON.stringify(next))
|
||||||
|
$userSettings.setKey("statusFilter", next)
|
||||||
|
queueUserSettings({ statusFilter: next })
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleViewModeChange = useCallback((view: string) => {
|
||||||
|
const next = view as ViewMode
|
||||||
|
setViewMode(next)
|
||||||
|
localStorage.setItem("besz-viewMode", JSON.stringify(next))
|
||||||
|
$userSettings.setKey("viewMode", next)
|
||||||
|
queueUserSettings({ viewMode: next })
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleSortingChange = useCallback((updater: SortingState | ((prev: SortingState) => SortingState)) => {
|
||||||
|
setSorting((prev) => {
|
||||||
|
const next = typeof updater === "function" ? updater(prev) : updater
|
||||||
|
sessionStorage.setItem("besz-sortMode", JSON.stringify(next))
|
||||||
|
$userSettings.setKey("sortMode", next)
|
||||||
|
queueUserSettings({ sortMode: next })
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
const locale = i18n.locale
|
const locale = i18n.locale
|
||||||
|
|
||||||
@@ -87,10 +158,12 @@ export default function SystemsTable() {
|
|||||||
return Object.values(pausedSystems) ?? []
|
return Object.values(pausedSystems) ?? []
|
||||||
}, [data, statusFilter])
|
}, [data, statusFilter])
|
||||||
|
|
||||||
const [viewMode, setViewMode] = useBrowserStorage<ViewMode>(
|
const [viewMode, setViewMode] = useState<ViewMode>(
|
||||||
"viewMode",
|
() =>
|
||||||
// show grid view on mobile if there are less than 200 systems (looks better but table is more efficient)
|
$userSettings.get().viewMode ??
|
||||||
window.innerWidth < 1024 && filteredData.length < 200 ? "grid" : "table"
|
(JSON.parse(localStorage.getItem("besz-viewMode") || "null") as ViewMode | null) ??
|
||||||
|
// show grid view on mobile if there are less than 200 systems (looks better but table is more efficient)
|
||||||
|
(window.innerWidth < 1024 && filteredData.length < 200 ? "grid" : "table")
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -105,11 +178,11 @@ export default function SystemsTable() {
|
|||||||
data: filteredData,
|
data: filteredData,
|
||||||
columns: columnDefs,
|
columns: columnDefs,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
onSortingChange: setSorting,
|
onSortingChange: handleSortingChange,
|
||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
onColumnFiltersChange: setColumnFilters,
|
onColumnFiltersChange: setColumnFilters,
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
onColumnVisibilityChange: setColumnVisibility,
|
onColumnVisibilityChange: handleColumnVisibilityChange,
|
||||||
state: {
|
state: {
|
||||||
sorting,
|
sorting,
|
||||||
columnFilters,
|
columnFilters,
|
||||||
@@ -181,11 +254,7 @@ export default function SystemsTable() {
|
|||||||
<Trans>Layout</Trans>
|
<Trans>Layout</Trans>
|
||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuRadioGroup
|
<DropdownMenuRadioGroup className="px-1 pb-1" value={viewMode} onValueChange={handleViewModeChange}>
|
||||||
className="px-1 pb-1"
|
|
||||||
value={viewMode}
|
|
||||||
onValueChange={(view) => setViewMode(view as ViewMode)}
|
|
||||||
>
|
|
||||||
<DropdownMenuRadioItem value="table" onSelect={(e) => e.preventDefault()} className="gap-2">
|
<DropdownMenuRadioItem value="table" onSelect={(e) => e.preventDefault()} className="gap-2">
|
||||||
<LayoutListIcon className="size-4" />
|
<LayoutListIcon className="size-4" />
|
||||||
<Trans>Table</Trans>
|
<Trans>Table</Trans>
|
||||||
@@ -206,7 +275,7 @@ export default function SystemsTable() {
|
|||||||
<DropdownMenuRadioGroup
|
<DropdownMenuRadioGroup
|
||||||
className="px-1 pb-1"
|
className="px-1 pb-1"
|
||||||
value={statusFilter}
|
value={statusFilter}
|
||||||
onValueChange={(value) => setStatusFilter(value as StatusFilter)}
|
onValueChange={handleStatusFilterChange}
|
||||||
>
|
>
|
||||||
<DropdownMenuRadioItem value="all" onSelect={(e) => e.preventDefault()}>
|
<DropdownMenuRadioItem value="all" onSelect={(e) => e.preventDefault()}>
|
||||||
<Trans>All Systems</Trans>
|
<Trans>All Systems</Trans>
|
||||||
@@ -245,7 +314,9 @@ export default function SystemsTable() {
|
|||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onSelect={(e) => {
|
onSelect={(e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
setSorting([{ id: column.id, desc: sorting[0]?.id === column.id && !sorting[0]?.desc }])
|
handleSortingChange([
|
||||||
|
{ id: column.id, desc: sorting[0]?.id === column.id && !sorting[0]?.desc },
|
||||||
|
])
|
||||||
}}
|
}}
|
||||||
key={column.id}
|
key={column.id}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createContext, useContext, useEffect, useState } from "react"
|
import { createContext, useContext, useEffect, useState } from "react"
|
||||||
|
|
||||||
type Theme = "dark" | "light" | "system"
|
type Theme = "dark" | "light" | "system"
|
||||||
|
type ResolvedTheme = "dark" | "light"
|
||||||
|
|
||||||
type ThemeProviderProps = {
|
type ThemeProviderProps = {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
@@ -10,11 +11,13 @@ type ThemeProviderProps = {
|
|||||||
|
|
||||||
type ThemeProviderState = {
|
type ThemeProviderState = {
|
||||||
theme: Theme
|
theme: Theme
|
||||||
|
resolvedTheme: ResolvedTheme
|
||||||
setTheme: (theme: Theme) => void
|
setTheme: (theme: Theme) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: ThemeProviderState = {
|
const initialState: ThemeProviderState = {
|
||||||
theme: "system",
|
theme: "system",
|
||||||
|
resolvedTheme: "light",
|
||||||
setTheme: () => null,
|
setTheme: () => null,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,24 +30,28 @@ export function ThemeProvider({
|
|||||||
...props
|
...props
|
||||||
}: ThemeProviderProps) {
|
}: ThemeProviderProps) {
|
||||||
const [theme, setTheme] = useState<Theme>(() => (localStorage.getItem(storageKey) as Theme) || defaultTheme)
|
const [theme, setTheme] = useState<Theme>(() => (localStorage.getItem(storageKey) as Theme) || defaultTheme)
|
||||||
|
const [systemDark, setSystemDark] = useState(() => window.matchMedia("(prefers-color-scheme: dark)").matches)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const media = window.matchMedia("(prefers-color-scheme: dark)")
|
||||||
|
const onChange = (event: MediaQueryListEvent) => setSystemDark(event.matches)
|
||||||
|
|
||||||
|
media.addEventListener("change", onChange)
|
||||||
|
return () => media.removeEventListener("change", onChange)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const resolvedTheme = theme === "system" ? (systemDark ? "dark" : "light") : theme
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const root = window.document.documentElement
|
const root = window.document.documentElement
|
||||||
|
|
||||||
root.classList.remove("light", "dark")
|
root.classList.remove("light", "dark")
|
||||||
|
root.classList.add(resolvedTheme)
|
||||||
if (theme === "system") {
|
}, [resolvedTheme])
|
||||||
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
|
|
||||||
|
|
||||||
root.classList.add(systemTheme)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
root.classList.add(theme)
|
|
||||||
}, [theme])
|
|
||||||
|
|
||||||
const value = {
|
const value = {
|
||||||
theme,
|
theme,
|
||||||
|
resolvedTheme,
|
||||||
setTheme: (theme: Theme) => {
|
setTheme: (theme: Theme) => {
|
||||||
localStorage.setItem(storageKey, theme)
|
localStorage.setItem(storageKey, theme)
|
||||||
setTheme(theme)
|
setTheme(theme)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { t } from "@lingui/core/macro"
|
|||||||
import PocketBase from "pocketbase"
|
import PocketBase from "pocketbase"
|
||||||
import { basePath } from "@/components/router"
|
import { basePath } from "@/components/router"
|
||||||
import { toast } from "@/components/ui/use-toast"
|
import { toast } from "@/components/ui/use-toast"
|
||||||
|
import { dynamicActivate, getLocale } from "@/lib/i18n"
|
||||||
import type { ChartTimes, UserSettings } from "@/types"
|
import type { ChartTimes, UserSettings } from "@/types"
|
||||||
import { $alerts, $allSystemsById, $allSystemsByName, $userSettings } from "./stores"
|
import { $alerts, $allSystemsById, $allSystemsByName, $userSettings } from "./stores"
|
||||||
import { chartTimeData, debounce } from "./utils"
|
import { chartTimeData, debounce } from "./utils"
|
||||||
@@ -52,11 +53,45 @@ export function logOut() {
|
|||||||
pb.realtime.unsubscribe()
|
pb.realtime.unsubscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Save a partial update to user settings in database immediately */
|
||||||
|
export async function saveUserSettings(newSettings: Partial<UserSettings>) {
|
||||||
|
// get fresh copy of settings so concurrent changes aren't overwritten
|
||||||
|
const req = await pb.collection("user_settings").getFirstListItem("", { fields: "id,settings" })
|
||||||
|
const updatedSettings = await pb.collection("user_settings").update(req.id, {
|
||||||
|
settings: {
|
||||||
|
...req.settings,
|
||||||
|
...newSettings,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
$userSettings.set(updatedSettings.settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
// keys queued by queueUserSettings, flushed together in a single request so that
|
||||||
|
// two debounced saves for different keys can't race each other's read-modify-write
|
||||||
|
// and silently drop one of the changes
|
||||||
|
let queuedSettings: Partial<UserSettings> = {}
|
||||||
|
|
||||||
|
const flushQueuedSettings = debounce(() => {
|
||||||
|
const toSave = queuedSettings
|
||||||
|
queuedSettings = {}
|
||||||
|
if (Object.keys(toSave).length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
saveUserSettings(toSave).catch(console.error)
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
/** Queue a partial user settings update, merging with any other pending keys and saving them together after a debounce window */
|
||||||
|
export function queueUserSettings(newSettings: Partial<UserSettings>) {
|
||||||
|
queuedSettings = { ...queuedSettings, ...newSettings }
|
||||||
|
flushQueuedSettings()
|
||||||
|
}
|
||||||
|
|
||||||
/** Fetch or create user settings in database */
|
/** Fetch or create user settings in database */
|
||||||
export async function updateUserSettings() {
|
export async function updateUserSettings() {
|
||||||
try {
|
try {
|
||||||
const req = await pb.collection("user_settings").getFirstListItem("", { fields: "settings" })
|
const req = await pb.collection("user_settings").getFirstListItem("", { fields: "settings" })
|
||||||
$userSettings.set(req.settings)
|
$userSettings.set(req.settings)
|
||||||
|
dynamicActivate(req.settings.lang || getLocale())
|
||||||
return
|
return
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("get settings", e)
|
console.error("get settings", e)
|
||||||
@@ -65,6 +100,7 @@ export async function updateUserSettings() {
|
|||||||
try {
|
try {
|
||||||
const createdSettings = await pb.collection("user_settings").create({ user: pb.authStore.record?.id })
|
const createdSettings = await pb.collection("user_settings").create({ user: pb.authStore.record?.id })
|
||||||
$userSettings.set(createdSettings.settings)
|
$userSettings.set(createdSettings.settings)
|
||||||
|
dynamicActivate(createdSettings.settings.lang || getLocale())
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("create settings", e)
|
console.error("create settings", e)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: ar\n"
|
"Language: ar\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:46\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Arabic\n"
|
"Language-Team: Arabic\n"
|
||||||
"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
|
"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "نعم"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "تم تحديث إعدادات المستخدم الخاصة بك."
|
msgstr "تم تحديث إعدادات المستخدم الخاصة بك."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: bg\n"
|
"Language: bg\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:45\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Bulgarian\n"
|
"Language-Team: Bulgarian\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@@ -928,7 +928,7 @@ msgstr "Глобален"
|
|||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
msgstr "GPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
msgid "GPU Engines"
|
msgid "GPU Engines"
|
||||||
@@ -954,7 +954,7 @@ msgstr "Здраве"
|
|||||||
|
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Heartbeat"
|
msgid "Heartbeat"
|
||||||
msgstr "Heartbeat"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Heartbeat Monitoring"
|
msgid "Heartbeat Monitoring"
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "Да"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "Настройките за потребителя ти са обновени."
|
msgstr "Настройките за потребителя ти са обновени."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: cs\n"
|
"Language: cs\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:45\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Czech\n"
|
"Language-Team: Czech\n"
|
||||||
"Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n"
|
"Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n"
|
||||||
@@ -61,7 +61,7 @@ msgstr "1 hodina"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 min"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 minute"
|
msgid "1 minute"
|
||||||
@@ -78,7 +78,7 @@ msgstr "12 hodin"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "15 min"
|
msgid "15 min"
|
||||||
msgstr "15 min"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "24 hours"
|
msgid "24 hours"
|
||||||
@@ -91,7 +91,7 @@ msgstr "30 dní"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "5 min"
|
msgid "5 min"
|
||||||
msgstr "5 min"
|
msgstr ""
|
||||||
|
|
||||||
#. Table column
|
#. Table column
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
@@ -154,7 +154,7 @@ msgstr "Po nastavení proměnných prostředí restartujte hub Beszel, aby se zm
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Agent"
|
msgid "Agent"
|
||||||
msgstr "Agent"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
@@ -647,7 +647,7 @@ msgstr "Popis"
|
|||||||
|
|
||||||
#: src/components/containers-table/containers-table.tsx
|
#: src/components/containers-table/containers-table.tsx
|
||||||
msgid "Detail"
|
msgid "Detail"
|
||||||
msgstr "Detail"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Device"
|
msgid "Device"
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "Ano"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "Vaše uživatelská nastavení byla aktualizována."
|
msgstr "Vaše uživatelská nastavení byla aktualizována."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: da\n"
|
"Language: da\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:45\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Danish\n"
|
"Language-Team: Danish\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@@ -48,7 +48,7 @@ msgstr "{count, plural, one {{countString} minut} other {{countString} minutter}
|
|||||||
|
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
msgid "{diskName} I/O"
|
msgid "{diskName} I/O"
|
||||||
msgstr "{diskName} I/O"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
msgid "{threads, plural, one {# thread} other {# threads}}"
|
msgid "{threads, plural, one {# thread} other {# threads}}"
|
||||||
@@ -154,7 +154,7 @@ msgstr "Efter indstilling af miljøvariablerne skal du genstarte din Beszel-hub
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Agent"
|
msgid "Agent"
|
||||||
msgstr "Agent"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
@@ -300,7 +300,7 @@ msgstr "Binær"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bits (Kbps, Mbps, Gbps)"
|
msgid "Bits (Kbps, Mbps, Gbps)"
|
||||||
msgstr "Bits (Kbps, Mbps, Gbps)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Boot state"
|
msgid "Boot state"
|
||||||
@@ -309,7 +309,7 @@ msgstr "Opstartstilstand"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bytes (KB/s, MB/s, GB/s)"
|
msgid "Bytes (KB/s, MB/s, GB/s)"
|
||||||
msgstr "Bytes (KB/s, MB/s, GB/s)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/memory-charts.tsx
|
#: src/components/routes/system/charts/memory-charts.tsx
|
||||||
msgid "Cache / Buffers"
|
msgid "Cache / Buffers"
|
||||||
@@ -348,7 +348,7 @@ msgstr "Forsigtig - muligt tab af data"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr "Celsius (°C)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -447,7 +447,7 @@ msgstr "Forbindelsen er nede"
|
|||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Container"
|
msgid "Container"
|
||||||
msgstr "Container"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Container Health"
|
msgid "Container Health"
|
||||||
@@ -530,7 +530,7 @@ msgstr "Kerne"
|
|||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "CPU"
|
msgid "CPU"
|
||||||
msgstr "CPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -542,7 +542,7 @@ msgstr "CPU-I/O-ventetid (IOWait)"
|
|||||||
|
|
||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
msgid "CPU Peak"
|
msgid "CPU Peak"
|
||||||
msgstr "CPU Peak"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "CPU Steal Time"
|
msgid "CPU Steal Time"
|
||||||
@@ -661,7 +661,7 @@ msgstr "Aflader"
|
|||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Disk"
|
msgid "Disk"
|
||||||
msgstr "Disk"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Disk unit"
|
msgid "Disk unit"
|
||||||
@@ -732,7 +732,7 @@ msgstr "Rediger {foo}"
|
|||||||
#: src/components/login/forgot-pass-form.tsx
|
#: src/components/login/forgot-pass-form.tsx
|
||||||
#: src/components/login/otp-forms.tsx
|
#: src/components/login/otp-forms.tsx
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Email"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Email notifications"
|
msgid "Email notifications"
|
||||||
@@ -826,7 +826,7 @@ msgstr "Eksporter din nuværende systemkonfiguration."
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "Fahrenheit (°F)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
@@ -880,7 +880,7 @@ msgstr "Blæsere"
|
|||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Filter..."
|
msgid "Filter..."
|
||||||
msgstr "Filter..."
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Fingerprint"
|
msgid "Fingerprint"
|
||||||
@@ -888,7 +888,7 @@ msgstr "Fingeraftryk"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Firmware"
|
msgid "Firmware"
|
||||||
msgstr "Firmware"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
||||||
@@ -924,7 +924,7 @@ msgstr "Generelt"
|
|||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Global"
|
msgid "Global"
|
||||||
msgstr "Global"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
@@ -1091,7 +1091,7 @@ msgstr "Loginforsøg mislykkedes"
|
|||||||
#: src/components/containers-table/containers-table.tsx
|
#: src/components/containers-table/containers-table.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Logs"
|
msgid "Logs"
|
||||||
msgstr "Logs"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
|
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
|
||||||
@@ -1144,7 +1144,7 @@ msgstr "Containeres hukommelsesforbrug"
|
|||||||
#. Device model
|
#. Device model
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Model"
|
msgid "Model"
|
||||||
msgstr "Model"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/storage-pools-table.tsx
|
#: src/components/routes/system/storage-pools-table.tsx
|
||||||
msgid "Mountpoint"
|
msgid "Mountpoint"
|
||||||
@@ -1161,7 +1161,7 @@ msgstr "Navn"
|
|||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Net"
|
msgid "Net"
|
||||||
msgstr "Net"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/network-charts.tsx
|
#: src/components/routes/system/charts/network-charts.tsx
|
||||||
msgid "Network traffic of containers"
|
msgid "Network traffic of containers"
|
||||||
@@ -1311,7 +1311,7 @@ msgstr "Tidligere"
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Pause"
|
msgid "Pause"
|
||||||
msgstr "Pause"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
@@ -1393,7 +1393,7 @@ msgstr "Poolforbrug"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr "Port"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Container ports"
|
msgctxt "Container ports"
|
||||||
@@ -1516,7 +1516,7 @@ msgstr "Genoptag"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgctxt "Root disk label"
|
msgctxt "Root disk label"
|
||||||
msgid "Root"
|
msgid "Root"
|
||||||
msgstr "Root"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Rotate token"
|
msgid "Rotate token"
|
||||||
@@ -1669,7 +1669,7 @@ msgstr "Tilstand"
|
|||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Status"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
msgid "Sub State"
|
msgid "Sub State"
|
||||||
@@ -1701,7 +1701,7 @@ msgstr "Skift tema"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "System"
|
msgid "System"
|
||||||
msgstr "System"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
msgid "System fan speeds (RPM)"
|
msgid "System fan speeds (RPM)"
|
||||||
@@ -1713,7 +1713,7 @@ msgstr "Gennemsnitlig system belastning over tid"
|
|||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Systemd Services"
|
msgid "Systemd Services"
|
||||||
msgstr "Systemd Services"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Systems"
|
msgid "Systems"
|
||||||
@@ -1757,7 +1757,7 @@ msgstr "Temperaturer i systemsensorer"
|
|||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Test <0>URL</0>"
|
msgid "Test <0>URL</0>"
|
||||||
msgstr "Test <0>URL</0>"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Test heartbeat"
|
msgid "Test heartbeat"
|
||||||
@@ -1914,7 +1914,7 @@ msgstr "Udløser når brugen af en disk overstiger en tærskel"
|
|||||||
#: src/components/routes/system/smart-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
|
||||||
msgid "Type"
|
msgid "Type"
|
||||||
msgstr "Type"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Unhealthy"
|
msgid "Unhealthy"
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "Ja"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "Dine brugerindstillinger er opdateret."
|
msgstr "Dine brugerindstillinger er opdateret."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: de\n"
|
"Language: de\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:46\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: German\n"
|
"Language-Team: German\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@@ -142,7 +142,7 @@ msgstr "Breite des Hauptlayouts anpassen"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr "Admin"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "After"
|
msgid "After"
|
||||||
@@ -154,7 +154,7 @@ msgstr "Starten Sie nach dem Festlegen der Umgebungsvariablen Ihren Beszel-Hub n
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Agent"
|
msgid "Agent"
|
||||||
msgstr "Agent"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
@@ -248,7 +248,7 @@ msgstr "Durchschnittliche Auslastung der GPU-Engines"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Backups"
|
msgid "Backups"
|
||||||
msgstr "Backups"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/network-charts.tsx
|
#: src/components/routes/system/charts/network-charts.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
@@ -300,7 +300,7 @@ msgstr "Binär"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bits (Kbps, Mbps, Gbps)"
|
msgid "Bits (Kbps, Mbps, Gbps)"
|
||||||
msgstr "Bits (Kbps, Mbps, Gbps)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Boot state"
|
msgid "Boot state"
|
||||||
@@ -309,7 +309,7 @@ msgstr "Boot-Zustand"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bytes (KB/s, MB/s, GB/s)"
|
msgid "Bytes (KB/s, MB/s, GB/s)"
|
||||||
msgstr "Bytes (KB/s, MB/s, GB/s)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/memory-charts.tsx
|
#: src/components/routes/system/charts/memory-charts.tsx
|
||||||
msgid "Cache / Buffers"
|
msgid "Cache / Buffers"
|
||||||
@@ -348,7 +348,7 @@ msgstr "Vorsicht - potenzieller Datenverlust"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr "Celsius (°C)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -447,7 +447,7 @@ msgstr "Verbindung unterbrochen"
|
|||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Container"
|
msgid "Container"
|
||||||
msgstr "Container"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Container Health"
|
msgid "Container Health"
|
||||||
@@ -530,7 +530,7 @@ msgstr "Kern"
|
|||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "CPU"
|
msgid "CPU"
|
||||||
msgstr "CPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -826,7 +826,7 @@ msgstr "Exportiere die aktuelle Systemkonfiguration."
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "Fahrenheit (°F)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
@@ -880,7 +880,7 @@ msgstr "Lüfter"
|
|||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Filter..."
|
msgid "Filter..."
|
||||||
msgstr "Filter..."
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Fingerprint"
|
msgid "Fingerprint"
|
||||||
@@ -924,7 +924,7 @@ msgstr "Allgemein"
|
|||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Global"
|
msgid "Global"
|
||||||
msgstr "Global"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
@@ -954,7 +954,7 @@ msgstr "Gesundheit"
|
|||||||
|
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Heartbeat"
|
msgid "Heartbeat"
|
||||||
msgstr "Heartbeat"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Heartbeat Monitoring"
|
msgid "Heartbeat Monitoring"
|
||||||
@@ -972,7 +972,7 @@ msgstr "Homebrew-Befehl"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Host / IP"
|
msgid "Host / IP"
|
||||||
msgstr "Host / IP"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "HTTP Method"
|
msgid "HTTP Method"
|
||||||
@@ -1009,7 +1009,7 @@ msgstr "Wenn du das Passwort für dein Administratorkonto verloren hast, kannst
|
|||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Docker image"
|
msgctxt "Docker image"
|
||||||
msgid "Image"
|
msgid "Image"
|
||||||
msgstr "Image"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Inactive"
|
msgid "Inactive"
|
||||||
@@ -1156,7 +1156,7 @@ msgstr "Einhängepunkt"
|
|||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Name"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
@@ -1311,7 +1311,7 @@ msgstr "Vergangen"
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Pause"
|
msgid "Pause"
|
||||||
msgstr "Pause"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
@@ -1393,12 +1393,12 @@ msgstr "Pool-Auslastung"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr "Port"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Container ports"
|
msgctxt "Container ports"
|
||||||
msgid "Ports"
|
msgid "Ports"
|
||||||
msgstr "Ports"
|
msgstr ""
|
||||||
|
|
||||||
#. Power On Time
|
#. Power On Time
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
@@ -1516,7 +1516,7 @@ msgstr "Fortsetzen"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgctxt "Root disk label"
|
msgctxt "Root disk label"
|
||||||
msgid "Root"
|
msgid "Root"
|
||||||
msgstr "Root"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Rotate token"
|
msgid "Rotate token"
|
||||||
@@ -1669,7 +1669,7 @@ msgstr "Status"
|
|||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Status"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
msgid "Sub State"
|
msgid "Sub State"
|
||||||
@@ -1701,7 +1701,7 @@ msgstr "Design wechseln"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "System"
|
msgid "System"
|
||||||
msgstr "System"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
msgid "System fan speeds (RPM)"
|
msgid "System fan speeds (RPM)"
|
||||||
@@ -1730,7 +1730,7 @@ msgstr "Tabelle"
|
|||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
msgctxt "Tabs system layout option"
|
msgctxt "Tabs system layout option"
|
||||||
msgid "Tabs"
|
msgid "Tabs"
|
||||||
msgstr "Tabs"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Tasks"
|
msgid "Tasks"
|
||||||
@@ -1757,7 +1757,7 @@ msgstr "Temperaturen der Systemsensoren"
|
|||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Test <0>URL</0>"
|
msgid "Test <0>URL</0>"
|
||||||
msgstr "Test <0>URL</0>"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Test heartbeat"
|
msgid "Test heartbeat"
|
||||||
@@ -1802,7 +1802,7 @@ msgstr "An E-Mail(s)"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr "Token"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "Ja"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "Deine Benutzereinstellungen wurden aktualisiert."
|
msgstr "Deine Benutzereinstellungen wurden aktualisiert."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: el\n"
|
"Language: el\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:45\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Greek\n"
|
"Language-Team: Greek\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@@ -52,7 +52,7 @@ msgstr "I/O {diskName}"
|
|||||||
|
|
||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
msgid "{threads, plural, one {# thread} other {# threads}}"
|
msgid "{threads, plural, one {# thread} other {# threads}}"
|
||||||
msgstr "{threads, plural, one {# thread} other {# threads}}"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
@@ -530,7 +530,7 @@ msgstr "Βασικά"
|
|||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "CPU"
|
msgid "CPU"
|
||||||
msgstr "CPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -732,7 +732,7 @@ msgstr "Επεξεργασία {foo}"
|
|||||||
#: src/components/login/forgot-pass-form.tsx
|
#: src/components/login/forgot-pass-form.tsx
|
||||||
#: src/components/login/otp-forms.tsx
|
#: src/components/login/otp-forms.tsx
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Email"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Email notifications"
|
msgid "Email notifications"
|
||||||
@@ -928,7 +928,7 @@ msgstr "Καθολικό"
|
|||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
msgstr "GPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
msgid "GPU Engines"
|
msgid "GPU Engines"
|
||||||
@@ -954,7 +954,7 @@ msgstr "Υγεία"
|
|||||||
|
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Heartbeat"
|
msgid "Heartbeat"
|
||||||
msgstr "Heartbeat"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Heartbeat Monitoring"
|
msgid "Heartbeat Monitoring"
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "Ναι"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "Οι ρυθμίσεις χρήστη σας ενημερώθηκαν."
|
msgstr "Οι ρυθμίσεις χρήστη σας ενημερώθηκαν."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: es\n"
|
"Language: es\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:45\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Spanish\n"
|
"Language-Team: Spanish\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@@ -61,7 +61,7 @@ msgstr "1 hora"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 min"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 minute"
|
msgid "1 minute"
|
||||||
@@ -78,7 +78,7 @@ msgstr "12 horas"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "15 min"
|
msgid "15 min"
|
||||||
msgstr "15 min"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "24 hours"
|
msgid "24 hours"
|
||||||
@@ -91,7 +91,7 @@ msgstr "30 días"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "5 min"
|
msgid "5 min"
|
||||||
msgstr "5 min"
|
msgstr ""
|
||||||
|
|
||||||
#. Table column
|
#. Table column
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
@@ -258,7 +258,7 @@ msgstr "Ancho de banda"
|
|||||||
#. Battery label in systems table header
|
#. Battery label in systems table header
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Bat"
|
msgid "Bat"
|
||||||
msgstr "Bat"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
@@ -348,7 +348,7 @@ msgstr "Precaución - posible pérdida de datos"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr "Celsius (°C)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -530,7 +530,7 @@ msgstr "Núcleo"
|
|||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "CPU"
|
msgid "CPU"
|
||||||
msgstr "CPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -783,7 +783,7 @@ msgstr "Efímero"
|
|||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr "Error"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Example:"
|
msgid "Example:"
|
||||||
@@ -826,7 +826,7 @@ msgstr "Exporta la configuración actual de sus sistemas."
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "Fahrenheit (°F)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
@@ -888,7 +888,7 @@ msgstr "Huella dactilar"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Firmware"
|
msgid "Firmware"
|
||||||
msgstr "Firmware"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
||||||
@@ -920,11 +920,11 @@ msgstr "Llena"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "General"
|
msgid "General"
|
||||||
msgstr "General"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Global"
|
msgid "Global"
|
||||||
msgstr "Global"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
@@ -1183,7 +1183,7 @@ msgstr "Unidad de red"
|
|||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "No"
|
msgid "No"
|
||||||
msgstr "No"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/storage-pools-table.tsx
|
#: src/components/routes/system/storage-pools-table.tsx
|
||||||
msgid "No detail data for this pool."
|
msgid "No detail data for this pool."
|
||||||
@@ -1802,7 +1802,7 @@ msgstr "A correo(s)"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr "Token"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -1821,7 +1821,7 @@ msgstr "Los tokens y las huellas digitales se utilizan para autenticar las conex
|
|||||||
#: src/components/ui/chart.tsx
|
#: src/components/ui/chart.tsx
|
||||||
#: src/components/ui/chart.tsx
|
#: src/components/ui/chart.tsx
|
||||||
msgid "Total"
|
msgid "Total"
|
||||||
msgstr "Total"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/network-sheet.tsx
|
#: src/components/routes/system/network-sheet.tsx
|
||||||
msgid "Total data received for each interface"
|
msgid "Total data received for each interface"
|
||||||
@@ -1839,7 +1839,7 @@ msgstr "Tiempo total dedicado a lectura/escritura (puede superar el 100 %)"
|
|||||||
#. placeholder {0}: data.length
|
#. placeholder {0}: data.length
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Total: {0}"
|
msgid "Total: {0}"
|
||||||
msgstr "Total: {0}"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Triggered by"
|
msgid "Triggered by"
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "Sí"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "Tu configuración de usuario ha sido actualizada."
|
msgstr "Tu configuración de usuario ha sido actualizada."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: fa\n"
|
"Language: fa\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:46\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Persian\n"
|
"Language-Team: Persian\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "بله"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "تنظیمات کاربری شما بهروزرسانی شد."
|
msgstr "تنظیمات کاربری شما بهروزرسانی شد."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: fr\n"
|
"Language: fr\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:45\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: French\n"
|
"Language-Team: French\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
@@ -52,7 +52,7 @@ msgstr "E/S {diskName}"
|
|||||||
|
|
||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
msgid "{threads, plural, one {# thread} other {# threads}}"
|
msgid "{threads, plural, one {# thread} other {# threads}}"
|
||||||
msgstr "{threads, plural, one {# thread} other {# threads}}"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
@@ -61,11 +61,11 @@ msgstr "1 heure"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 min"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 minute"
|
msgid "1 minute"
|
||||||
msgstr "1 minute"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
@@ -78,7 +78,7 @@ msgstr "12 heures"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "15 min"
|
msgid "15 min"
|
||||||
msgstr "15 min"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "24 hours"
|
msgid "24 hours"
|
||||||
@@ -91,7 +91,7 @@ msgstr "30 jours"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "5 min"
|
msgid "5 min"
|
||||||
msgstr "5 min"
|
msgstr ""
|
||||||
|
|
||||||
#. Table column
|
#. Table column
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
@@ -100,14 +100,14 @@ msgstr "5 min"
|
|||||||
#: src/components/routes/system/storage-pools-table.tsx
|
#: src/components/routes/system/storage-pools-table.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Actions"
|
msgid "Actions"
|
||||||
msgstr "Actions"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Active"
|
msgid "Active"
|
||||||
msgstr "Active"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/active-alerts.tsx
|
#: src/components/active-alerts.tsx
|
||||||
msgid "Active Alerts"
|
msgid "Active Alerts"
|
||||||
@@ -142,7 +142,7 @@ msgstr "Ajuster la largeur de la mise en page principale"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr "Admin"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "After"
|
msgid "After"
|
||||||
@@ -154,7 +154,7 @@ msgstr "Après avoir défini les variables d'environnement, redémarrez votre hu
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Agent"
|
msgid "Agent"
|
||||||
msgstr "Agent"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
@@ -258,7 +258,7 @@ msgstr "Bande passante"
|
|||||||
#. Battery label in systems table header
|
#. Battery label in systems table header
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Bat"
|
msgid "Bat"
|
||||||
msgstr "Bat"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
@@ -300,7 +300,7 @@ msgstr "Binaire"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bits (Kbps, Mbps, Gbps)"
|
msgid "Bits (Kbps, Mbps, Gbps)"
|
||||||
msgstr "Bits (Kbps, Mbps, Gbps)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Boot state"
|
msgid "Boot state"
|
||||||
@@ -309,7 +309,7 @@ msgstr "État de démarrage"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bytes (KB/s, MB/s, GB/s)"
|
msgid "Bytes (KB/s, MB/s, GB/s)"
|
||||||
msgstr "Bytes (KB/s, MB/s, GB/s)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/memory-charts.tsx
|
#: src/components/routes/system/charts/memory-charts.tsx
|
||||||
msgid "Cache / Buffers"
|
msgid "Cache / Buffers"
|
||||||
@@ -348,7 +348,7 @@ msgstr "Attention - perte de données potentielle"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr "Celsius (°C)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -360,7 +360,7 @@ msgstr "Modifier les options générales de l'application."
|
|||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
msgid "Charge"
|
msgid "Charge"
|
||||||
msgstr "Charge"
|
msgstr ""
|
||||||
|
|
||||||
#. Context: Battery state
|
#. Context: Battery state
|
||||||
#: src/lib/i18n.ts
|
#: src/lib/i18n.ts
|
||||||
@@ -530,7 +530,7 @@ msgstr "Cœur"
|
|||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "CPU"
|
msgid "CPU"
|
||||||
msgstr "CPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -614,7 +614,7 @@ msgstr "État actuel"
|
|||||||
#. Power Cycles
|
#. Power Cycles
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Cycles"
|
msgid "Cycles"
|
||||||
msgstr "Cycles"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
@@ -643,7 +643,7 @@ msgstr "Supprimer l'empreinte"
|
|||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr "Description"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table.tsx
|
#: src/components/containers-table/containers-table.tsx
|
||||||
msgid "Detail"
|
msgid "Detail"
|
||||||
@@ -696,7 +696,7 @@ msgstr "Entrée/Sortie réseau Docker"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Documentation"
|
msgid "Documentation"
|
||||||
msgstr "Documentation"
|
msgstr ""
|
||||||
|
|
||||||
#. Context: System is down
|
#. Context: System is down
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
@@ -732,7 +732,7 @@ msgstr "Modifier {foo}"
|
|||||||
#: src/components/login/forgot-pass-form.tsx
|
#: src/components/login/forgot-pass-form.tsx
|
||||||
#: src/components/login/otp-forms.tsx
|
#: src/components/login/otp-forms.tsx
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Email"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Email notifications"
|
msgid "Email notifications"
|
||||||
@@ -826,7 +826,7 @@ msgstr "Exportez la configuration actuelle de vos systèmes."
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "Fahrenheit (°F)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
@@ -924,11 +924,11 @@ msgstr "Général"
|
|||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Global"
|
msgid "Global"
|
||||||
msgstr "Global"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
msgstr "GPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
msgid "GPU Engines"
|
msgid "GPU Engines"
|
||||||
@@ -1009,7 +1009,7 @@ msgstr "Si vous avez perdu le mot de passe de votre compte administrateur, vous
|
|||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Docker image"
|
msgctxt "Docker image"
|
||||||
msgid "Image"
|
msgid "Image"
|
||||||
msgstr "Image"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Inactive"
|
msgid "Inactive"
|
||||||
@@ -1113,7 +1113,7 @@ msgstr "Guide pour une installation manuelle"
|
|||||||
#. Chart select field. Please try to keep this short.
|
#. Chart select field. Please try to keep this short.
|
||||||
#: src/components/routes/system/chart-card.tsx
|
#: src/components/routes/system/chart-card.tsx
|
||||||
msgid "Max 1 min"
|
msgid "Max 1 min"
|
||||||
msgstr "Max 1 min"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
@@ -1220,7 +1220,7 @@ msgstr "Aucun"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Notifications"
|
msgid "Notifications"
|
||||||
msgstr "Notifications"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Notifications may include recent container log excerpts."
|
msgid "Notifications may include recent container log excerpts."
|
||||||
@@ -1276,7 +1276,7 @@ msgstr "Écraser les alertes existantes"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
msgid "Page"
|
msgid "Page"
|
||||||
msgstr "Page"
|
msgstr ""
|
||||||
|
|
||||||
#. placeholder {0}: table.getState().pagination.pageIndex + 1
|
#. placeholder {0}: table.getState().pagination.pageIndex + 1
|
||||||
#. placeholder {1}: table.getPageCount()
|
#. placeholder {1}: table.getPageCount()
|
||||||
@@ -1311,7 +1311,7 @@ msgstr "Passé"
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Pause"
|
msgid "Pause"
|
||||||
msgstr "Pause"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
@@ -1340,7 +1340,7 @@ msgstr "Pourcentage de temps passé dans chaque état"
|
|||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Permanent"
|
msgid "Permanent"
|
||||||
msgstr "Permanent"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Persistence"
|
msgid "Persistence"
|
||||||
@@ -1393,12 +1393,12 @@ msgstr "Utilisation du pool"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr "Port"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Container ports"
|
msgctxt "Container ports"
|
||||||
msgid "Ports"
|
msgid "Ports"
|
||||||
msgstr "Ports"
|
msgstr ""
|
||||||
|
|
||||||
#. Power On Time
|
#. Power On Time
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
@@ -1614,7 +1614,7 @@ msgstr "Détails du service"
|
|||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Services"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Set percentage thresholds for meter colors."
|
msgid "Set percentage thresholds for meter colors."
|
||||||
@@ -1802,7 +1802,7 @@ msgstr "Aux email(s)"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr "Token"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -1821,7 +1821,7 @@ msgstr "Les tokens et les empreintes sont utilisés pour authentifier les connex
|
|||||||
#: src/components/ui/chart.tsx
|
#: src/components/ui/chart.tsx
|
||||||
#: src/components/ui/chart.tsx
|
#: src/components/ui/chart.tsx
|
||||||
msgid "Total"
|
msgid "Total"
|
||||||
msgstr "Total"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/network-sheet.tsx
|
#: src/components/routes/system/network-sheet.tsx
|
||||||
msgid "Total data received for each interface"
|
msgid "Total data received for each interface"
|
||||||
@@ -1914,7 +1914,7 @@ msgstr "Déclenchement lorsque l'utilisation de tout disque dépasse un seuil"
|
|||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
#: src/components/routes/system/storage-pools-table.tsx
|
#: src/components/routes/system/storage-pools-table.tsx
|
||||||
msgid "Type"
|
msgid "Type"
|
||||||
msgstr "Type"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Unhealthy"
|
msgid "Unhealthy"
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "Oui"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "Vos paramètres utilisateur ont été mis à jour."
|
msgstr "Vos paramètres utilisateur ont été mis à jour."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: he\n"
|
"Language: he\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:45\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Hebrew\n"
|
"Language-Team: Hebrew\n"
|
||||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n==2 ? 1 : 2);\n"
|
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n==2 ? 1 : 2);\n"
|
||||||
@@ -530,7 +530,7 @@ msgstr "ליבה"
|
|||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "CPU"
|
msgid "CPU"
|
||||||
msgstr "CPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -1802,7 +1802,7 @@ msgstr "לאימייל(ים)"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr "Token"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "כן"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "הגדרות המשתמש שלך עודכנו."
|
msgstr "הגדרות המשתמש שלך עודכנו."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: hr\n"
|
"Language: hr\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:46\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Croatian\n"
|
"Language-Team: Croatian\n"
|
||||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||||
@@ -142,7 +142,7 @@ msgstr "Prilagodite širinu glavnog rasporeda"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr "Admin"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "After"
|
msgid "After"
|
||||||
@@ -154,7 +154,7 @@ msgstr "Nakon postavljanja varijabli okruženja, ponovno pokrenite svoj Beszel h
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Agent"
|
msgid "Agent"
|
||||||
msgstr "Agent"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
@@ -348,7 +348,7 @@ msgstr "Oprez - mogući gubitak podataka"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr "Celsius (°C)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -661,7 +661,7 @@ msgstr "Prazni se"
|
|||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Disk"
|
msgid "Disk"
|
||||||
msgstr "Disk"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Disk unit"
|
msgid "Disk unit"
|
||||||
@@ -732,7 +732,7 @@ msgstr "Uredi {foo}"
|
|||||||
#: src/components/login/forgot-pass-form.tsx
|
#: src/components/login/forgot-pass-form.tsx
|
||||||
#: src/components/login/otp-forms.tsx
|
#: src/components/login/otp-forms.tsx
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Email"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Email notifications"
|
msgid "Email notifications"
|
||||||
@@ -972,7 +972,7 @@ msgstr "Homebrew naredba"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Host / IP"
|
msgid "Host / IP"
|
||||||
msgstr "Host / IP"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "HTTP Method"
|
msgid "HTTP Method"
|
||||||
@@ -1393,7 +1393,7 @@ msgstr "Iskorištenost spremišta"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr "Port"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Container ports"
|
msgctxt "Container ports"
|
||||||
@@ -1669,7 +1669,7 @@ msgstr "Stanje"
|
|||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Status"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
msgid "Sub State"
|
msgid "Sub State"
|
||||||
@@ -1740,7 +1740,7 @@ msgstr "Zadaci"
|
|||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Temp"
|
msgid "Temp"
|
||||||
msgstr "Temp"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
@@ -1802,7 +1802,7 @@ msgstr "Primaoci emaila"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr "Token"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "Da"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "Vaše korisničke postavke su ažurirane."
|
msgstr "Vaše korisničke postavke su ažurirane."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: hu\n"
|
"Language: hu\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:45\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Hungarian\n"
|
"Language-Team: Hungarian\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@@ -48,7 +48,7 @@ msgstr "{count, plural, one {{countString} perc} few {{countString} perc} many {
|
|||||||
|
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
msgid "{diskName} I/O"
|
msgid "{diskName} I/O"
|
||||||
msgstr "{diskName} I/O"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
msgid "{threads, plural, one {# thread} other {# threads}}"
|
msgid "{threads, plural, one {# thread} other {# threads}}"
|
||||||
@@ -348,7 +348,7 @@ msgstr "Figyelem - potenciális adatvesztés"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr "Celsius (°C)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -530,7 +530,7 @@ msgstr "Mag"
|
|||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "CPU"
|
msgid "CPU"
|
||||||
msgstr "CPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -732,7 +732,7 @@ msgstr "Szerkesztés {foo}"
|
|||||||
#: src/components/login/forgot-pass-form.tsx
|
#: src/components/login/forgot-pass-form.tsx
|
||||||
#: src/components/login/otp-forms.tsx
|
#: src/components/login/otp-forms.tsx
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Email"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Email notifications"
|
msgid "Email notifications"
|
||||||
@@ -826,7 +826,7 @@ msgstr "Exportálja a jelenlegi rendszerkonfigurációt."
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "Fahrenheit (°F)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
@@ -888,7 +888,7 @@ msgstr "Ujjlenyomat"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Firmware"
|
msgid "Firmware"
|
||||||
msgstr "Firmware"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
||||||
@@ -1393,7 +1393,7 @@ msgstr "Tárkészlet kihasználtsága"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr "Port"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Container ports"
|
msgctxt "Container ports"
|
||||||
@@ -1802,7 +1802,7 @@ msgstr "E-mailben"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr "Token"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "Igen"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "A felhasználói beállítások frissítésre kerültek."
|
msgstr "A felhasználói beállítások frissítésre kerültek."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: id\n"
|
"Language: id\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:46\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Indonesian\n"
|
"Language-Team: Indonesian\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
@@ -142,7 +142,7 @@ msgstr "Sesuaikan lebar layar utama"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr "Admin"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "After"
|
msgid "After"
|
||||||
@@ -313,7 +313,7 @@ msgstr "Byte (KB/s, MB/s, GB/s)"
|
|||||||
|
|
||||||
#: src/components/routes/system/charts/memory-charts.tsx
|
#: src/components/routes/system/charts/memory-charts.tsx
|
||||||
msgid "Cache / Buffers"
|
msgid "Cache / Buffers"
|
||||||
msgstr "Cache / Buffers"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Can reload"
|
msgid "Can reload"
|
||||||
@@ -348,7 +348,7 @@ msgstr "Perhatian - potensi kehilangan data"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr "Celsius (°C)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "Ya"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "Pengaturan pengguna anda telah diperbarui."
|
msgstr "Pengaturan pengguna anda telah diperbarui."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: it\n"
|
"Language: it\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:45\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Italian\n"
|
"Language-Team: Italian\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@@ -61,7 +61,7 @@ msgstr "1 ora"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 min"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 minute"
|
msgid "1 minute"
|
||||||
@@ -78,7 +78,7 @@ msgstr "12 ore"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "15 min"
|
msgid "15 min"
|
||||||
msgstr "15 min"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "24 hours"
|
msgid "24 hours"
|
||||||
@@ -91,7 +91,7 @@ msgstr "30 giorni"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "5 min"
|
msgid "5 min"
|
||||||
msgstr "5 min"
|
msgstr ""
|
||||||
|
|
||||||
#. Table column
|
#. Table column
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
@@ -348,7 +348,7 @@ msgstr "Attenzione - possibile perdita di dati"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr "Celsius (°C)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -447,7 +447,7 @@ msgstr "La connessione è interrotta"
|
|||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Container"
|
msgid "Container"
|
||||||
msgstr "Container"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Container Health"
|
msgid "Container Health"
|
||||||
@@ -530,7 +530,7 @@ msgstr "Interne"
|
|||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "CPU"
|
msgid "CPU"
|
||||||
msgstr "CPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -679,7 +679,7 @@ msgstr "Utilizzo del disco di {extraFsName}"
|
|||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
msgctxt "Layout display options"
|
msgctxt "Layout display options"
|
||||||
msgid "Display"
|
msgid "Display"
|
||||||
msgstr "Display"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/cpu-charts.tsx
|
#: src/components/routes/system/charts/cpu-charts.tsx
|
||||||
msgid "Docker CPU Usage"
|
msgid "Docker CPU Usage"
|
||||||
@@ -732,7 +732,7 @@ msgstr "Modifica {foo}"
|
|||||||
#: src/components/login/forgot-pass-form.tsx
|
#: src/components/login/forgot-pass-form.tsx
|
||||||
#: src/components/login/otp-forms.tsx
|
#: src/components/login/otp-forms.tsx
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Email"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Email notifications"
|
msgid "Email notifications"
|
||||||
@@ -826,7 +826,7 @@ msgstr "Esporta la configurazione attuale dei tuoi sistemi."
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "Fahrenheit (°F)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
@@ -888,7 +888,7 @@ msgstr "Impronta digitale"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Firmware"
|
msgid "Firmware"
|
||||||
msgstr "Firmware"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
||||||
@@ -928,7 +928,7 @@ msgstr "Globale"
|
|||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
msgstr "GPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
msgid "GPU Engines"
|
msgid "GPU Engines"
|
||||||
@@ -954,7 +954,7 @@ msgstr "Stato"
|
|||||||
|
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Heartbeat"
|
msgid "Heartbeat"
|
||||||
msgstr "Heartbeat"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Heartbeat Monitoring"
|
msgid "Heartbeat Monitoring"
|
||||||
@@ -972,7 +972,7 @@ msgstr "Comando Homebrew"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Host / IP"
|
msgid "Host / IP"
|
||||||
msgstr "Host / IP"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "HTTP Method"
|
msgid "HTTP Method"
|
||||||
@@ -1113,7 +1113,7 @@ msgstr "Istruzioni di configurazione manuale"
|
|||||||
#. Chart select field. Please try to keep this short.
|
#. Chart select field. Please try to keep this short.
|
||||||
#: src/components/routes/system/chart-card.tsx
|
#: src/components/routes/system/chart-card.tsx
|
||||||
msgid "Max 1 min"
|
msgid "Max 1 min"
|
||||||
msgstr "Max 1 min"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
@@ -1183,7 +1183,7 @@ msgstr "Unità rete"
|
|||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "No"
|
msgid "No"
|
||||||
msgstr "No"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/storage-pools-table.tsx
|
#: src/components/routes/system/storage-pools-table.tsx
|
||||||
msgid "No detail data for this pool."
|
msgid "No detail data for this pool."
|
||||||
@@ -1291,7 +1291,7 @@ msgstr "Pagine / Impostazioni"
|
|||||||
#: src/components/login/auth-form.tsx
|
#: src/components/login/auth-form.tsx
|
||||||
#: src/components/login/auth-form.tsx
|
#: src/components/login/auth-form.tsx
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "Password"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/login/auth-form.tsx
|
#: src/components/login/auth-form.tsx
|
||||||
msgid "Password must be at least 8 characters."
|
msgid "Password must be at least 8 characters."
|
||||||
@@ -1516,7 +1516,7 @@ msgstr "Riprendi"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgctxt "Root disk label"
|
msgctxt "Root disk label"
|
||||||
msgid "Root"
|
msgid "Root"
|
||||||
msgstr "Root"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Rotate token"
|
msgid "Rotate token"
|
||||||
@@ -1757,7 +1757,7 @@ msgstr "Temperature dei sensori di sistema"
|
|||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Test <0>URL</0>"
|
msgid "Test <0>URL</0>"
|
||||||
msgstr "Test <0>URL</0>"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Test heartbeat"
|
msgid "Test heartbeat"
|
||||||
@@ -1802,7 +1802,7 @@ msgstr "A email(s)"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr "Token"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "Sì"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "Le impostazioni utente sono state aggiornate."
|
msgstr "Le impostazioni utente sono state aggiornate."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: ja\n"
|
"Language: ja\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:45\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Japanese\n"
|
"Language-Team: Japanese\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "はい"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "ユーザー設定が更新されました。"
|
msgstr "ユーザー設定が更新されました。"
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: ko\n"
|
"Language: ko\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:45\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Korean\n"
|
"Language-Team: Korean\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
@@ -48,7 +48,7 @@ msgstr "{count, plural, one {{countString} 분} few {{countString} 분} many {{c
|
|||||||
|
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
msgid "{diskName} I/O"
|
msgid "{diskName} I/O"
|
||||||
msgstr "{diskName} I/O"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
msgid "{threads, plural, one {# thread} other {# threads}}"
|
msgid "{threads, plural, one {# thread} other {# threads}}"
|
||||||
@@ -530,7 +530,7 @@ msgstr "코어"
|
|||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "CPU"
|
msgid "CPU"
|
||||||
msgstr "CPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "예"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "사용자 설정이 업데이트되었습니다."
|
msgstr "사용자 설정이 업데이트되었습니다."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: nl\n"
|
"Language: nl\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:45\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Dutch\n"
|
"Language-Team: Dutch\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@@ -52,7 +52,7 @@ msgstr "I/O van {diskName}"
|
|||||||
|
|
||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
msgid "{threads, plural, one {# thread} other {# threads}}"
|
msgid "{threads, plural, one {# thread} other {# threads}}"
|
||||||
msgstr "{threads, plural, one {# thread} other {# threads}}"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
@@ -69,7 +69,7 @@ msgstr "1 minuut"
|
|||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1 week"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "12 hours"
|
msgid "12 hours"
|
||||||
@@ -154,7 +154,7 @@ msgstr "Start na het instellen van de omgevingsvariabelen je Beszel-hub opnieuw
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Agent"
|
msgid "Agent"
|
||||||
msgstr "Agent"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
@@ -258,7 +258,7 @@ msgstr "Bandbreedte"
|
|||||||
#. Battery label in systems table header
|
#. Battery label in systems table header
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Bat"
|
msgid "Bat"
|
||||||
msgstr "Bat"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
@@ -300,7 +300,7 @@ msgstr "Binair"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bits (Kbps, Mbps, Gbps)"
|
msgid "Bits (Kbps, Mbps, Gbps)"
|
||||||
msgstr "Bits (Kbps, Mbps, Gbps)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Boot state"
|
msgid "Boot state"
|
||||||
@@ -309,11 +309,11 @@ msgstr "Opstartstatus"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bytes (KB/s, MB/s, GB/s)"
|
msgid "Bytes (KB/s, MB/s, GB/s)"
|
||||||
msgstr "Bytes (KB/s, MB/s, GB/s)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/memory-charts.tsx
|
#: src/components/routes/system/charts/memory-charts.tsx
|
||||||
msgid "Cache / Buffers"
|
msgid "Cache / Buffers"
|
||||||
msgstr "Cache / Buffers"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Can reload"
|
msgid "Can reload"
|
||||||
@@ -348,7 +348,7 @@ msgstr "Opgelet - potentieel gegevensverlies"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr "Celsius (°C)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -447,7 +447,7 @@ msgstr "Verbinding is niet actief"
|
|||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Container"
|
msgid "Container"
|
||||||
msgstr "Container"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Container Health"
|
msgid "Container Health"
|
||||||
@@ -455,7 +455,7 @@ msgstr "Containergezondheid"
|
|||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Containers"
|
msgid "Containers"
|
||||||
msgstr "Containers"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
@@ -530,7 +530,7 @@ msgstr "Kern"
|
|||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "CPU"
|
msgid "CPU"
|
||||||
msgstr "CPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -826,7 +826,7 @@ msgstr "Exporteer je huidige systeemconfiguratie."
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "Fahrenheit (°F)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
@@ -888,7 +888,7 @@ msgstr "Vingerafdruk"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Firmware"
|
msgid "Firmware"
|
||||||
msgstr "Firmware"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
||||||
@@ -928,7 +928,7 @@ msgstr "Globaal"
|
|||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
msgstr "GPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
msgid "GPU Engines"
|
msgid "GPU Engines"
|
||||||
@@ -954,7 +954,7 @@ msgstr "Gezondheid"
|
|||||||
|
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Heartbeat"
|
msgid "Heartbeat"
|
||||||
msgstr "Heartbeat"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Heartbeat Monitoring"
|
msgid "Heartbeat Monitoring"
|
||||||
@@ -1009,7 +1009,7 @@ msgstr "Als je het wachtwoord voor je beheerdersaccount bent kwijtgeraakt, kan j
|
|||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Docker image"
|
msgctxt "Docker image"
|
||||||
msgid "Image"
|
msgid "Image"
|
||||||
msgstr "Image"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Inactive"
|
msgid "Inactive"
|
||||||
@@ -1017,7 +1017,7 @@ msgstr "Inactief"
|
|||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Interval"
|
msgid "Interval"
|
||||||
msgstr "Interval"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/login/auth-form.tsx
|
#: src/components/login/auth-form.tsx
|
||||||
msgid "Invalid email address."
|
msgid "Invalid email address."
|
||||||
@@ -1113,7 +1113,7 @@ msgstr "Handmatige installatie-instructies"
|
|||||||
#. Chart select field. Please try to keep this short.
|
#. Chart select field. Please try to keep this short.
|
||||||
#: src/components/routes/system/chart-card.tsx
|
#: src/components/routes/system/chart-card.tsx
|
||||||
msgid "Max 1 min"
|
msgid "Max 1 min"
|
||||||
msgstr "Max 1 min"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
@@ -1144,7 +1144,7 @@ msgstr "Geheugengebruik van containers"
|
|||||||
#. Device model
|
#. Device model
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Model"
|
msgid "Model"
|
||||||
msgstr "Model"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/storage-pools-table.tsx
|
#: src/components/routes/system/storage-pools-table.tsx
|
||||||
msgid "Mountpoint"
|
msgid "Mountpoint"
|
||||||
@@ -1516,7 +1516,7 @@ msgstr "Hervatten"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgctxt "Root disk label"
|
msgctxt "Root disk label"
|
||||||
msgid "Root"
|
msgid "Root"
|
||||||
msgstr "Root"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Rotate token"
|
msgid "Rotate token"
|
||||||
@@ -1614,7 +1614,7 @@ msgstr "Servicedetails"
|
|||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Services"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Set percentage thresholds for meter colors."
|
msgid "Set percentage thresholds for meter colors."
|
||||||
@@ -1669,7 +1669,7 @@ msgstr "Status"
|
|||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Status"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
msgid "Sub State"
|
msgid "Sub State"
|
||||||
@@ -1757,7 +1757,7 @@ msgstr "Temperatuur van systeem sensoren"
|
|||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Test <0>URL</0>"
|
msgid "Test <0>URL</0>"
|
||||||
msgstr "Test <0>URL</0>"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Test heartbeat"
|
msgid "Test heartbeat"
|
||||||
@@ -1802,7 +1802,7 @@ msgstr "Naar e-mail(s)"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr "Token"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -1847,7 +1847,7 @@ msgstr "Geactiveerd door"
|
|||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Triggers"
|
msgid "Triggers"
|
||||||
msgstr "Triggers"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Triggers when 1 minute load average exceeds a threshold"
|
msgid "Triggers when 1 minute load average exceeds a threshold"
|
||||||
@@ -1914,7 +1914,7 @@ msgstr "Triggert wanneer het gebruik van een schijf een drempelwaarde overschrij
|
|||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
#: src/components/routes/system/storage-pools-table.tsx
|
#: src/components/routes/system/storage-pools-table.tsx
|
||||||
msgid "Type"
|
msgid "Type"
|
||||||
msgstr "Type"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Unhealthy"
|
msgid "Unhealthy"
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "Ja"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "Je gebruikersinstellingen zijn bijgewerkt."
|
msgstr "Je gebruikersinstellingen zijn bijgewerkt."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: no\n"
|
"Language: no\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:45\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Norwegian\n"
|
"Language-Team: Norwegian\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@@ -61,7 +61,7 @@ msgstr "1 time"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 min"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 minute"
|
msgid "1 minute"
|
||||||
@@ -78,7 +78,7 @@ msgstr "12 timer"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "15 min"
|
msgid "15 min"
|
||||||
msgstr "15 min"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "24 hours"
|
msgid "24 hours"
|
||||||
@@ -91,7 +91,7 @@ msgstr "30 dager"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "5 min"
|
msgid "5 min"
|
||||||
msgstr "5 min"
|
msgstr ""
|
||||||
|
|
||||||
#. Table column
|
#. Table column
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
@@ -142,7 +142,7 @@ msgstr "Juster bredden på hovedlayouten"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr "Admin"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "After"
|
msgid "After"
|
||||||
@@ -154,7 +154,7 @@ msgstr "Etter å ha angitt miljøvariablene, start Beszel-huben på nytt for at
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Agent"
|
msgid "Agent"
|
||||||
msgstr "Agent"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
@@ -300,7 +300,7 @@ msgstr "Binær"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bits (Kbps, Mbps, Gbps)"
|
msgid "Bits (Kbps, Mbps, Gbps)"
|
||||||
msgstr "Bits (Kbps, Mbps, Gbps)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Boot state"
|
msgid "Boot state"
|
||||||
@@ -309,7 +309,7 @@ msgstr "Oppstartstilstand"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bytes (KB/s, MB/s, GB/s)"
|
msgid "Bytes (KB/s, MB/s, GB/s)"
|
||||||
msgstr "Bytes (KB/s, MB/s, GB/s)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/memory-charts.tsx
|
#: src/components/routes/system/charts/memory-charts.tsx
|
||||||
msgid "Cache / Buffers"
|
msgid "Cache / Buffers"
|
||||||
@@ -348,7 +348,7 @@ msgstr "Advarsel - potensielt tap av data"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr "Celsius (°C)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -447,7 +447,7 @@ msgstr "Tilkoblingen er nede"
|
|||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Container"
|
msgid "Container"
|
||||||
msgstr "Container"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Container Health"
|
msgid "Container Health"
|
||||||
@@ -530,7 +530,7 @@ msgstr "Kjerne"
|
|||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "CPU"
|
msgid "CPU"
|
||||||
msgstr "CPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -661,7 +661,7 @@ msgstr "Lader ut"
|
|||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Disk"
|
msgid "Disk"
|
||||||
msgstr "Disk"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Disk unit"
|
msgid "Disk unit"
|
||||||
@@ -826,7 +826,7 @@ msgstr "Eksporter din nåværende systemkonfigurasjon"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "Fahrenheit (°F)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
@@ -880,7 +880,7 @@ msgstr "Vifter"
|
|||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Filter..."
|
msgid "Filter..."
|
||||||
msgstr "Filter..."
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Fingerprint"
|
msgid "Fingerprint"
|
||||||
@@ -924,11 +924,11 @@ msgstr "Generelt"
|
|||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Global"
|
msgid "Global"
|
||||||
msgstr "Global"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
msgstr "GPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
msgid "GPU Engines"
|
msgid "GPU Engines"
|
||||||
@@ -1009,7 +1009,7 @@ msgstr "Dersom du har mistet passordet til admin-kontoen kan du nullstille det m
|
|||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Docker image"
|
msgctxt "Docker image"
|
||||||
msgid "Image"
|
msgid "Image"
|
||||||
msgstr "Image"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Inactive"
|
msgid "Inactive"
|
||||||
@@ -1311,7 +1311,7 @@ msgstr "Fortid"
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Pause"
|
msgid "Pause"
|
||||||
msgstr "Pause"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
@@ -1340,7 +1340,7 @@ msgstr "Prosentandel av tid brukt i hver tilstand"
|
|||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Permanent"
|
msgid "Permanent"
|
||||||
msgstr "Permanent"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Persistence"
|
msgid "Persistence"
|
||||||
@@ -1393,7 +1393,7 @@ msgstr "Poolbruk"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr "Port"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Container ports"
|
msgctxt "Container ports"
|
||||||
@@ -1669,7 +1669,7 @@ msgstr "Tilstand"
|
|||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Status"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
msgid "Sub State"
|
msgid "Sub State"
|
||||||
@@ -1701,7 +1701,7 @@ msgstr "Bytt tema"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "System"
|
msgid "System"
|
||||||
msgstr "System"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
msgid "System fan speeds (RPM)"
|
msgid "System fan speeds (RPM)"
|
||||||
@@ -1740,7 +1740,7 @@ msgstr "Oppgaver"
|
|||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Temp"
|
msgid "Temp"
|
||||||
msgstr "Temp"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
@@ -1757,7 +1757,7 @@ msgstr "Temperaturer på system-sensorer"
|
|||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Test <0>URL</0>"
|
msgid "Test <0>URL</0>"
|
||||||
msgstr "Test <0>URL</0>"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Test heartbeat"
|
msgid "Test heartbeat"
|
||||||
@@ -1802,7 +1802,7 @@ msgstr "Til e-postadresse(r)"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr "Token"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -1914,7 +1914,7 @@ msgstr "Slår inn når forbruk av hvilken som helst disk overstiger en grensever
|
|||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
#: src/components/routes/system/storage-pools-table.tsx
|
#: src/components/routes/system/storage-pools-table.tsx
|
||||||
msgid "Type"
|
msgid "Type"
|
||||||
msgstr "Type"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Unhealthy"
|
msgid "Unhealthy"
|
||||||
@@ -1932,7 +1932,7 @@ msgstr "Enhetspreferanser"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Universal token"
|
msgid "Universal token"
|
||||||
msgstr "Universal token"
|
msgstr ""
|
||||||
|
|
||||||
#. Context: Battery state
|
#. Context: Battery state
|
||||||
#: src/components/routes/system/storage-pools-table.tsx
|
#: src/components/routes/system/storage-pools-table.tsx
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "Ja"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "Dine brukerinnstillinger har blitt oppdatert."
|
msgstr "Dine brukerinnstillinger har blitt oppdatert."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: pl\n"
|
"Language: pl\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:45\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Polish\n"
|
"Language-Team: Polish\n"
|
||||||
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
|
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
|
||||||
@@ -61,7 +61,7 @@ msgstr "1 godzina"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 min"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 minute"
|
msgid "1 minute"
|
||||||
@@ -78,7 +78,7 @@ msgstr "12 godzin"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "15 min"
|
msgid "15 min"
|
||||||
msgstr "15 min"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "24 hours"
|
msgid "24 hours"
|
||||||
@@ -91,7 +91,7 @@ msgstr "30 dni"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "5 min"
|
msgid "5 min"
|
||||||
msgstr "5 min"
|
msgstr ""
|
||||||
|
|
||||||
#. Table column
|
#. Table column
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
@@ -142,7 +142,7 @@ msgstr "Dostosuj szerokość widoku"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr "Admin"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "After"
|
msgid "After"
|
||||||
@@ -154,7 +154,7 @@ msgstr "Po ustawieniu zmiennych środowiskowych zrestartuj Beszel hub, aby zmian
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Agent"
|
msgid "Agent"
|
||||||
msgstr "Agent"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
@@ -826,7 +826,7 @@ msgstr "Eksportuj aktualną konfigurację systemów."
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "Fahrenheit (°F)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
@@ -928,7 +928,7 @@ msgstr "Globalny"
|
|||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
msgstr "GPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
msgid "GPU Engines"
|
msgid "GPU Engines"
|
||||||
@@ -954,7 +954,7 @@ msgstr "Kondycja"
|
|||||||
|
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Heartbeat"
|
msgid "Heartbeat"
|
||||||
msgstr "Heartbeat"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Heartbeat Monitoring"
|
msgid "Heartbeat Monitoring"
|
||||||
@@ -1042,7 +1042,7 @@ msgstr "Cykl życia"
|
|||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "limit"
|
msgid "limit"
|
||||||
msgstr "limit"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "Load Average"
|
msgid "Load Average"
|
||||||
@@ -1144,7 +1144,7 @@ msgstr "Zużycie pamięci przez kontenery"
|
|||||||
#. Device model
|
#. Device model
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Model"
|
msgid "Model"
|
||||||
msgstr "Model"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/storage-pools-table.tsx
|
#: src/components/routes/system/storage-pools-table.tsx
|
||||||
msgid "Mountpoint"
|
msgid "Mountpoint"
|
||||||
@@ -1393,7 +1393,7 @@ msgstr "Użycie puli"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr "Port"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Container ports"
|
msgctxt "Container ports"
|
||||||
@@ -1516,7 +1516,7 @@ msgstr "Wznów"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgctxt "Root disk label"
|
msgctxt "Root disk label"
|
||||||
msgid "Root"
|
msgid "Root"
|
||||||
msgstr "Root"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Rotate token"
|
msgid "Rotate token"
|
||||||
@@ -1669,7 +1669,7 @@ msgstr "Stan"
|
|||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Status"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
msgid "Sub State"
|
msgid "Sub State"
|
||||||
@@ -1701,7 +1701,7 @@ msgstr "Zmień motyw"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "System"
|
msgid "System"
|
||||||
msgstr "System"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
msgid "System fan speeds (RPM)"
|
msgid "System fan speeds (RPM)"
|
||||||
@@ -1757,7 +1757,7 @@ msgstr "Temperatury czujników systemowych."
|
|||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Test <0>URL</0>"
|
msgid "Test <0>URL</0>"
|
||||||
msgstr "Test <0>URL</0>"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Test heartbeat"
|
msgid "Test heartbeat"
|
||||||
@@ -1802,7 +1802,7 @@ msgstr "Do e-mail(ów)"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr "Token"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "Tak"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "Twoje ustawienia użytkownika zostały zaktualizowane."
|
msgstr "Twoje ustawienia użytkownika zostały zaktualizowane."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: pt\n"
|
"Language: pt\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:45\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Portuguese\n"
|
"Language-Team: Portuguese\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@@ -52,7 +52,7 @@ msgstr "E/S de {diskName}"
|
|||||||
|
|
||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
msgid "{threads, plural, one {# thread} other {# threads}}"
|
msgid "{threads, plural, one {# thread} other {# threads}}"
|
||||||
msgstr "{threads, plural, one {# thread} other {# threads}}"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
@@ -61,7 +61,7 @@ msgstr "1 hora"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 min"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 minute"
|
msgid "1 minute"
|
||||||
@@ -78,7 +78,7 @@ msgstr "12 horas"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "15 min"
|
msgid "15 min"
|
||||||
msgstr "15 min"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "24 hours"
|
msgid "24 hours"
|
||||||
@@ -91,7 +91,7 @@ msgstr "30 dias"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "5 min"
|
msgid "5 min"
|
||||||
msgstr "5 min"
|
msgstr ""
|
||||||
|
|
||||||
#. Table column
|
#. Table column
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
@@ -142,7 +142,7 @@ msgstr "Ajustar a largura do layout principal"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr "Admin"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "After"
|
msgid "After"
|
||||||
@@ -258,7 +258,7 @@ msgstr "Largura de Banda"
|
|||||||
#. Battery label in systems table header
|
#. Battery label in systems table header
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Bat"
|
msgid "Bat"
|
||||||
msgstr "Bat"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
@@ -300,7 +300,7 @@ msgstr "Binário"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bits (Kbps, Mbps, Gbps)"
|
msgid "Bits (Kbps, Mbps, Gbps)"
|
||||||
msgstr "Bits (Kbps, Mbps, Gbps)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Boot state"
|
msgid "Boot state"
|
||||||
@@ -309,11 +309,11 @@ msgstr "Estado de inicialização"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bytes (KB/s, MB/s, GB/s)"
|
msgid "Bytes (KB/s, MB/s, GB/s)"
|
||||||
msgstr "Bytes (KB/s, MB/s, GB/s)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/memory-charts.tsx
|
#: src/components/routes/system/charts/memory-charts.tsx
|
||||||
msgid "Cache / Buffers"
|
msgid "Cache / Buffers"
|
||||||
msgstr "Cache / Buffers"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Can reload"
|
msgid "Can reload"
|
||||||
@@ -348,7 +348,7 @@ msgstr "Cuidado - possível perda de dados"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr "Celsius (°C)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -530,7 +530,7 @@ msgstr "Núcleos"
|
|||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "CPU"
|
msgid "CPU"
|
||||||
msgstr "CPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -732,7 +732,7 @@ msgstr "Editar {foo}"
|
|||||||
#: src/components/login/forgot-pass-form.tsx
|
#: src/components/login/forgot-pass-form.tsx
|
||||||
#: src/components/login/otp-forms.tsx
|
#: src/components/login/otp-forms.tsx
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Email"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Email notifications"
|
msgid "Email notifications"
|
||||||
@@ -826,7 +826,7 @@ msgstr "Exporte a configuração atual dos seus sistemas."
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "Fahrenheit (°F)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
@@ -888,7 +888,7 @@ msgstr "Impressão digital"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Firmware"
|
msgid "Firmware"
|
||||||
msgstr "Firmware"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
|
||||||
@@ -924,11 +924,11 @@ msgstr "Geral"
|
|||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Global"
|
msgid "Global"
|
||||||
msgstr "Global"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
msgstr "GPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
msgid "GPU Engines"
|
msgid "GPU Engines"
|
||||||
@@ -972,7 +972,7 @@ msgstr "Comando Homebrew"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Host / IP"
|
msgid "Host / IP"
|
||||||
msgstr "Host / IP"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "HTTP Method"
|
msgid "HTTP Method"
|
||||||
@@ -1091,7 +1091,7 @@ msgstr "Tentativa de login falhou"
|
|||||||
#: src/components/containers-table/containers-table.tsx
|
#: src/components/containers-table/containers-table.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Logs"
|
msgid "Logs"
|
||||||
msgstr "Logs"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
|
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
|
||||||
@@ -1740,7 +1740,7 @@ msgstr "Tarefas"
|
|||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Temp"
|
msgid "Temp"
|
||||||
msgstr "Temp"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
@@ -1802,7 +1802,7 @@ msgstr "Para email(s)"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr "Token"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -1821,7 +1821,7 @@ msgstr "Tokens e impressões digitais são usados para autenticar conexões WebS
|
|||||||
#: src/components/ui/chart.tsx
|
#: src/components/ui/chart.tsx
|
||||||
#: src/components/ui/chart.tsx
|
#: src/components/ui/chart.tsx
|
||||||
msgid "Total"
|
msgid "Total"
|
||||||
msgstr "Total"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/network-sheet.tsx
|
#: src/components/routes/system/network-sheet.tsx
|
||||||
msgid "Total data received for each interface"
|
msgid "Total data received for each interface"
|
||||||
@@ -1839,7 +1839,7 @@ msgstr "Tempo total gasto em leitura/escrita (pode exceder 100%)"
|
|||||||
#. placeholder {0}: data.length
|
#. placeholder {0}: data.length
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Total: {0}"
|
msgid "Total: {0}"
|
||||||
msgstr "Total: {0}"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "Triggered by"
|
msgid "Triggered by"
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "Sim"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "As configurações do seu usuário foram atualizadas."
|
msgstr "As configurações do seu usuário foram atualizadas."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: ru\n"
|
"Language: ru\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:45\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Russian\n"
|
"Language-Team: Russian\n"
|
||||||
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
|
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
|
||||||
@@ -928,7 +928,7 @@ msgstr "Глобально"
|
|||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
msgstr "GPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
msgid "GPU Engines"
|
msgid "GPU Engines"
|
||||||
@@ -954,7 +954,7 @@ msgstr "Здоровье"
|
|||||||
|
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Heartbeat"
|
msgid "Heartbeat"
|
||||||
msgstr "Heartbeat"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Heartbeat Monitoring"
|
msgid "Heartbeat Monitoring"
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "Да"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "Ваши настройки пользователя были обновлены."
|
msgstr "Ваши настройки пользователя были обновлены."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: sl\n"
|
"Language: sl\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:45\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Slovenian\n"
|
"Language-Team: Slovenian\n"
|
||||||
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n"
|
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n"
|
||||||
@@ -61,7 +61,7 @@ msgstr "1 ura"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 min"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 minute"
|
msgid "1 minute"
|
||||||
@@ -78,7 +78,7 @@ msgstr "12 ur"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "15 min"
|
msgid "15 min"
|
||||||
msgstr "15 min"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "24 hours"
|
msgid "24 hours"
|
||||||
@@ -91,7 +91,7 @@ msgstr "30 dni"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "5 min"
|
msgid "5 min"
|
||||||
msgstr "5 min"
|
msgstr ""
|
||||||
|
|
||||||
#. Table column
|
#. Table column
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
@@ -1740,7 +1740,7 @@ msgstr "Naloge"
|
|||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Temp"
|
msgid "Temp"
|
||||||
msgstr "Temp"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "Da"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "Vaše uporabniške nastavitve so posodobljene."
|
msgstr "Vaše uporabniške nastavitve so posodobljene."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: sr\n"
|
"Language: sr\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:45\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Serbian (Cyrillic)\n"
|
"Language-Team: Serbian (Cyrillic)\n"
|
||||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||||
@@ -1516,7 +1516,7 @@ msgstr "Настави"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgctxt "Root disk label"
|
msgctxt "Root disk label"
|
||||||
msgid "Root"
|
msgid "Root"
|
||||||
msgstr "Root"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Rotate token"
|
msgid "Rotate token"
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "Да"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "Ваша корисничка подешавања су ажурирана."
|
msgstr "Ваша корисничка подешавања су ажурирана."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: sv\n"
|
"Language: sv\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:45\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Swedish\n"
|
"Language-Team: Swedish\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@@ -61,7 +61,7 @@ msgstr "1 timme"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 min"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 minute"
|
msgid "1 minute"
|
||||||
@@ -78,7 +78,7 @@ msgstr "12 timmar"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "15 min"
|
msgid "15 min"
|
||||||
msgstr "15 min"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "24 hours"
|
msgid "24 hours"
|
||||||
@@ -91,7 +91,7 @@ msgstr "30 dagar"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/routes/system/charts/load-average-chart.tsx
|
#: src/components/routes/system/charts/load-average-chart.tsx
|
||||||
msgid "5 min"
|
msgid "5 min"
|
||||||
msgstr "5 min"
|
msgstr ""
|
||||||
|
|
||||||
#. Table column
|
#. Table column
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
@@ -142,7 +142,7 @@ msgstr "Justera bredden på huvudlayouten"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr "Admin"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "After"
|
msgid "After"
|
||||||
@@ -154,7 +154,7 @@ msgstr "Efter att du har ställt in miljövariablerna, starta om din Beszel-hubb
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Agent"
|
msgid "Agent"
|
||||||
msgstr "Agent"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
@@ -348,7 +348,7 @@ msgstr "Varning - potentiell dataförlust"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr "Celsius (°C)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -447,7 +447,7 @@ msgstr "Ej ansluten"
|
|||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Container"
|
msgid "Container"
|
||||||
msgstr "Container"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Container Health"
|
msgid "Container Health"
|
||||||
@@ -530,7 +530,7 @@ msgstr "Kärna"
|
|||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "CPU"
|
msgid "CPU"
|
||||||
msgstr "CPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -661,7 +661,7 @@ msgstr "Urladdar"
|
|||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Disk"
|
msgid "Disk"
|
||||||
msgstr "Disk"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Disk unit"
|
msgid "Disk unit"
|
||||||
@@ -826,7 +826,7 @@ msgstr "Exportera din nuvarande systemkonfiguration."
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "Fahrenheit (°F)"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
@@ -914,7 +914,7 @@ msgstr "FreeBSD kommando"
|
|||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
#: src/lib/i18n.ts
|
#: src/lib/i18n.ts
|
||||||
msgid "Full"
|
msgid "Full"
|
||||||
msgstr "Full"
|
msgstr ""
|
||||||
|
|
||||||
#. Context: General settings
|
#. Context: General settings
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
@@ -924,7 +924,7 @@ msgstr "Allmänt"
|
|||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Global"
|
msgid "Global"
|
||||||
msgstr "Global"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
@@ -1029,7 +1029,7 @@ msgstr "Språk"
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Layout"
|
msgid "Layout"
|
||||||
msgstr "Layout"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Layout width"
|
msgid "Layout width"
|
||||||
@@ -1113,7 +1113,7 @@ msgstr "Manuella installationsinstruktioner"
|
|||||||
#. Chart select field. Please try to keep this short.
|
#. Chart select field. Please try to keep this short.
|
||||||
#: src/components/routes/system/chart-card.tsx
|
#: src/components/routes/system/chart-card.tsx
|
||||||
msgid "Max 1 min"
|
msgid "Max 1 min"
|
||||||
msgstr "Max 1 min"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
@@ -1393,7 +1393,7 @@ msgstr "Poolanvändning"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr "Port"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Container ports"
|
msgctxt "Container ports"
|
||||||
@@ -1669,7 +1669,7 @@ msgstr "Tillstånd"
|
|||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Status"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
msgid "Sub State"
|
msgid "Sub State"
|
||||||
@@ -1701,7 +1701,7 @@ msgstr "Byt tema"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "System"
|
msgid "System"
|
||||||
msgstr "System"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
msgid "System fan speeds (RPM)"
|
msgid "System fan speeds (RPM)"
|
||||||
@@ -1740,7 +1740,7 @@ msgstr "Uppgifter"
|
|||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Temp"
|
msgid "Temp"
|
||||||
msgstr "Temp"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
@@ -1821,7 +1821,7 @@ msgstr "Tokens och fingeravtryck används för att autentisera WebSocket-anslutn
|
|||||||
#: src/components/ui/chart.tsx
|
#: src/components/ui/chart.tsx
|
||||||
#: src/components/ui/chart.tsx
|
#: src/components/ui/chart.tsx
|
||||||
msgid "Total"
|
msgid "Total"
|
||||||
msgstr "Total"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/network-sheet.tsx
|
#: src/components/routes/system/network-sheet.tsx
|
||||||
msgid "Total data received for each interface"
|
msgid "Total data received for each interface"
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "Ja"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "Dina användarinställningar har uppdaterats."
|
msgstr "Dina användarinställningar har uppdaterats."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: tr\n"
|
"Language: tr\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:45\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Turkish\n"
|
"Language-Team: Turkish\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@@ -1144,7 +1144,7 @@ msgstr "Konteynerlerin bellek kullanımı"
|
|||||||
#. Device model
|
#. Device model
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Model"
|
msgid "Model"
|
||||||
msgstr "Model"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/storage-pools-table.tsx
|
#: src/components/routes/system/storage-pools-table.tsx
|
||||||
msgid "Mountpoint"
|
msgid "Mountpoint"
|
||||||
@@ -1393,7 +1393,7 @@ msgstr "Pool kullanımı"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr "Port"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Container ports"
|
msgctxt "Container ports"
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "Evet"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "Kullanıcı ayarlarınız güncellendi."
|
msgstr "Kullanıcı ayarlarınız güncellendi."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: uk\n"
|
"Language: uk\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:45\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Ukrainian\n"
|
"Language-Team: Ukrainian\n"
|
||||||
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
|
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
|
||||||
@@ -954,7 +954,7 @@ msgstr "Здоров’я"
|
|||||||
|
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Heartbeat"
|
msgid "Heartbeat"
|
||||||
msgstr "Heartbeat"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Heartbeat Monitoring"
|
msgid "Heartbeat Monitoring"
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "Так"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "Ваші налаштування користувача були оновлені."
|
msgstr "Ваші налаштування користувача були оновлені."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: uz\n"
|
"Language: uz\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:46\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Uzbek\n"
|
"Language-Team: Uzbek\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
@@ -48,7 +48,7 @@ msgstr "{count, plural, other {{countString} daqiqa}}"
|
|||||||
|
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
msgid "{diskName} I/O"
|
msgid "{diskName} I/O"
|
||||||
msgstr "{diskName} I/O"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
msgid "{threads, plural, one {# thread} other {# threads}}"
|
msgid "{threads, plural, one {# thread} other {# threads}}"
|
||||||
@@ -142,7 +142,7 @@ msgstr "Asosiy tartib kengligini sozlang"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr "Admin"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systemd-table/systemd-table.tsx
|
#: src/components/systemd-table/systemd-table.tsx
|
||||||
msgid "After"
|
msgid "After"
|
||||||
@@ -154,7 +154,7 @@ msgstr "Muhit o'zgaruvchilarini sozlagandan so'ng, o'zgarishlar kuchga kirishi u
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Agent"
|
msgid "Agent"
|
||||||
msgstr "Agent"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
@@ -258,7 +258,7 @@ msgstr "O'tkazish qobiliyati"
|
|||||||
#. Battery label in systems table header
|
#. Battery label in systems table header
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Bat"
|
msgid "Bat"
|
||||||
msgstr "Bat"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
#: src/components/routes/system/charts/sensor-charts.tsx
|
#: src/components/routes/system/charts/sensor-charts.tsx
|
||||||
@@ -530,7 +530,7 @@ msgstr "Asosiy"
|
|||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "CPU"
|
msgid "CPU"
|
||||||
msgstr "CPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -661,7 +661,7 @@ msgstr "Razryadlanmoqda"
|
|||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Disk"
|
msgid "Disk"
|
||||||
msgstr "Disk"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Disk unit"
|
msgid "Disk unit"
|
||||||
@@ -750,7 +750,7 @@ msgstr "Tugash vaqti"
|
|||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Endpoint URL"
|
msgid "Endpoint URL"
|
||||||
msgstr "Endpoint URL"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Endpoint URL to ping (required)"
|
msgid "Endpoint URL to ping (required)"
|
||||||
@@ -924,11 +924,11 @@ msgstr "Umumiy"
|
|||||||
|
|
||||||
#: src/components/routes/settings/quiet-hours.tsx
|
#: src/components/routes/settings/quiet-hours.tsx
|
||||||
msgid "Global"
|
msgid "Global"
|
||||||
msgstr "Global"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
msgstr "GPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
msgid "GPU Engines"
|
msgid "GPU Engines"
|
||||||
@@ -945,7 +945,7 @@ msgstr "GPU yuklanishi"
|
|||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Grid"
|
msgid "Grid"
|
||||||
msgstr "Grid"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
#: src/components/routes/system/storage-pools-table.tsx
|
#: src/components/routes/system/storage-pools-table.tsx
|
||||||
@@ -954,7 +954,7 @@ msgstr "Sog'lik"
|
|||||||
|
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Heartbeat"
|
msgid "Heartbeat"
|
||||||
msgstr "Heartbeat"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Heartbeat Monitoring"
|
msgid "Heartbeat Monitoring"
|
||||||
@@ -1017,7 +1017,7 @@ msgstr "Nofaol"
|
|||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Interval"
|
msgid "Interval"
|
||||||
msgstr "Interval"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/login/auth-form.tsx
|
#: src/components/login/auth-form.tsx
|
||||||
msgid "Invalid email address."
|
msgid "Invalid email address."
|
||||||
@@ -1144,7 +1144,7 @@ msgstr "Konteynerlarning xotira ishlatilishi"
|
|||||||
#. Device model
|
#. Device model
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Model"
|
msgid "Model"
|
||||||
msgstr "Model"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/storage-pools-table.tsx
|
#: src/components/routes/system/storage-pools-table.tsx
|
||||||
msgid "Mountpoint"
|
msgid "Mountpoint"
|
||||||
@@ -1393,7 +1393,7 @@ msgstr "Pool ishlatilishi"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr "Port"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Container ports"
|
msgctxt "Container ports"
|
||||||
@@ -1757,11 +1757,11 @@ msgstr "Tizim sensorlarining haroratlari"
|
|||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Test <0>URL</0>"
|
msgid "Test <0>URL</0>"
|
||||||
msgstr "Test <0>URL</0>"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Test heartbeat"
|
msgid "Test heartbeat"
|
||||||
msgstr "Test heartbeat"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Test notification sent"
|
msgid "Test notification sent"
|
||||||
@@ -1802,7 +1802,7 @@ msgstr "Kimga (elektron pochta)"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr "Token"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -1932,7 +1932,7 @@ msgstr "Birlik parametrlari"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Universal token"
|
msgid "Universal token"
|
||||||
msgstr "Universal token"
|
msgstr ""
|
||||||
|
|
||||||
#. Context: Battery state
|
#. Context: Battery state
|
||||||
#: src/components/routes/system/storage-pools-table.tsx
|
#: src/components/routes/system/storage-pools-table.tsx
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "Ha"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "Foydalanuvchi sozlamalaringiz yangilandi."
|
msgstr "Foydalanuvchi sozlamalaringiz yangilandi."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: vi\n"
|
"Language: vi\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-16 10:11\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Vietnamese\n"
|
"Language-Team: Vietnamese\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
@@ -447,7 +447,7 @@ msgstr "Mất kết nối"
|
|||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Container"
|
msgid "Container"
|
||||||
msgstr "Container"
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Container Health"
|
msgid "Container Health"
|
||||||
@@ -530,7 +530,7 @@ msgstr "Lõi"
|
|||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "CPU"
|
msgid "CPU"
|
||||||
msgstr "CPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -732,7 +732,7 @@ msgstr "Chỉnh sửa {foo}"
|
|||||||
#: src/components/login/forgot-pass-form.tsx
|
#: src/components/login/forgot-pass-form.tsx
|
||||||
#: src/components/login/otp-forms.tsx
|
#: src/components/login/otp-forms.tsx
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Email"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Email notifications"
|
msgid "Email notifications"
|
||||||
@@ -928,7 +928,7 @@ msgstr "Toàn cầu"
|
|||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
msgstr "GPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
msgid "GPU Engines"
|
msgid "GPU Engines"
|
||||||
@@ -954,7 +954,7 @@ msgstr "Sức khỏe"
|
|||||||
|
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Heartbeat"
|
msgid "Heartbeat"
|
||||||
msgstr "Tín hiệu"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Heartbeat Monitoring"
|
msgid "Heartbeat Monitoring"
|
||||||
@@ -1802,7 +1802,7 @@ msgstr "Đến email(s)"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr "Token"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "Có"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "Cài đặt người dùng của bạn đã được cập nhật."
|
msgstr "Cài đặt người dùng của bạn đã được cập nhật."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: zh\n"
|
"Language: zh\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:45\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Chinese Simplified\n"
|
"Language-Team: Chinese Simplified\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
@@ -48,7 +48,7 @@ msgstr "{count, plural, one {{countString} 分钟} few {{countString} 分钟} ma
|
|||||||
|
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
msgid "{diskName} I/O"
|
msgid "{diskName} I/O"
|
||||||
msgstr "{diskName} I/O"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
msgid "{threads, plural, one {# thread} other {# threads}}"
|
msgid "{threads, plural, one {# thread} other {# threads}}"
|
||||||
@@ -530,7 +530,7 @@ msgstr "核心"
|
|||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "CPU"
|
msgid "CPU"
|
||||||
msgstr "CPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "是"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "您的用户设置已更新。"
|
msgstr "您的用户设置已更新。"
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: zh\n"
|
"Language: zh\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:46\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Chinese Traditional, Hong Kong\n"
|
"Language-Team: Chinese Traditional, Hong Kong\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "是"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "您的用戶設置已更新。"
|
msgstr "您的用戶設置已更新。"
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: zh\n"
|
"Language: zh\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2026-09-10 01:45\n"
|
"PO-Revision-Date: 2026-09-10 00:33\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Chinese Traditional\n"
|
"Language-Team: Chinese Traditional\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
@@ -48,7 +48,7 @@ msgstr "{count, plural, one {{countString} 分鐘} few {{countString} 分鐘} ma
|
|||||||
|
|
||||||
#: src/components/routes/system/charts/disk-charts.tsx
|
#: src/components/routes/system/charts/disk-charts.tsx
|
||||||
msgid "{diskName} I/O"
|
msgid "{diskName} I/O"
|
||||||
msgstr "{diskName} I/O"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/info-bar.tsx
|
#: src/components/routes/system/info-bar.tsx
|
||||||
msgid "{threads, plural, one {# thread} other {# threads}}"
|
msgid "{threads, plural, one {# thread} other {# threads}}"
|
||||||
@@ -154,7 +154,7 @@ msgstr "設定環境變數後,請重新啟動 Beszel Hub 以使變更生效。
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Agent"
|
msgid "Agent"
|
||||||
msgstr "Agent"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
@@ -530,7 +530,7 @@ msgstr "核心指標"
|
|||||||
#: src/components/systemd-table/systemd-table-columns.tsx
|
#: src/components/systemd-table/systemd-table-columns.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "CPU"
|
msgid "CPU"
|
||||||
msgstr "CPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/cpu-sheet.tsx
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
msgid "CPU Cores"
|
msgid "CPU Cores"
|
||||||
@@ -750,7 +750,7 @@ msgstr "結束時間"
|
|||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Endpoint URL"
|
msgid "Endpoint URL"
|
||||||
msgstr "Endpoint URL"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Endpoint URL to ping (required)"
|
msgid "Endpoint URL to ping (required)"
|
||||||
@@ -884,7 +884,7 @@ msgstr "篩選..."
|
|||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Fingerprint"
|
msgid "Fingerprint"
|
||||||
msgstr "Fingerprint"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Firmware"
|
msgid "Firmware"
|
||||||
@@ -928,7 +928,7 @@ msgstr "全域"
|
|||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "GPU"
|
msgid "GPU"
|
||||||
msgstr "GPU"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/charts/gpu-charts.tsx
|
#: src/components/routes/system/charts/gpu-charts.tsx
|
||||||
msgid "GPU Engines"
|
msgid "GPU Engines"
|
||||||
@@ -954,7 +954,7 @@ msgstr "健康狀態"
|
|||||||
|
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Heartbeat"
|
msgid "Heartbeat"
|
||||||
msgstr "Heartbeat"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "Heartbeat Monitoring"
|
msgid "Heartbeat Monitoring"
|
||||||
@@ -972,7 +972,7 @@ msgstr "Homebrew 指令"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Host / IP"
|
msgid "Host / IP"
|
||||||
msgstr "Host / IP"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/heartbeat.tsx
|
#: src/components/routes/settings/heartbeat.tsx
|
||||||
msgid "HTTP Method"
|
msgid "HTTP Method"
|
||||||
@@ -1393,7 +1393,7 @@ msgstr "儲存池使用率"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr "Port"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
#: src/components/containers-table/containers-table-columns.tsx
|
||||||
msgctxt "Container ports"
|
msgctxt "Container ports"
|
||||||
@@ -1516,7 +1516,7 @@ msgstr "繼續"
|
|||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgctxt "Root disk label"
|
msgctxt "Root disk label"
|
||||||
msgid "Root"
|
msgid "Root"
|
||||||
msgstr "Root"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Rotate token"
|
msgid "Rotate token"
|
||||||
@@ -1802,7 +1802,7 @@ msgstr "發送到電子郵件"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr "Token"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -2103,4 +2103,3 @@ msgstr "是"
|
|||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
msgid "Your user settings have been updated."
|
msgid "Your user settings have been updated."
|
||||||
msgstr "已更新您的使用者設定"
|
msgstr "已更新您的使用者設定"
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ const Layout = () => {
|
|||||||
|
|
||||||
const I18nApp = () => {
|
const I18nApp = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// Activate a locale so I18nProvider can mount App and load the account settings.
|
||||||
dynamicActivate(getLocale())
|
dynamicActivate(getLocale())
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|||||||
Vendored
+8
@@ -335,6 +335,7 @@ export interface ContainerRecord extends RecordModel {
|
|||||||
system: string
|
system: string
|
||||||
name: string
|
name: string
|
||||||
image: string
|
image: string
|
||||||
|
updatable?: boolean
|
||||||
ports: string
|
ports: string
|
||||||
cpu: number
|
cpu: number
|
||||||
memory: number
|
memory: number
|
||||||
@@ -369,6 +370,13 @@ export interface UserSettings {
|
|||||||
colorCrit?: number
|
colorCrit?: number
|
||||||
hourFormat?: HourFormat
|
hourFormat?: HourFormat
|
||||||
layoutWidth?: number
|
layoutWidth?: number
|
||||||
|
lang?: string
|
||||||
|
cols?: Record<string, boolean>
|
||||||
|
statusFilter?: "all" | "up" | "down" | "paused" | "pending"
|
||||||
|
viewMode?: "table" | "grid"
|
||||||
|
sortMode?: Array<{ id: string; desc: boolean }>
|
||||||
|
grid?: boolean
|
||||||
|
displayMode?: "default" | "tabs"
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChartDataContainer = {
|
type ChartDataContainer = {
|
||||||
|
|||||||
+51
-29
@@ -2,6 +2,7 @@
|
|||||||
package users
|
package users
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@@ -15,6 +16,8 @@ type UserManager struct {
|
|||||||
app core.App
|
app core.App
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errBootstrapUnavailable = errors.New("bootstrap unavailable")
|
||||||
|
|
||||||
func NewUserManager(app core.App) *UserManager {
|
func NewUserManager(app core.App) *UserManager {
|
||||||
return &UserManager{
|
return &UserManager{
|
||||||
app: app,
|
app: app,
|
||||||
@@ -59,17 +62,7 @@ func (um *UserManager) InitializeUserSettings(e *core.RecordEvent) error {
|
|||||||
// Custom API endpoint to create the first user.
|
// Custom API endpoint to create the first user.
|
||||||
// Mimics previous default behavior in PocketBase < 0.23.0 allowing user to be created through the Beszel UI.
|
// Mimics previous default behavior in PocketBase < 0.23.0 allowing user to be created through the Beszel UI.
|
||||||
func (um *UserManager) CreateFirstUser(e *core.RequestEvent) error {
|
func (um *UserManager) CreateFirstUser(e *core.RequestEvent) error {
|
||||||
// check that there are no users
|
// Consume the complete body before evaluating the one-time bootstrap state.
|
||||||
totalUsers, err := um.app.CountRecords("users")
|
|
||||||
if err != nil || totalUsers > 0 {
|
|
||||||
return e.JSON(http.StatusForbidden, map[string]string{"err": "Forbidden"})
|
|
||||||
}
|
|
||||||
// check that there is only one superuser and the email matches the email of the superuser we set up in initial-settings.go
|
|
||||||
adminUsers, err := um.app.FindAllRecords(core.CollectionNameSuperusers)
|
|
||||||
if err != nil || len(adminUsers) != 1 || adminUsers[0].GetString("email") != migrations.TempAdminEmail {
|
|
||||||
return e.JSON(http.StatusForbidden, map[string]string{"err": "Forbidden"})
|
|
||||||
}
|
|
||||||
// create first user using supplied email and password in request body
|
|
||||||
data := struct {
|
data := struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
@@ -81,26 +74,55 @@ func (um *UserManager) CreateFirstUser(e *core.RequestEvent) error {
|
|||||||
return e.JSON(http.StatusBadRequest, map[string]string{"err": "Bad request"})
|
return e.JSON(http.StatusBadRequest, map[string]string{"err": "Bad request"})
|
||||||
}
|
}
|
||||||
|
|
||||||
collection, _ := um.app.FindCollectionByNameOrId("users")
|
err := um.app.RunInTransaction(func(txApp core.App) error {
|
||||||
user := core.NewRecord(collection)
|
totalUsers, err := txApp.CountRecords("users")
|
||||||
user.SetEmail(data.Email)
|
if err != nil {
|
||||||
user.SetPassword(data.Password)
|
return err
|
||||||
user.Set("role", "admin")
|
}
|
||||||
user.Set("verified", true)
|
if totalUsers > 0 {
|
||||||
if err := um.app.Save(user); err != nil {
|
return errBootstrapUnavailable
|
||||||
return e.JSON(http.StatusInternalServerError, map[string]string{"err": err.Error()})
|
}
|
||||||
|
|
||||||
|
adminUsers, err := txApp.FindAllRecords(core.CollectionNameSuperusers)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(adminUsers) != 1 || adminUsers[0].GetString("email") != migrations.TempAdminEmail {
|
||||||
|
return errBootstrapUnavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
collection, err := txApp.FindCollectionByNameOrId("users")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
user := core.NewRecord(collection)
|
||||||
|
user.SetEmail(data.Email)
|
||||||
|
user.SetPassword(data.Password)
|
||||||
|
user.Set("role", "admin")
|
||||||
|
user.Set("verified", true)
|
||||||
|
if err := txApp.Save(user); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
collection, err = txApp.FindCollectionByNameOrId(core.CollectionNameSuperusers)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
adminUser := core.NewRecord(collection)
|
||||||
|
adminUser.SetEmail(data.Email)
|
||||||
|
adminUser.SetPassword(data.Password)
|
||||||
|
if err := txApp.Save(adminUser); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return txApp.Delete(adminUsers[0])
|
||||||
|
})
|
||||||
|
if errors.Is(err, errBootstrapUnavailable) {
|
||||||
|
return e.JSON(http.StatusForbidden, map[string]string{"err": "Forbidden"})
|
||||||
}
|
}
|
||||||
// create superuser using the email of the first user
|
if err != nil {
|
||||||
collection, _ = um.app.FindCollectionByNameOrId(core.CollectionNameSuperusers)
|
|
||||||
adminUser := core.NewRecord(collection)
|
|
||||||
adminUser.SetEmail(data.Email)
|
|
||||||
adminUser.SetPassword(data.Password)
|
|
||||||
if err := um.app.Save(adminUser); err != nil {
|
|
||||||
return e.JSON(http.StatusInternalServerError, map[string]string{"err": err.Error()})
|
|
||||||
}
|
|
||||||
// delete the intial superuser
|
|
||||||
if err := um.app.Delete(adminUsers[0]); err != nil {
|
|
||||||
return e.JSON(http.StatusInternalServerError, map[string]string{"err": err.Error()})
|
return e.JSON(http.StatusInternalServerError, map[string]string{"err": err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.JSON(http.StatusOK, map[string]string{"msg": "User created"})
|
return e.JSON(http.StatusOK, map[string]string{"msg": "User created"})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
//go:build testing
|
||||||
|
|
||||||
|
package users_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/henrygd/beszel/internal/migrations"
|
||||||
|
beszelTests "github.com/henrygd/beszel/internal/tests"
|
||||||
|
"github.com/henrygd/beszel/internal/users"
|
||||||
|
"github.com/pocketbase/pocketbase/core"
|
||||||
|
"github.com/pocketbase/pocketbase/tools/router"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type blockedBody struct {
|
||||||
|
io.Reader
|
||||||
|
entered chan struct{}
|
||||||
|
resume chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *blockedBody) Read(p []byte) (int, error) {
|
||||||
|
if b.entered != nil {
|
||||||
|
close(b.entered)
|
||||||
|
b.entered = nil
|
||||||
|
<-b.resume
|
||||||
|
}
|
||||||
|
return b.Reader.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateFirstUserAtomic(t *testing.T) {
|
||||||
|
for _, scenario := range []string{"parked body", "concurrent requests", "rollback"} {
|
||||||
|
t.Run(scenario, func(t *testing.T) {
|
||||||
|
h, err := beszelTests.NewTestHub(t.TempDir())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer h.Cleanup()
|
||||||
|
h.StartHub()
|
||||||
|
um := users.NewUserManager(h.App)
|
||||||
|
invoke := func(body io.Reader) int {
|
||||||
|
req := httptest.NewRequest("POST", "/api/beszel/create-user", body)
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
if err := um.CreateFirstUser(&core.RequestEvent{App: h.App, Event: router.Event{Request: req, Response: res}}); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
return res.Code
|
||||||
|
}
|
||||||
|
body := func(email string) io.Reader {
|
||||||
|
return strings.NewReader(`{"email":"` + email + `","password":"password12345"}`)
|
||||||
|
}
|
||||||
|
await := func(results <-chan int) int {
|
||||||
|
select {
|
||||||
|
case status := <-results:
|
||||||
|
return status
|
||||||
|
case <-time.After(10 * time.Second):
|
||||||
|
t.Fatal("request did not finish")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch scenario {
|
||||||
|
case "parked body":
|
||||||
|
entered, resume := make(chan struct{}), make(chan struct{})
|
||||||
|
defer func() {
|
||||||
|
select {
|
||||||
|
case <-resume:
|
||||||
|
default:
|
||||||
|
close(resume)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
result := make(chan int, 1)
|
||||||
|
go func() { result <- invoke(&blockedBody{body("attacker@example.com"), entered, resume}) }()
|
||||||
|
select {
|
||||||
|
case <-entered:
|
||||||
|
case <-time.After(10 * time.Second):
|
||||||
|
t.Fatal("request did not reach body parsing")
|
||||||
|
}
|
||||||
|
require.Equal(t, 200, invoke(body("operator@example.com")))
|
||||||
|
close(resume)
|
||||||
|
require.Equal(t, 403, await(result))
|
||||||
|
case "concurrent requests":
|
||||||
|
start := make(chan struct{})
|
||||||
|
results := make(chan int, 2)
|
||||||
|
for _, email := range []string{"one@example.com", "two@example.com"} {
|
||||||
|
go func() { <-start; results <- invoke(body(email)) }()
|
||||||
|
}
|
||||||
|
close(start)
|
||||||
|
require.ElementsMatch(t, []int{200, 403}, []int{await(results), await(results)})
|
||||||
|
case "rollback":
|
||||||
|
hook := h.OnRecordCreate(core.CollectionNameSuperusers).BindFunc(func(e *core.RecordEvent) error {
|
||||||
|
return errors.New("injected superuser creation failure")
|
||||||
|
})
|
||||||
|
require.Equal(t, 500, invoke(body("operator@example.com")))
|
||||||
|
count, err := h.CountRecords("users")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Zero(t, count)
|
||||||
|
admins, err := h.FindAllRecords(core.CollectionNameSuperusers)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, admins, 1)
|
||||||
|
require.Equal(t, migrations.TempAdminEmail, admins[0].Email())
|
||||||
|
h.OnRecordCreate(core.CollectionNameSuperusers).Unbind(hook)
|
||||||
|
require.Equal(t, 200, invoke(body("operator@example.com")))
|
||||||
|
}
|
||||||
|
count, err := h.CountRecords("users")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, 1, count)
|
||||||
|
admins, err := h.FindAllRecords(core.CollectionNameSuperusers)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, admins, 1)
|
||||||
|
require.NotEqual(t, migrations.TempAdminEmail, admins[0].Email())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user