Compare commits

...

7 Commits

Author SHA1 Message Date
henrygd
806c4e51c5 v0.12.0-beta2 release 2025-07-12 21:19:57 -04:00
henrygd
6520783fe9 small ui tweaks 2025-07-12 21:16:40 -04:00
henrygd
48c8a3a4a5 update package-lock.json 2025-07-12 21:06:01 -04:00
henrygd
e0c839f78c add skip_upload: auto to goreleaser homebrew config 2025-07-12 19:56:48 -04:00
NeMeow
1ba362bafe Add 5m and 10m load avg alerts and table values (#816)
Co-authored-by: henrygd <hank@henrygd.me>
2025-07-12 19:47:33 -04:00
henrygd
b5d55ead4a send websocket close message to agent 2025-07-12 18:49:40 -04:00
henrygd
4f879ccc66 Fix universal tokens registering multiple systems 2025-07-12 17:11:53 -04:00
53 changed files with 3092 additions and 2876 deletions

View File

@@ -119,8 +119,8 @@ scoops:
repository: repository:
owner: henrygd owner: henrygd
name: beszel-scoops name: beszel-scoops
homepage: 'https://beszel.dev' homepage: "https://beszel.dev"
description: 'Agent for Beszel, a lightweight server monitoring platform.' description: "Agent for Beszel, a lightweight server monitoring platform."
license: MIT license: MIT
# # Needs choco installed, so doesn't build on linux / default gh workflow :( # # Needs choco installed, so doesn't build on linux / default gh workflow :(
@@ -152,9 +152,10 @@ brews:
repository: repository:
owner: henrygd owner: henrygd
name: homebrew-beszel name: homebrew-beszel
homepage: 'https://beszel.dev' homepage: "https://beszel.dev"
description: 'Agent for Beszel, a lightweight server monitoring platform.' description: "Agent for Beszel, a lightweight server monitoring platform."
license: MIT license: MIT
skip_upload: auto
extra_install: | extra_install: |
(bin/"beszel-agent-launcher").write <<~EOS (bin/"beszel-agent-launcher").write <<~EOS
#!/bin/bash #!/bin/bash
@@ -181,12 +182,12 @@ winget:
package_identifier: henrygd.beszel-agent package_identifier: henrygd.beszel-agent
publisher: henrygd publisher: henrygd
license: MIT license: MIT
license_url: 'https://github.com/henrygd/beszel/blob/main/LICENSE' license_url: "https://github.com/henrygd/beszel/blob/main/LICENSE"
copyright: '2025 henrygd' copyright: "2025 henrygd"
homepage: 'https://beszel.dev' homepage: "https://beszel.dev"
release_notes_url: 'https://github.com/henrygd/beszel/releases/tag/v{{ .Version }}' release_notes_url: "https://github.com/henrygd/beszel/releases/tag/v{{ .Version }}"
publisher_support_url: 'https://github.com/henrygd/beszel/issues' publisher_support_url: "https://github.com/henrygd/beszel/issues"
short_description: 'Agent for Beszel, a lightweight server monitoring platform.' short_description: "Agent for Beszel, a lightweight server monitoring platform."
skip_upload: auto skip_upload: auto
description: | description: |
Beszel is a lightweight server monitoring platform that includes Docker Beszel is a lightweight server monitoring platform that includes Docker
@@ -218,5 +219,5 @@ changelog:
sort: asc sort: asc
filters: filters:
exclude: exclude:
- '^docs:' - "^docs:"
- '^test:' - "^test:"

View File

@@ -475,7 +475,7 @@ func TestWriteToSessionEncoding(t *testing.T) {
}, },
{ {
name: "matching beta version should use CBOR", name: "matching beta version should use CBOR",
hubVersion: "0.12.0-beta1", hubVersion: "0.12.0-beta2",
expectedUsesCbor: true, expectedUsesCbor: true,
}, },
} }

View File

@@ -14,6 +14,7 @@ import (
"github.com/shirou/gopsutil/v4/cpu" "github.com/shirou/gopsutil/v4/cpu"
"github.com/shirou/gopsutil/v4/disk" "github.com/shirou/gopsutil/v4/disk"
"github.com/shirou/gopsutil/v4/host" "github.com/shirou/gopsutil/v4/host"
"github.com/shirou/gopsutil/v4/load"
"github.com/shirou/gopsutil/v4/mem" "github.com/shirou/gopsutil/v4/mem"
psutilNet "github.com/shirou/gopsutil/v4/net" psutilNet "github.com/shirou/gopsutil/v4/net"
) )
@@ -77,6 +78,16 @@ func (a *Agent) getSystemStats() system.Stats {
systemStats.Cpu = twoDecimals(cpuPct[0]) systemStats.Cpu = twoDecimals(cpuPct[0])
} }
// load average
if avgstat, err := load.Avg(); err == nil {
systemStats.LoadAvg1 = twoDecimals(avgstat.Load1)
systemStats.LoadAvg5 = twoDecimals(avgstat.Load5)
systemStats.LoadAvg15 = twoDecimals(avgstat.Load15)
slog.Debug("Load average", "5m", systemStats.LoadAvg5, "15m", systemStats.LoadAvg15)
} else {
slog.Error("Error getting load average", "err", err)
}
// memory // memory
if v, err := mem.VirtualMemory(); err == nil { if v, err := mem.VirtualMemory(); err == nil {
// swap // swap
@@ -240,6 +251,8 @@ func (a *Agent) getSystemStats() system.Stats {
// update base system info // update base system info
a.systemInfo.Cpu = systemStats.Cpu a.systemInfo.Cpu = systemStats.Cpu
a.systemInfo.LoadAvg5 = systemStats.LoadAvg5
a.systemInfo.LoadAvg15 = systemStats.LoadAvg15
a.systemInfo.MemPct = systemStats.MemPct a.systemInfo.MemPct = systemStats.MemPct
a.systemInfo.DiskPct = systemStats.DiskPct a.systemInfo.DiskPct = systemStats.DiskPct
a.systemInfo.Uptime, _ = host.Uptime() a.systemInfo.Uptime, _ = host.Uptime()

View File

@@ -47,6 +47,8 @@ type SystemAlertStats struct {
NetSent float64 `json:"ns"` NetSent float64 `json:"ns"`
NetRecv float64 `json:"nr"` NetRecv float64 `json:"nr"`
Temperatures map[string]float32 `json:"t"` Temperatures map[string]float32 `json:"t"`
LoadAvg5 float64 `json:"l5"`
LoadAvg15 float64 `json:"l15"`
} }
type SystemAlertData struct { type SystemAlertData struct {

View File

@@ -54,6 +54,12 @@ func (am *AlertManager) HandleSystemAlerts(systemRecord *core.Record, data *syst
} }
val = data.Info.DashboardTemp val = data.Info.DashboardTemp
unit = "°C" unit = "°C"
case "LoadAvg5":
val = data.Info.LoadAvg5
unit = ""
case "LoadAvg15":
val = data.Info.LoadAvg15
unit = ""
} }
triggered := alertRecord.GetBool("triggered") triggered := alertRecord.GetBool("triggered")
@@ -190,6 +196,10 @@ func (am *AlertManager) HandleSystemAlerts(systemRecord *core.Record, data *syst
} }
alert.mapSums[key] += temp alert.mapSums[key] += temp
} }
case "LoadAvg5":
alert.val += stats.LoadAvg5
case "LoadAvg15":
alert.val += stats.LoadAvg15
default: default:
continue continue
} }
@@ -247,6 +257,10 @@ func (am *AlertManager) sendSystemAlert(alert SystemAlertData) {
if alert.name == "Disk" { if alert.name == "Disk" {
alert.name += " usage" alert.name += " usage"
} }
// format LoadAvg5 and LoadAvg15
if after, ok := strings.CutPrefix(alert.name, "LoadAvg"); ok {
alert.name = after + "m Load"
}
// make title alert name lowercase if not CPU // make title alert name lowercase if not CPU
titleAlertName := alert.name titleAlertName := alert.name

View File

@@ -31,6 +31,9 @@ type Stats struct {
Temperatures map[string]float64 `json:"t,omitempty" cbor:"20,keyasint,omitempty"` Temperatures map[string]float64 `json:"t,omitempty" cbor:"20,keyasint,omitempty"`
ExtraFs map[string]*FsStats `json:"efs,omitempty" cbor:"21,keyasint,omitempty"` ExtraFs map[string]*FsStats `json:"efs,omitempty" cbor:"21,keyasint,omitempty"`
GPUData map[string]GPUData `json:"g,omitempty" cbor:"22,keyasint,omitempty"` GPUData map[string]GPUData `json:"g,omitempty" cbor:"22,keyasint,omitempty"`
LoadAvg1 float64 `json:"l1,omitempty" cbor:"23,keyasint,omitempty,omitzero"`
LoadAvg5 float64 `json:"l5,omitempty" cbor:"24,keyasint,omitempty,omitzero"`
LoadAvg15 float64 `json:"l15,omitempty" cbor:"25,keyasint,omitempty,omitzero"`
} }
type GPUData struct { type GPUData struct {
@@ -89,6 +92,8 @@ type Info struct {
GpuPct float64 `json:"g,omitempty" cbor:"12,keyasint,omitempty"` GpuPct float64 `json:"g,omitempty" cbor:"12,keyasint,omitempty"`
DashboardTemp float64 `json:"dt,omitempty" cbor:"13,keyasint,omitempty"` DashboardTemp float64 `json:"dt,omitempty" cbor:"13,keyasint,omitempty"`
Os Os `json:"os" cbor:"14,keyasint"` Os Os `json:"os" cbor:"14,keyasint"`
LoadAvg5 float64 `json:"l5,omitempty" cbor:"15,keyasint,omitempty,omitzero"`
LoadAvg15 float64 `json:"l15,omitempty" cbor:"16,keyasint,omitempty,omitzero"`
} }
// Final data structure to return to the hub // Final data structure to return to the hub

View File

@@ -5,10 +5,10 @@ import (
"beszel/internal/hub/expirymap" "beszel/internal/hub/expirymap"
"beszel/internal/hub/ws" "beszel/internal/hub/ws"
"errors" "errors"
"fmt"
"net" "net"
"net/http" "net/http"
"strings" "strings"
"sync"
"time" "time"
"github.com/blang/semver" "github.com/blang/semver"
@@ -17,118 +17,96 @@ import (
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
) )
// tokenMap maps tokens to user IDs for universal tokens // agentConnectRequest holds information related to an agent's connection attempt.
var tokenMap *expirymap.ExpiryMap[string]
type agentConnectRequest struct { type agentConnectRequest struct {
hub *Hub
req *http.Request
res http.ResponseWriter
token string token string
agentSemVer semver.Version agentSemVer semver.Version
// for universal token // isUniversalToken is true if the token is a universal token.
isUniversalToken bool isUniversalToken bool
userId string // userId is the user ID associated with the universal token.
remoteAddr string userId string
} }
// validateAgentHeaders validates the required headers from agent connection requests. // universalTokenMap stores active universal tokens and their associated user IDs.
func (h *Hub) validateAgentHeaders(headers http.Header) (string, string, error) { var universalTokenMap tokenMap
token := headers.Get("X-Token")
agentVersion := headers.Get("X-Beszel")
if agentVersion == "" || token == "" || len(token) > 512 { type tokenMap struct {
return "", "", errors.New("") store *expirymap.ExpiryMap[string]
} once sync.Once
return token, agentVersion, nil
} }
// getFingerprintRecord retrieves fingerprint data from the database by token. // getMap returns the expirymap, creating it if necessary.
func (h *Hub) getFingerprintRecord(token string, recordData *ws.FingerprintRecord) error { func (tm *tokenMap) GetMap() *expirymap.ExpiryMap[string] {
err := h.DB().NewQuery("SELECT id, system, fingerprint, token FROM fingerprints WHERE token = {:token}"). tm.once.Do(func() {
Bind(dbx.Params{ tm.store = expirymap.New[string](time.Hour)
"token": token, })
}). return tm.store
One(recordData)
return err
} }
// sendResponseError sends an HTTP error response with the given status code and message. // handleAgentConnect is the HTTP handler for an agent's connection request.
func sendResponseError(res http.ResponseWriter, code int, message string) error {
res.WriteHeader(code)
if message != "" {
res.Write([]byte(message))
}
return nil
}
// handleAgentConnect handles the incoming connection request from the agent.
func (h *Hub) handleAgentConnect(e *core.RequestEvent) error { func (h *Hub) handleAgentConnect(e *core.RequestEvent) error {
if err := h.agentConnect(e.Request, e.Response); err != nil { agentRequest := agentConnectRequest{req: e.Request, res: e.Response, hub: h}
return err _ = agentRequest.agentConnect()
}
return nil return nil
} }
// agentConnect handles agent connection requests, validating credentials and upgrading to WebSocket. // agentConnect validates agent credentials and upgrades the connection to a WebSocket.
func (h *Hub) agentConnect(req *http.Request, res http.ResponseWriter) (err error) { func (acr *agentConnectRequest) agentConnect() (err error) {
var agentConnectRequest agentConnectRequest
var agentVersion string var agentVersion string
// check if user agent and token are valid
agentConnectRequest.token, agentVersion, err = h.validateAgentHeaders(req.Header) acr.token, agentVersion, err = acr.validateAgentHeaders(acr.req.Header)
if err != nil { if err != nil {
return sendResponseError(res, http.StatusUnauthorized, "") return acr.sendResponseError(acr.res, http.StatusBadRequest, "")
} }
// Pull fingerprint from database matching token // Check if token is an active universal token
var fpRecord ws.FingerprintRecord acr.userId, acr.isUniversalToken = universalTokenMap.GetMap().GetOk(acr.token)
err = h.getFingerprintRecord(agentConnectRequest.token, &fpRecord)
// if no existing record, check if token is a universal token // Find matching fingerprint records for this token
if err != nil { fpRecords := getFingerprintRecordsByToken(acr.token, acr.hub)
if err = checkUniversalToken(&agentConnectRequest); err == nil { if len(fpRecords) == 0 && !acr.isUniversalToken {
// if this is a universal token, set the remote address and new record token // Invalid token - no records found and not a universal token
agentConnectRequest.remoteAddr = getRealIP(req) return acr.sendResponseError(acr.res, http.StatusUnauthorized, "Invalid token")
fpRecord.Token = agentConnectRequest.token
}
}
// If no matching token, return unauthorized
if err != nil {
return sendResponseError(res, http.StatusUnauthorized, "Invalid token")
} }
// Validate agent version // Validate agent version
agentConnectRequest.agentSemVer, err = semver.Parse(agentVersion) acr.agentSemVer, err = semver.Parse(agentVersion)
if err != nil { if err != nil {
return sendResponseError(res, http.StatusUnauthorized, "Invalid agent version") return acr.sendResponseError(acr.res, http.StatusUnauthorized, "Invalid agent version")
} }
// Upgrade connection to WebSocket // Upgrade connection to WebSocket
conn, err := ws.GetUpgrader().Upgrade(res, req) conn, err := ws.GetUpgrader().Upgrade(acr.res, acr.req)
if err != nil { if err != nil {
return sendResponseError(res, http.StatusInternalServerError, "WebSocket upgrade failed") return acr.sendResponseError(acr.res, http.StatusInternalServerError, "WebSocket upgrade failed")
} }
go h.verifyWsConn(conn, agentConnectRequest, fpRecord) go acr.verifyWsConn(conn, fpRecords)
return nil return nil
} }
// verifyWsConn verifies the WebSocket connection using agent's fingerprint and SSH key signature. // verifyWsConn verifies the WebSocket connection using the agent's fingerprint and
func (h *Hub) verifyWsConn(conn *gws.Conn, acr agentConnectRequest, fpRecord ws.FingerprintRecord) (err error) { // SSH key signature, then adds the system to the system manager.
func (acr *agentConnectRequest) verifyWsConn(conn *gws.Conn, fpRecords []ws.FingerprintRecord) (err error) {
wsConn := ws.NewWsConnection(conn) wsConn := ws.NewWsConnection(conn)
// must be set before the read loop
// must set wsConn in connection store before the read loop
conn.Session().Store("wsConn", wsConn) conn.Session().Store("wsConn", wsConn)
// make sure connection is closed if there is an error // make sure connection is closed if there is an error
defer func() { defer func() {
if err != nil { if err != nil {
wsConn.Close() wsConn.Close([]byte(err.Error()))
h.Logger().Error("WebSocket error", "error", err, "system", fpRecord.SystemId)
} }
}() }()
go conn.ReadLoop() go conn.ReadLoop()
signer, err := h.GetSSHKey("") signer, err := acr.hub.GetSSHKey("")
if err != nil { if err != nil {
return err return err
} }
@@ -138,40 +116,152 @@ func (h *Hub) verifyWsConn(conn *gws.Conn, acr agentConnectRequest, fpRecord ws.
return err return err
} }
// Create system if using universal token // Find or create the appropriate system for this token and fingerprint
if acr.isUniversalToken { fpRecord, err := acr.findOrCreateSystemForToken(fpRecords, agentFingerprint)
if acr.userId == "" { if err != nil {
return errors.New("token user not found") return err
}
fpRecord.SystemId, err = h.createSystemFromAgentData(&acr, agentFingerprint)
if err != nil {
return fmt.Errorf("failed to create system from universal token: %w", err)
}
} }
switch { return acr.hub.sm.AddWebSocketSystem(fpRecord.SystemId, acr.agentSemVer, wsConn)
// If no current fingerprint, update with new fingerprint (first time connecting)
case fpRecord.Fingerprint == "":
if err := h.SetFingerprint(&fpRecord, agentFingerprint.Fingerprint); err != nil {
return err
}
// Abort if fingerprint exists but doesn't match (different machine)
case fpRecord.Fingerprint != agentFingerprint.Fingerprint:
return errors.New("fingerprint mismatch")
}
return h.sm.AddWebSocketSystem(fpRecord.SystemId, acr.agentSemVer, wsConn)
} }
// createSystemFromAgentData creates a new system record using data from the agent // validateAgentHeaders extracts and validates the token and agent version from HTTP headers.
func (h *Hub) createSystemFromAgentData(acr *agentConnectRequest, agentFingerprint common.FingerprintResponse) (recordId string, err error) { func (acr *agentConnectRequest) validateAgentHeaders(headers http.Header) (string, string, error) {
systemsCollection, err := h.FindCollectionByNameOrId("systems") token := headers.Get("X-Token")
if err != nil { agentVersion := headers.Get("X-Beszel")
return "", fmt.Errorf("failed to find systems collection: %w", err)
if agentVersion == "" || token == "" || len(token) > 64 {
return "", "", errors.New("")
} }
return token, agentVersion, nil
}
// sendResponseError writes an HTTP error response.
func (acr *agentConnectRequest) sendResponseError(res http.ResponseWriter, code int, message string) error {
res.WriteHeader(code)
if message != "" {
res.Write([]byte(message))
}
return nil
}
// getFingerprintRecordsByToken retrieves all fingerprint records associated with a given token.
func getFingerprintRecordsByToken(token string, h *Hub) []ws.FingerprintRecord {
var records []ws.FingerprintRecord
// All will populate empty slice even on error
_ = h.DB().NewQuery("SELECT id, system, fingerprint, token FROM fingerprints WHERE token = {:token}").
Bind(dbx.Params{
"token": token,
}).
All(&records)
return records
}
// findOrCreateSystemForToken finds an existing system matching the token and fingerprint,
// or creates a new one for a universal token.
func (acr *agentConnectRequest) findOrCreateSystemForToken(fpRecords []ws.FingerprintRecord, agentFingerprint common.FingerprintResponse) (ws.FingerprintRecord, error) {
// No records - only valid for active universal tokens
if len(fpRecords) == 0 {
return acr.handleNoRecords(agentFingerprint)
}
// Single record - handle as regular token
if len(fpRecords) == 1 && !acr.isUniversalToken {
return acr.handleSingleRecord(fpRecords[0], agentFingerprint)
}
// Multiple records or universal token - look for matching fingerprint
return acr.handleMultipleRecordsOrUniversalToken(fpRecords, agentFingerprint)
}
// handleNoRecords handles the case where no fingerprint records are found for a token.
// A new system is created if the token is a valid universal token.
func (acr *agentConnectRequest) handleNoRecords(agentFingerprint common.FingerprintResponse) (ws.FingerprintRecord, error) {
var fpRecord ws.FingerprintRecord
if !acr.isUniversalToken || acr.userId == "" {
return fpRecord, errors.New("no matching fingerprints")
}
return acr.createNewSystemForUniversalToken(agentFingerprint)
}
// handleSingleRecord handles the case with a single fingerprint record. It validates
// the agent's fingerprint against the stored one, or sets it on first connect.
func (acr *agentConnectRequest) handleSingleRecord(fpRecord ws.FingerprintRecord, agentFingerprint common.FingerprintResponse) (ws.FingerprintRecord, error) {
// If no current fingerprint, update with new fingerprint (first time connecting)
if fpRecord.Fingerprint == "" {
if err := acr.hub.SetFingerprint(&fpRecord, agentFingerprint.Fingerprint); err != nil {
return fpRecord, err
}
// Update the record with the fingerprint that was set
fpRecord.Fingerprint = agentFingerprint.Fingerprint
return fpRecord, nil
}
// Abort if fingerprint exists but doesn't match (different machine)
if fpRecord.Fingerprint != agentFingerprint.Fingerprint {
return fpRecord, errors.New("fingerprint mismatch")
}
return fpRecord, nil
}
// handleMultipleRecordsOrUniversalToken finds a matching fingerprint from multiple records.
// If no match is found and the token is a universal token, a new system is created.
func (acr *agentConnectRequest) handleMultipleRecordsOrUniversalToken(fpRecords []ws.FingerprintRecord, agentFingerprint common.FingerprintResponse) (ws.FingerprintRecord, error) {
// Return existing record with matching fingerprint if found
for i := range fpRecords {
if fpRecords[i].Fingerprint == agentFingerprint.Fingerprint {
return fpRecords[i], nil
}
}
// No matching fingerprint record found, but it's
// an active universal token so create a new system
if acr.isUniversalToken {
return acr.createNewSystemForUniversalToken(agentFingerprint)
}
return ws.FingerprintRecord{}, errors.New("fingerprint mismatch")
}
// createNewSystemForUniversalToken creates a new system and fingerprint record for a universal token.
func (acr *agentConnectRequest) createNewSystemForUniversalToken(agentFingerprint common.FingerprintResponse) (ws.FingerprintRecord, error) {
var fpRecord ws.FingerprintRecord
if !acr.isUniversalToken || acr.userId == "" {
return fpRecord, errors.New("invalid token")
}
fpRecord.Token = acr.token
systemId, err := acr.createSystem(agentFingerprint)
if err != nil {
return fpRecord, err
}
fpRecord.SystemId = systemId
// Set the fingerprint for the new system
if err := acr.hub.SetFingerprint(&fpRecord, agentFingerprint.Fingerprint); err != nil {
return fpRecord, err
}
// Update the record with the fingerprint that was set
fpRecord.Fingerprint = agentFingerprint.Fingerprint
return fpRecord, nil
}
// createSystem creates a new system record in the database using details from the agent.
func (acr *agentConnectRequest) createSystem(agentFingerprint common.FingerprintResponse) (recordId string, err error) {
systemsCollection, err := acr.hub.FindCachedCollectionByNameOrId("systems")
if err != nil {
return "", err
}
remoteAddr := getRealIP(acr.req)
// separate port from address // separate port from address
if agentFingerprint.Hostname == "" { if agentFingerprint.Hostname == "" {
agentFingerprint.Hostname = acr.remoteAddr agentFingerprint.Hostname = remoteAddr
} }
if agentFingerprint.Port == "" { if agentFingerprint.Port == "" {
agentFingerprint.Port = "45876" agentFingerprint.Port = "45876"
@@ -179,14 +269,14 @@ func (h *Hub) createSystemFromAgentData(acr *agentConnectRequest, agentFingerpri
// create new record // create new record
systemRecord := core.NewRecord(systemsCollection) systemRecord := core.NewRecord(systemsCollection)
systemRecord.Set("name", agentFingerprint.Hostname) systemRecord.Set("name", agentFingerprint.Hostname)
systemRecord.Set("host", acr.remoteAddr) systemRecord.Set("host", remoteAddr)
systemRecord.Set("port", agentFingerprint.Port) systemRecord.Set("port", agentFingerprint.Port)
systemRecord.Set("users", []string{acr.userId}) systemRecord.Set("users", []string{acr.userId})
return systemRecord.Id, h.Save(systemRecord) return systemRecord.Id, acr.hub.Save(systemRecord)
} }
// SetFingerprint updates the fingerprint for a given record ID. // SetFingerprint creates or updates a fingerprint record in the database.
func (h *Hub) SetFingerprint(fpRecord *ws.FingerprintRecord, fingerprint string) (err error) { func (h *Hub) SetFingerprint(fpRecord *ws.FingerprintRecord, fingerprint string) (err error) {
// // can't use raw query here because it doesn't trigger SSE // // can't use raw query here because it doesn't trigger SSE
var record *core.Record var record *core.Record
@@ -207,25 +297,8 @@ func (h *Hub) SetFingerprint(fpRecord *ws.FingerprintRecord, fingerprint string)
return h.SaveNoValidate(record) return h.SaveNoValidate(record)
} }
func getTokenMap() *expirymap.ExpiryMap[string] { // getRealIP extracts the client's real IP address from request headers,
if tokenMap == nil { // checking common proxy headers before falling back to the remote address.
tokenMap = expirymap.New[string](time.Hour)
}
return tokenMap
}
func checkUniversalToken(acr *agentConnectRequest) (err error) {
if tokenMap == nil {
tokenMap = expirymap.New[string](time.Hour)
}
acr.userId, acr.isUniversalToken = tokenMap.GetOk(acr.token)
if !acr.isUniversalToken {
return errors.New("invalid token")
}
return nil
}
// getRealIP attempts to extract the real IP address from the request headers.
func getRealIP(r *http.Request) string { func getRealIP(r *http.Request) string {
if ip := r.Header.Get("CF-Connecting-IP"); ip != "" { if ip := r.Header.Get("CF-Connecting-IP"); ip != "" {
return ip return ip

File diff suppressed because it is too large Load Diff

View File

@@ -259,7 +259,7 @@ func (h *Hub) getUniversalToken(e *core.RequestEvent) error {
return apis.NewForbiddenError("Forbidden", nil) return apis.NewForbiddenError("Forbidden", nil)
} }
tokenMap := getTokenMap() tokenMap := universalTokenMap.GetMap()
userID := info.Auth.Id userID := info.Auth.Id
query := e.Request.URL.Query() query := e.Request.URL.Query()
token := query.Get("token") token := query.Get("token")

View File

@@ -254,5 +254,3 @@ func TestGetSSHKey(t *testing.T) {
} }
}) })
} }
// Helper function to create test records

View File

@@ -365,7 +365,7 @@ func (sys *System) closeSSHConnection() {
// The system will be set as down a few seconds later if the connection is not re-established. // The system will be set as down a few seconds later if the connection is not re-established.
func (sys *System) closeWebSocketConnection() { func (sys *System) closeWebSocketConnection() {
if sys.WsConn != nil { if sys.WsConn != nil {
sys.WsConn.Close() sys.WsConn.Close(nil)
} }
} }

View File

@@ -77,7 +77,7 @@ func (h *Handler) OnMessage(conn *gws.Conn, message *gws.Message) {
case wsConn.(*WsConn).responseChan <- message: case wsConn.(*WsConn).responseChan <- message:
default: default:
// close if the connection is not expecting a response // close if the connection is not expecting a response
wsConn.(*WsConn).Close() wsConn.(*WsConn).Close(nil)
} }
} }
@@ -100,9 +100,9 @@ func (h *Handler) OnClose(conn *gws.Conn, err error) {
} }
// Close terminates the WebSocket connection gracefully. // Close terminates the WebSocket connection gracefully.
func (ws *WsConn) Close() { func (ws *WsConn) Close(msg []byte) {
if ws.IsConnected() { if ws.IsConnected() {
ws.conn.WriteClose(1000, nil) ws.conn.WriteClose(1000, msg)
} }
} }
@@ -130,7 +130,7 @@ func (ws *WsConn) RequestSystemData(data *system.CombinedData) error {
}) })
select { select {
case <-time.After(10 * time.Second): case <-time.After(10 * time.Second):
ws.Close() ws.Close(nil)
return gws.ErrConnClosed return gws.ErrConnClosed
case message = <-ws.responseChan: case message = <-ws.responseChan:
} }
@@ -140,11 +140,12 @@ func (ws *WsConn) RequestSystemData(data *system.CombinedData) error {
// GetFingerprint authenticates with the agent using SSH signature and returns the agent's fingerprint. // GetFingerprint authenticates with the agent using SSH signature and returns the agent's fingerprint.
func (ws *WsConn) GetFingerprint(token string, signer ssh.Signer, needSysInfo bool) (common.FingerprintResponse, error) { func (ws *WsConn) GetFingerprint(token string, signer ssh.Signer, needSysInfo bool) (common.FingerprintResponse, error) {
var clientFingerprint common.FingerprintResponse
challenge := []byte(token) challenge := []byte(token)
signature, err := signer.Sign(nil, challenge) signature, err := signer.Sign(nil, challenge)
if err != nil { if err != nil {
return common.FingerprintResponse{}, err return clientFingerprint, err
} }
err = ws.sendMessage(common.HubRequest[any]{ err = ws.sendMessage(common.HubRequest[any]{
@@ -155,24 +156,19 @@ func (ws *WsConn) GetFingerprint(token string, signer ssh.Signer, needSysInfo bo
}, },
}) })
if err != nil { if err != nil {
return common.FingerprintResponse{}, err return clientFingerprint, err
} }
var message *gws.Message var message *gws.Message
var clientFingerprint common.FingerprintResponse
select { select {
case message = <-ws.responseChan: case message = <-ws.responseChan:
case <-time.After(10 * time.Second): case <-time.After(10 * time.Second):
return common.FingerprintResponse{}, errors.New("request expired") return clientFingerprint, errors.New("request expired")
} }
defer message.Close() defer message.Close()
err = cbor.Unmarshal(message.Data.Bytes(), &clientFingerprint) err = cbor.Unmarshal(message.Data.Bytes(), &clientFingerprint)
if err != nil { return clientFingerprint, err
return common.FingerprintResponse{}, err
}
return clientFingerprint, nil
} }
// IsConnected returns true if the WebSocket connection is active. // IsConnected returns true if the WebSocket connection is active.

View File

@@ -75,7 +75,9 @@ func init() {
"Memory", "Memory",
"Disk", "Disk",
"Temperature", "Temperature",
"Bandwidth" "Bandwidth",
"LoadAvg5",
"LoadAvg15"
] ]
}, },
{ {

View File

@@ -5,7 +5,7 @@ import (
m "github.com/pocketbase/pocketbase/migrations" m "github.com/pocketbase/pocketbase/migrations"
) )
var ( const (
TempAdminEmail = "_@b.b" TempAdminEmail = "_@b.b"
) )

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "beszel", "name": "beszel",
"private": true, "private": true,
"version": "0.12.0-beta1", "version": "0.12.0-beta2",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -39,13 +39,33 @@ export default memo(function AlertsButton({ system }: { system: SystemRecord })
/> />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-full overflow-auto max-w-[35rem]"> <DialogContent className="max-h-full sm:max-h-[95svh] overflow-auto max-w-[37rem]">
{opened && <AlertDialogContent system={system} />} {opened && <AlertDialogContent system={system} />}
</DialogContent> </DialogContent>
</Dialog> </Dialog>
), ),
[opened, hasAlert] [opened, hasAlert]
) )
// return useMemo(
// () => (
// <Sheet>
// <SheetTrigger asChild>
// <Button variant="ghost" size="icon" aria-label={t`Alerts`} data-nolink onClick={() => setOpened(true)}>
// <BellIcon
// className={cn("h-[1.2em] w-[1.2em] pointer-events-none", {
// "fill-primary": hasAlert,
// })}
// />
// </Button>
// </SheetTrigger>
// <SheetContent className="max-h-full overflow-auto w-[35em] p-4 sm:p-5">
// {opened && <AlertDialogContent system={system} />}
// </SheetContent>
// </Sheet>
// ),
// [opened, hasAlert]
// )
}) })
function AlertDialogContent({ system }: { system: SystemRecord }) { function AlertDialogContent({ system }: { system: SystemRecord }) {

View File

@@ -217,7 +217,7 @@ function AlertContent({ data }: { data: AlertData }) {
const [checked, setChecked] = useState(data.checked || false) const [checked, setChecked] = useState(data.checked || false)
const [min, setMin] = useState(data.min || 10) const [min, setMin] = useState(data.min || 10)
const [value, setValue] = useState(data.val || (singleDescription ? 0 : 80)) const [value, setValue] = useState(data.val || (singleDescription ? 0 : data.alert.start ?? 80))
const Icon = alertInfo[name].icon const Icon = alertInfo[name].icon
@@ -268,7 +268,8 @@ function AlertContent({ data }: { data: AlertData }) {
onValueChange={(val) => { onValueChange={(val) => {
setValue(val[0]) setValue(val[0])
}} }}
min={1} step={data.alert.step ?? 1}
min={data.alert.min ?? 1}
max={alertInfo[name].max ?? 99} max={alertInfo[name].max ?? 99}
/> />
</div> </div>

View File

@@ -95,8 +95,8 @@ export default function SettingsLayout() {
</CardHeader> </CardHeader>
<CardContent className="p-0"> <CardContent className="p-0">
<Separator className="hidden md:block my-5" /> <Separator className="hidden md:block my-5" />
<div className="flex flex-col gap-3.5 md:flex-row md:gap-5 lg:gap-10"> <div className="flex flex-col gap-3.5 md:flex-row md:gap-5 lg:gap-12">
<aside className="md:max-w-44 min-w-40"> <aside className="md:max-w-52 min-w-40">
<SidebarNav items={sidebarNavItems} /> <SidebarNav items={sidebarNavItems} />
</aside> </aside>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">

View File

@@ -68,7 +68,7 @@ import { useStore } from "@nanostores/react"
import { cn, copyToClipboard, decimalString, isReadOnlyUser, useLocalStorage } from "@/lib/utils" import { cn, copyToClipboard, decimalString, isReadOnlyUser, useLocalStorage } from "@/lib/utils"
import AlertsButton from "../alerts/alert-button" import AlertsButton from "../alerts/alert-button"
import { $router, Link, navigate } from "../router" import { $router, Link, navigate } from "../router"
import { EthernetIcon, GpuIcon, ThermometerIcon } from "../ui/icons" import { EthernetIcon, GpuIcon, HourglassIcon, ThermometerIcon } from "../ui/icons"
import { useLingui, Trans } from "@lingui/react/macro" import { useLingui, Trans } from "@lingui/react/macro"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card"
import { Input } from "../ui/input" import { Input } from "../ui/input"
@@ -83,8 +83,8 @@ function CellFormatter(info: CellContext<SystemRecord, unknown>) {
const val = (info.getValue() as number) || 0 const val = (info.getValue() as number) || 0
return ( return (
<div className="flex gap-2 items-center tabular-nums tracking-tight"> <div className="flex gap-2 items-center tabular-nums tracking-tight">
<span className="min-w-[3.3em]">{decimalString(val, 1)}%</span> <span className="min-w-8">{decimalString(val, 1)}%</span>
<span className="grow min-w-10 block bg-muted h-[1em] relative rounded-sm overflow-hidden"> <span className="grow min-w-8 block bg-muted h-[1em] relative rounded-sm overflow-hidden">
<span <span
className={cn( className={cn(
"absolute inset-0 w-full h-full origin-left", "absolute inset-0 w-full h-full origin-left",
@@ -144,7 +144,6 @@ export default function SystemsTable() {
} }
return [ return [
{ {
// size: 200,
size: 200, size: 200,
minSize: 0, minSize: 0,
accessorKey: "name", accessorKey: "name",
@@ -163,6 +162,7 @@ export default function SystemsTable() {
return false return false
}, },
enableHiding: false, enableHiding: false,
invertSorting: false,
Icon: ServerIcon, Icon: ServerIcon,
cell: (info) => ( cell: (info) => (
<span className="flex gap-0.5 items-center text-base md:pe-5"> <span className="flex gap-0.5 items-center text-base md:pe-5">
@@ -181,28 +181,26 @@ export default function SystemsTable() {
header: sortableHeader, header: sortableHeader,
}, },
{ {
accessorKey: "info.cpu", accessorFn: (originalRow) => originalRow.info.cpu,
id: "cpu", id: "cpu",
name: () => t`CPU`, name: () => t`CPU`,
invertSorting: true,
cell: CellFormatter, cell: CellFormatter,
Icon: CpuIcon, Icon: CpuIcon,
header: sortableHeader, header: sortableHeader,
}, },
{ {
accessorKey: "info.mp", // accessorKey: "info.mp",
accessorFn: (originalRow) => originalRow.info.mp,
id: "memory", id: "memory",
name: () => t`Memory`, name: () => t`Memory`,
invertSorting: true,
cell: CellFormatter, cell: CellFormatter,
Icon: MemoryStickIcon, Icon: MemoryStickIcon,
header: sortableHeader, header: sortableHeader,
}, },
{ {
accessorKey: "info.dp", accessorFn: (originalRow) => originalRow.info.dp,
id: "disk", id: "disk",
name: () => t`Disk`, name: () => t`Disk`,
invertSorting: true,
cell: CellFormatter, cell: CellFormatter,
Icon: HardDriveIcon, Icon: HardDriveIcon,
header: sortableHeader, header: sortableHeader,
@@ -211,8 +209,6 @@ export default function SystemsTable() {
accessorFn: (originalRow) => originalRow.info.g, accessorFn: (originalRow) => originalRow.info.g,
id: "gpu", id: "gpu",
name: () => "GPU", name: () => "GPU",
invertSorting: true,
sortUndefined: -1,
cell: CellFormatter, cell: CellFormatter,
Icon: GpuIcon, Icon: GpuIcon,
header: sortableHeader, header: sortableHeader,
@@ -221,19 +217,50 @@ export default function SystemsTable() {
accessorFn: (originalRow) => originalRow.info.b || 0, accessorFn: (originalRow) => originalRow.info.b || 0,
id: "net", id: "net",
name: () => t`Net`, name: () => t`Net`,
invertSorting: true,
size: 50, size: 50,
Icon: EthernetIcon, Icon: EthernetIcon,
header: sortableHeader, header: sortableHeader,
cell(info) { cell(info) {
const val = info.getValue() as number const val = info.getValue() as number
return <span className="tabular-nums whitespace-nowrap">{decimalString(val, val >= 100 ? 1 : 2)} MB/s</span>
},
},
{
accessorFn: (originalRow) => originalRow.info.l5,
id: "l5",
name: () => t({ message: "L5", comment: "Load average 5 minutes" }),
size: 0,
hideSort: true,
Icon: HourglassIcon,
header: sortableHeader,
cell(info) {
const val = info.getValue() as number
if (!val) {
return null
}
return ( return (
<span <span className={cn("tabular-nums whitespace-nowrap", viewMode === "table" && "ps-1")}>
className={cn("tabular-nums whitespace-nowrap", { {decimalString(val)}
"ps-1": viewMode === "table", </span>
})} )
> },
{decimalString(val, val >= 100 ? 1 : 2)} MB/s },
{
accessorFn: (originalRow) => originalRow.info.l15,
id: "l15",
name: () => t({ message: "L15", comment: "Load average 15 minutes" }),
size: 0,
hideSort: true,
Icon: HourglassIcon,
header: sortableHeader,
cell(info) {
const val = info.getValue() as number
if (!val) {
return null
}
return (
<span className={cn("tabular-nums whitespace-nowrap", viewMode === "table" && "ps-1")}>
{decimalString(val)}
</span> </span>
) )
}, },
@@ -242,8 +269,6 @@ export default function SystemsTable() {
accessorFn: (originalRow) => originalRow.info.dt, accessorFn: (originalRow) => originalRow.info.dt,
id: "temp", id: "temp",
name: () => t({ message: "Temp", comment: "Temperature label in systems table" }), name: () => t({ message: "Temp", comment: "Temperature label in systems table" }),
invertSorting: true,
sortUndefined: -1,
size: 50, size: 50,
hideSort: true, hideSort: true,
Icon: ThermometerIcon, Icon: ThermometerIcon,
@@ -254,21 +279,17 @@ export default function SystemsTable() {
return null return null
} }
return ( return (
<span <span className={cn("tabular-nums whitespace-nowrap", viewMode === "table" && "ps-0.5")}>
className={cn("tabular-nums whitespace-nowrap", {
"ps-1.5": viewMode === "table",
})}
>
{decimalString(val)} °C {decimalString(val)} °C
</span> </span>
) )
}, },
}, },
{ {
accessorKey: "info.v", accessorFn: (originalRow) => originalRow.info.v,
id: "agent", id: "agent",
name: () => t`Agent`, name: () => t`Agent`,
invertSorting: true, // invertSorting: true,
size: 50, size: 50,
Icon: WifiIcon, Icon: WifiIcon,
hideSort: true, hideSort: true,
@@ -280,11 +301,7 @@ export default function SystemsTable() {
} }
const system = info.row.original const system = info.row.original
return ( return (
<span <span className={cn("flex gap-2 items-center md:pe-5 tabular-nums", viewMode === "table" && "ps-0.5")}>
className={cn("flex gap-2 items-center md:pe-5 tabular-nums", {
"ps-1": viewMode === "table",
})}
>
<IndicatorDot <IndicatorDot
system={system} system={system}
className={ className={
@@ -304,7 +321,7 @@ export default function SystemsTable() {
name: () => t({ message: "Actions", comment: "Table column" }), name: () => t({ message: "Actions", comment: "Table column" }),
size: 50, size: 50,
cell: ({ row }) => ( cell: ({ row }) => (
<div className="flex justify-end items-center gap-1"> <div className="flex justify-end items-center gap-1 -ms-3">
<AlertsButton system={row.original} /> <AlertsButton system={row.original} />
<ActionsButton system={row.original} /> <ActionsButton system={row.original} />
</div> </div>
@@ -328,6 +345,9 @@ export default function SystemsTable() {
columnVisibility, columnVisibility,
}, },
defaultColumn: { defaultColumn: {
// sortDescFirst: true,
invertSorting: true,
sortUndefined: "last",
minSize: 0, minSize: 0,
size: 900, size: 900,
maxSize: 900, maxSize: 900,
@@ -511,7 +531,7 @@ function SystemsTableHead({ table, colLength }: { table: TableType<SystemRecord>
<TableRow key={headerGroup.id}> <TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => { {headerGroup.headers.map((header) => {
return ( return (
<TableHead className="px-2" key={header.id}> <TableHead className="px-1" key={header.id}>
{flexRender(header.column.columnDef.header, header.getContext())} {flexRender(header.column.columnDef.header, header.getContext())}
</TableHead> </TableHead>
) )

View File

@@ -121,3 +121,12 @@ export function GpuIcon(props: SVGProps<SVGSVGElement>) {
</svg> </svg>
) )
} }
// Remix icons (Apache 2.0) https://github.com/Remix-Design/RemixIcon/blob/master/License
export function HourglassIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 24 24" {...props} fill="currentColor">
<path d="M4 2h16v4.5L13.5 12l6.5 5.5V22H4v-4.5l6.5-5.5L4 6.5zm12.3 5L18 5.5V4H6v1.5L7.7 7zM12 13.3l-6 5.2V20h1l5-3 5 3h1v-1.5z" />
</svg>
)
}

View File

@@ -9,7 +9,7 @@ import { WritableAtom } from "nanostores"
import { timeDay, timeHour } from "d3-time" import { timeDay, timeHour } from "d3-time"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import { CpuIcon, HardDriveIcon, MemoryStickIcon, ServerIcon } from "lucide-react" import { CpuIcon, HardDriveIcon, MemoryStickIcon, ServerIcon } from "lucide-react"
import { EthernetIcon, ThermometerIcon } from "@/components/ui/icons" import { EthernetIcon, HourglassIcon, ThermometerIcon } from "@/components/ui/icons"
import { prependBasePath } from "@/components/router" import { prependBasePath } from "@/components/router"
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
@@ -342,6 +342,26 @@ export const alertInfo: Record<string, AlertInfo> = {
icon: ThermometerIcon, icon: ThermometerIcon,
desc: () => t`Triggers when any sensor exceeds a threshold`, desc: () => t`Triggers when any sensor exceeds a threshold`,
}, },
LoadAvg5: {
name: () => t`Load Average 5m`,
unit: "",
icon: HourglassIcon,
max: 100,
min: 0.1,
start: 10,
step: 0.1,
desc: () => t`Triggers when 5 minute load average exceeds a threshold`,
},
LoadAvg15: {
name: () => t`Load Average 15m`,
unit: "",
icon: HourglassIcon,
min: 0.1,
max: 100,
start: 10,
step: 0.1,
desc: () => t`Triggers when 15 minute load average exceeds a threshold`,
},
} }
/** /**

View File

@@ -448,6 +448,16 @@ msgstr "عنوان البريد الإشباكي غير صالح."
msgid "Kernel" msgid "Kernel"
msgstr "النواة" msgstr "النواة"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "اللغة" msgstr "اللغة"
@@ -461,6 +471,14 @@ msgstr "التخطيط"
msgid "Light" msgid "Light"
msgstr "فاتح" msgstr "فاتح"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "تسجيل الخروج" msgstr "تسجيل الخروج"
@@ -828,6 +846,14 @@ msgstr "تسمح الرموز المميزة للوكلاء بالاتصال و
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "تُستخدم الرموز المميزة والبصمات للمصادقة على اتصالات WebSocket إلى المحور." msgstr "تُستخدم الرموز المميزة والبصمات للمصادقة على اتصالات WebSocket إلى المحور."
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "يتم التفعيل عندما يتجاوز أي مستشعر عتبة معينة" msgstr "يتم التفعيل عندما يتجاوز أي مستشعر عتبة معينة"

View File

@@ -448,6 +448,16 @@ msgstr "Невалиден имейл адрес."
msgid "Kernel" msgid "Kernel"
msgstr "Linux Kernel" msgstr "Linux Kernel"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "Език" msgstr "Език"
@@ -461,6 +471,14 @@ msgstr "Подреждане"
msgid "Light" msgid "Light"
msgstr "Светъл" msgstr "Светъл"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Изход" msgstr "Изход"
@@ -828,6 +846,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Задейства се, когато някой даден сензор надвиши зададен праг" msgstr "Задейства се, когато някой даден сензор надвиши зададен праг"

View File

@@ -448,6 +448,16 @@ msgstr "Neplatná e-mailová adresa."
msgid "Kernel" msgid "Kernel"
msgstr "Kernel" msgstr "Kernel"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "Jazyk" msgstr "Jazyk"
@@ -461,6 +471,14 @@ msgstr "Rozvržení"
msgid "Light" msgid "Light"
msgstr "Světlý" msgstr "Světlý"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Odhlásit" msgstr "Odhlásit"
@@ -828,6 +846,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Spustí se, když některý senzor překročí prahovou hodnotu" msgstr "Spustí se, když některý senzor překročí prahovou hodnotu"

View File

@@ -448,6 +448,16 @@ msgstr "Ugyldig email adresse."
msgid "Kernel" msgid "Kernel"
msgstr "Kernel" msgstr "Kernel"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "Sprog" msgstr "Sprog"
@@ -461,6 +471,14 @@ msgstr "Layout"
msgid "Light" msgid "Light"
msgstr "Lys" msgstr "Lys"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Log ud" msgstr "Log ud"
@@ -828,6 +846,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Udløser når en sensor overstiger en tærskel" msgstr "Udløser når en sensor overstiger en tærskel"

View File

@@ -448,6 +448,16 @@ msgstr "Ungültige E-Mail-Adresse."
msgid "Kernel" msgid "Kernel"
msgstr "Kernel" msgstr "Kernel"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "Sprache" msgstr "Sprache"
@@ -461,6 +471,14 @@ msgstr "Anordnung"
msgid "Light" msgid "Light"
msgstr "Hell" msgstr "Hell"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Abmelden" msgstr "Abmelden"
@@ -828,6 +846,14 @@ msgstr "Tokens ermöglichen es Agents, sich zu verbinden und zu registrieren. Fi
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokens und Fingerabdrücke werden verwendet, um WebSocket-Verbindungen zum Hub zu authentifizieren." msgstr "Tokens und Fingerabdrücke werden verwendet, um WebSocket-Verbindungen zum Hub zu authentifizieren."
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Löst aus, wenn ein Sensor einen Schwellenwert überschreitet" msgstr "Löst aus, wenn ein Sensor einen Schwellenwert überschreitet"

View File

@@ -443,6 +443,16 @@ msgstr "Invalid email address."
msgid "Kernel" msgid "Kernel"
msgstr "Kernel" msgstr "Kernel"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr "L15"
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr "L5"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "Language" msgstr "Language"
@@ -456,6 +466,14 @@ msgstr "Layout"
msgid "Light" msgid "Light"
msgstr "Light" msgstr "Light"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr "Load Average 15m"
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr "Load Average 5m"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Log Out" msgstr "Log Out"
@@ -823,6 +841,14 @@ msgstr "Tokens allow agents to connect and register. Fingerprints are stable ide
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgstr "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr "Triggers when 15 minute load average exceeds a threshold"
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr "Triggers when 5 minute load average exceeds a threshold"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Triggers when any sensor exceeds a threshold" msgstr "Triggers when any sensor exceeds a threshold"

View File

@@ -448,6 +448,16 @@ msgstr "Dirección de correo electrónico no válida."
msgid "Kernel" msgid "Kernel"
msgstr "Kernel" msgstr "Kernel"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "Idioma" msgstr "Idioma"
@@ -461,6 +471,14 @@ msgstr "Diseño"
msgid "Light" msgid "Light"
msgstr "Claro" msgstr "Claro"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Cerrar Sesión" msgstr "Cerrar Sesión"
@@ -828,6 +846,14 @@ msgstr "Los tokens permiten que los agentes se conecten y registren. Las huellas
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Los tokens y las huellas digitales se utilizan para autenticar las conexiones WebSocket al hub." msgstr "Los tokens y las huellas digitales se utilizan para autenticar las conexiones WebSocket al hub."
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Se activa cuando cualquier sensor supera un umbral" msgstr "Se activa cuando cualquier sensor supera un umbral"

View File

@@ -448,6 +448,16 @@ msgstr "آدرس ایمیل نامعتبر است."
msgid "Kernel" msgid "Kernel"
msgstr "هسته" msgstr "هسته"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "زبان" msgstr "زبان"
@@ -461,6 +471,14 @@ msgstr "طرح‌بندی"
msgid "Light" msgid "Light"
msgstr "روشن" msgstr "روشن"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "خروج" msgstr "خروج"
@@ -828,6 +846,14 @@ msgstr "توکن‌ها به عامل‌ها اجازه اتصال و ثبت‌
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "توکن‌ها و اثرات انگشت برای احراز هویت اتصالات WebSocket به هاب استفاده می‌شوند." msgstr "توکن‌ها و اثرات انگشت برای احراز هویت اتصالات WebSocket به هاب استفاده می‌شوند."
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "هنگامی که هر حسگری از یک آستانه فراتر رود، فعال می‌شود" msgstr "هنگامی که هر حسگری از یک آستانه فراتر رود، فعال می‌شود"

View File

@@ -448,6 +448,16 @@ msgstr "Adresse email invalide."
msgid "Kernel" msgid "Kernel"
msgstr "Noyau" msgstr "Noyau"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "Langue" msgstr "Langue"
@@ -461,6 +471,14 @@ msgstr "Disposition"
msgid "Light" msgid "Light"
msgstr "Clair" msgstr "Clair"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Déconnexion" msgstr "Déconnexion"
@@ -828,6 +846,14 @@ msgstr "Les tokens permettent aux agents de se connecter et de s'enregistrer. Le
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Les tokens et les empreintes sont utilisés pour authentifier les connexions WebSocket vers le hub." msgstr "Les tokens et les empreintes sont utilisés pour authentifier les connexions WebSocket vers le hub."
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Déclenchement lorsque tout capteur dépasse un seuil" msgstr "Déclenchement lorsque tout capteur dépasse un seuil"

View File

@@ -448,6 +448,16 @@ msgstr "Nevažeća adresa e-pošte."
msgid "Kernel" msgid "Kernel"
msgstr "Kernel" msgstr "Kernel"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "Jezik" msgstr "Jezik"
@@ -461,6 +471,14 @@ msgstr "Izgled"
msgid "Light" msgid "Light"
msgstr "Svijetlo" msgstr "Svijetlo"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Odjava" msgstr "Odjava"
@@ -828,6 +846,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Pokreće se kada bilo koji senzor prijeđe prag" msgstr "Pokreće se kada bilo koji senzor prijeđe prag"

View File

@@ -448,6 +448,16 @@ msgstr "Érvénytelen e-mail cím."
msgid "Kernel" msgid "Kernel"
msgstr "Kernel" msgstr "Kernel"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "Nyelv" msgstr "Nyelv"
@@ -461,6 +471,14 @@ msgstr "Elrendezés"
msgid "Light" msgid "Light"
msgstr "Világos" msgstr "Világos"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Kijelentkezés" msgstr "Kijelentkezés"
@@ -828,6 +846,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Bekapcsol, ha bármelyik érzékelő túllép egy küszöbértéket" msgstr "Bekapcsol, ha bármelyik érzékelő túllép egy küszöbértéket"

View File

@@ -448,6 +448,16 @@ msgstr "Ógilt netfang."
msgid "Kernel" msgid "Kernel"
msgstr "" msgstr ""
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "Tungumál" msgstr "Tungumál"
@@ -461,6 +471,14 @@ msgstr ""
msgid "Light" msgid "Light"
msgstr "Ljóst" msgstr "Ljóst"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Útskrá" msgstr "Útskrá"
@@ -828,6 +846,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Virkjast þegar einhver skynjari fer yfir þröskuld" msgstr "Virkjast þegar einhver skynjari fer yfir þröskuld"

View File

@@ -448,6 +448,16 @@ msgstr "Indirizzo email non valido."
msgid "Kernel" msgid "Kernel"
msgstr "Kernel" msgstr "Kernel"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "Lingua" msgstr "Lingua"
@@ -461,6 +471,14 @@ msgstr "Aspetto"
msgid "Light" msgid "Light"
msgstr "Chiaro" msgstr "Chiaro"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Disconnetti" msgstr "Disconnetti"
@@ -828,6 +846,14 @@ msgstr "I token consentono agli agenti di connettersi e registrarsi. Le impronte
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "I token e le impronte digitali vengono utilizzati per autenticare le connessioni WebSocket all'hub." msgstr "I token e le impronte digitali vengono utilizzati per autenticare le connessioni WebSocket all'hub."
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Attiva quando un sensore supera una soglia" msgstr "Attiva quando un sensore supera una soglia"

View File

@@ -448,6 +448,16 @@ msgstr "無効なメールアドレスです。"
msgid "Kernel" msgid "Kernel"
msgstr "カーネル" msgstr "カーネル"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "言語" msgstr "言語"
@@ -461,6 +471,14 @@ msgstr "レイアウト"
msgid "Light" msgid "Light"
msgstr "ライト" msgstr "ライト"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "ログアウト" msgstr "ログアウト"
@@ -828,6 +846,14 @@ msgstr "トークンはエージェントの接続と登録を可能にします
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "トークンとフィンガープリントは、ハブへのWebSocket接続の認証に使用されます。" msgstr "トークンとフィンガープリントは、ハブへのWebSocket接続の認証に使用されます。"
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "センサーがしきい値を超えたときにトリガーされます" msgstr "センサーがしきい値を超えたときにトリガーされます"

View File

@@ -448,6 +448,16 @@ msgstr "잘못된 이메일 주소입니다."
msgid "Kernel" msgid "Kernel"
msgstr "커널" msgstr "커널"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "언어" msgstr "언어"
@@ -461,6 +471,14 @@ msgstr "레이아웃"
msgid "Light" msgid "Light"
msgstr "밝게" msgstr "밝게"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "로그아웃" msgstr "로그아웃"
@@ -828,6 +846,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "센서가 임계값을 초과할 때 트리거됩니다." msgstr "센서가 임계값을 초과할 때 트리거됩니다."

View File

@@ -448,6 +448,16 @@ msgstr "Ongeldig e-mailadres."
msgid "Kernel" msgid "Kernel"
msgstr "Kernel" msgstr "Kernel"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "Taal" msgstr "Taal"
@@ -461,6 +471,14 @@ msgstr "Indeling"
msgid "Light" msgid "Light"
msgstr "Licht" msgstr "Licht"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Afmelden" msgstr "Afmelden"
@@ -828,6 +846,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Triggert wanneer een sensor een drempelwaarde overschrijdt" msgstr "Triggert wanneer een sensor een drempelwaarde overschrijdt"

View File

@@ -448,6 +448,16 @@ msgstr "Ugyldig e-postadresse."
msgid "Kernel" msgid "Kernel"
msgstr "Kjerne" msgstr "Kjerne"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "Språk" msgstr "Språk"
@@ -461,6 +471,14 @@ msgstr "Layout"
msgid "Light" msgid "Light"
msgstr "Lyst" msgstr "Lyst"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Logg Ut" msgstr "Logg Ut"
@@ -828,6 +846,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Slår inn når enhver sensor overstiger en grenseverdi" msgstr "Slår inn når enhver sensor overstiger en grenseverdi"

View File

@@ -448,6 +448,16 @@ msgstr "Nieprawidłowy adres e-mail."
msgid "Kernel" msgid "Kernel"
msgstr "Jądro" msgstr "Jądro"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "Język" msgstr "Język"
@@ -461,6 +471,14 @@ msgstr "Układ"
msgid "Light" msgid "Light"
msgstr "Jasny" msgstr "Jasny"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Wyloguj" msgstr "Wyloguj"
@@ -828,6 +846,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Wyzwalane, gdy jakikolwiek czujnik przekroczy ustalony próg." msgstr "Wyzwalane, gdy jakikolwiek czujnik przekroczy ustalony próg."

View File

@@ -448,6 +448,16 @@ msgstr "Endereço de email inválido."
msgid "Kernel" msgid "Kernel"
msgstr "Kernel" msgstr "Kernel"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "Idioma" msgstr "Idioma"
@@ -461,6 +471,14 @@ msgstr "Aspeto"
msgid "Light" msgid "Light"
msgstr "Claro" msgstr "Claro"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Sair" msgstr "Sair"
@@ -828,6 +846,14 @@ msgstr "Os tokens permitem que os agentes se conectem e registrem. As impressõe
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokens e impressões digitais são usados para autenticar conexões WebSocket ao hub." msgstr "Tokens e impressões digitais são usados para autenticar conexões WebSocket ao hub."
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Dispara quando qualquer sensor excede um limite" msgstr "Dispara quando qualquer sensor excede um limite"

View File

@@ -448,6 +448,16 @@ msgstr "Неверный адрес электронной почты."
msgid "Kernel" msgid "Kernel"
msgstr "Ядро" msgstr "Ядро"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "Язык" msgstr "Язык"
@@ -461,6 +471,14 @@ msgstr "Макет"
msgid "Light" msgid "Light"
msgstr "Светлая" msgstr "Светлая"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Выйти" msgstr "Выйти"
@@ -828,6 +846,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Срабатывает, когда любой датчик превышает порог" msgstr "Срабатывает, когда любой датчик превышает порог"

View File

@@ -448,6 +448,16 @@ msgstr "Napačen e-poštni naslov."
msgid "Kernel" msgid "Kernel"
msgstr "Jedro" msgstr "Jedro"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "Jezik" msgstr "Jezik"
@@ -461,6 +471,14 @@ msgstr "Postavitev"
msgid "Light" msgid "Light"
msgstr "Svetlo" msgstr "Svetlo"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Odjava" msgstr "Odjava"
@@ -828,6 +846,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Sproži se, ko kateri koli senzor preseže prag" msgstr "Sproži se, ko kateri koli senzor preseže prag"

View File

@@ -448,6 +448,16 @@ msgstr "Ogiltig e-postadress."
msgid "Kernel" msgid "Kernel"
msgstr "Kärna" msgstr "Kärna"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "Språk" msgstr "Språk"
@@ -461,6 +471,14 @@ msgstr "Layout"
msgid "Light" msgid "Light"
msgstr "Ljust" msgstr "Ljust"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Logga ut" msgstr "Logga ut"
@@ -828,6 +846,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Utlöses när någon sensor överskrider ett tröskelvärde" msgstr "Utlöses när någon sensor överskrider ett tröskelvärde"

View File

@@ -448,6 +448,16 @@ msgstr "Geçersiz e-posta adresi."
msgid "Kernel" msgid "Kernel"
msgstr "Çekirdek" msgstr "Çekirdek"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "Dil" msgstr "Dil"
@@ -461,6 +471,14 @@ msgstr "Düzen"
msgid "Light" msgid "Light"
msgstr "Açık" msgstr "Açık"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Çıkış Yap" msgstr "Çıkış Yap"
@@ -828,6 +846,14 @@ msgstr "Token'lar agentların bağlanıp kaydolmasına izin verir. Parmak izleri
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Token'lar ve parmak izleri hub'a WebSocket bağlantılarını doğrulamak için kullanılır." msgstr "Token'lar ve parmak izleri hub'a WebSocket bağlantılarını doğrulamak için kullanılır."
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Herhangi bir sensör bir eşiği aştığında tetiklenir" msgstr "Herhangi bir sensör bir eşiği aştığında tetiklenir"

View File

@@ -448,6 +448,16 @@ msgstr "Неправильна адреса електронної пошти."
msgid "Kernel" msgid "Kernel"
msgstr "Ядро" msgstr "Ядро"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "Мова" msgstr "Мова"
@@ -461,6 +471,14 @@ msgstr "Макет"
msgid "Light" msgid "Light"
msgstr "Світлий" msgstr "Світлий"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Вийти" msgstr "Вийти"
@@ -828,6 +846,14 @@ msgstr "Токени дозволяють агентам підключатис
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Токени та відбитки використовуються для автентифікації WebSocket з'єднань до хабу." msgstr "Токени та відбитки використовуються для автентифікації WebSocket з'єднань до хабу."
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Спрацьовує, коли будь-який датчик перевищує поріг" msgstr "Спрацьовує, коли будь-який датчик перевищує поріг"

View File

@@ -448,6 +448,16 @@ msgstr "Địa chỉ email không hợp lệ."
msgid "Kernel" msgid "Kernel"
msgstr "Nhân" msgstr "Nhân"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "Ngôn ngữ" msgstr "Ngôn ngữ"
@@ -461,6 +471,14 @@ msgstr "Bố cục"
msgid "Light" msgid "Light"
msgstr "Sáng" msgstr "Sáng"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Đăng xuất" msgstr "Đăng xuất"
@@ -828,6 +846,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Kích hoạt khi bất kỳ cảm biến nào vượt quá ngưỡng" msgstr "Kích hoạt khi bất kỳ cảm biến nào vượt quá ngưỡng"

View File

@@ -448,6 +448,16 @@ msgstr "无效的电子邮件地址。"
msgid "Kernel" msgid "Kernel"
msgstr "内核" msgstr "内核"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "语言" msgstr "语言"
@@ -461,6 +471,14 @@ msgstr "布局"
msgid "Light" msgid "Light"
msgstr "浅色模式" msgstr "浅色模式"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "登出" msgstr "登出"
@@ -828,6 +846,14 @@ msgstr "令牌允许客户端连接和注册。指纹是每个系统唯一的稳
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "令牌和指纹用于验证到中心的WebSocket连接。" msgstr "令牌和指纹用于验证到中心的WebSocket连接。"
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "当任何传感器超过阈值时触发" msgstr "当任何传感器超过阈值时触发"

View File

@@ -448,6 +448,16 @@ msgstr "無效的電子郵件地址。"
msgid "Kernel" msgid "Kernel"
msgstr "Kernel" msgstr "Kernel"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "語言" msgstr "語言"
@@ -461,6 +471,14 @@ msgstr "版面配置"
msgid "Light" msgid "Light"
msgstr "淺色" msgstr "淺色"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "登出" msgstr "登出"
@@ -828,6 +846,14 @@ msgstr "令牌允許代理程式連接和註冊。指紋是每個系統唯一的
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "令牌和指紋用於驗證到中心的WebSocket連接。" msgstr "令牌和指紋用於驗證到中心的WebSocket連接。"
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "當任何傳感器超過閾值時觸發" msgstr "當任何傳感器超過閾值時觸發"

View File

@@ -448,6 +448,16 @@ msgstr "無效的電子郵件地址。"
msgid "Kernel" msgid "Kernel"
msgstr "Kernel" msgstr "Kernel"
#. Load average 15 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L15"
msgstr ""
#. Load average 5 minutes
#: src/components/systems-table/systems-table.tsx
msgid "L5"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
msgstr "語言" msgstr "語言"
@@ -461,6 +471,14 @@ msgstr "版面配置"
msgid "Light" msgid "Light"
msgstr "淺色" msgstr "淺色"
#: src/lib/utils.ts
msgid "Load Average 15m"
msgstr ""
#: src/lib/utils.ts
msgid "Load Average 5m"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "登出" msgstr "登出"
@@ -828,6 +846,14 @@ msgstr "令牌允許代理程式連接和註冊。指紋是每個系統唯一的
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "令牌和指紋用於驗證到中心的WebSocket連接。" msgstr "令牌和指紋用於驗證到中心的WebSocket連接。"
#: src/lib/utils.ts
msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts
msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "當任何感應器超過閾值時觸發" msgstr "當任何感應器超過閾值時觸發"

View File

@@ -44,6 +44,10 @@ export interface SystemInfo {
c: number c: number
/** cpu model */ /** cpu model */
m: string m: string
/** load average 5 minutes */
l5?: number
/** load average 15 minutes */
l15?: number
/** operating system */ /** operating system */
o?: string o?: string
/** uptime */ /** uptime */
@@ -71,6 +75,12 @@ export interface SystemStats {
cpu: number cpu: number
/** peak cpu */ /** peak cpu */
cpum?: number cpum?: number
/** load average 1 minute */
l1?: number
/** load average 5 minutes */
l5?: number
/** load average 15 minutes */
l15?: number
/** total memory (gb) */ /** total memory (gb) */
m: number m: number
/** memory used (gb) */ /** memory used (gb) */
@@ -218,6 +228,9 @@ interface AlertInfo {
icon: any icon: any
desc: () => string desc: () => string
max?: number max?: number
min?: number
step?: number
start?: number
/** Single value description (when there's only one value, like status) */ /** Single value description (when there's only one value, like status) */
singleDesc?: () => string singleDesc?: () => string
} }

View File

@@ -8,7 +8,7 @@ module.exports = {
center: true, center: true,
padding: "1rem", padding: "1rem",
screens: { screens: {
"2xl": "1420px", "2xl": "1440px",
}, },
}, },
extend: { extend: {

View File

@@ -3,8 +3,8 @@ package beszel
import "github.com/blang/semver" import "github.com/blang/semver"
const ( const (
Version = "0.12.0-beta1" Version = "0.12.0-beta2"
AppName = "beszel" AppName = "beszel"
) )
var MinVersionCbor = semver.MustParse("0.12.0-beta1") var MinVersionCbor = semver.MustParse("0.12.0-beta2")