mirror of
https://github.com/henrygd/beszel.git
synced 2026-03-23 22:16:18 +01:00
Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e37469ea9 | ||
|
|
e027479bb1 | ||
|
|
1597e869c1 | ||
|
|
862399d8ec | ||
|
|
f6f85f8f9d | ||
|
|
e22d7ca801 | ||
|
|
c382c1d5f6 | ||
|
|
f7618ed6b0 | ||
|
|
d1295b7c50 | ||
|
|
a162a54a58 | ||
|
|
794db0ac6a | ||
|
|
e9fb9b856f | ||
|
|
66bca11d36 | ||
|
|
86e87f0d47 | ||
|
|
fadfc5d81d | ||
|
|
fc39ff1e4d | ||
|
|
82ccfc66e0 | ||
|
|
890bad1c39 | ||
|
|
9c458885f1 | ||
|
|
d2aed0dc72 | ||
|
|
3dbcb5d7da | ||
|
|
57a1a8b39e | ||
|
|
ab81c04569 | ||
|
|
0c32be3bea | ||
|
|
81d43fbf6e | ||
|
|
96f441de40 | ||
|
|
0e95caaee9 | ||
|
|
7697a12b42 | ||
|
|
94245a9ba4 | ||
|
|
b084814aea | ||
|
|
cce74246ee | ||
|
|
a3420b8c67 | ||
|
|
e1bb17ee9e | ||
|
|
52983f60b7 | ||
|
|
1f053fd85d | ||
|
|
a989d121d3 | ||
|
|
50d2406423 | ||
|
|
059d2d0a5b | ||
|
|
621bef30b5 | ||
|
|
5f4d3dc730 | ||
|
|
8fa9aece63 | ||
|
|
2f1a022e2a | ||
|
|
4815cd29bc | ||
|
|
e49bfaf5d7 | ||
|
|
b13915b76f | ||
|
|
e2a57dc43b | ||
|
|
7222224b40 | ||
|
|
02ff475b84 |
6
.github/workflows/docker-images.yml
vendored
6
.github/workflows/docker-images.yml
vendored
@@ -93,7 +93,9 @@ jobs:
|
|||||||
|
|
||||||
# https://github.com/docker/login-action
|
# https://github.com/docker/login-action
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
if: github.event_name != 'pull_request'
|
env:
|
||||||
|
password_secret_exists: ${{ secrets[matrix.password_secret] != '' && 'true' || 'false' }}
|
||||||
|
if: github.event_name != 'pull_request' && env.password_secret_exists == 'true'
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ matrix.username || secrets[matrix.username_secret] }}
|
username: ${{ matrix.username || secrets[matrix.username_secret] }}
|
||||||
@@ -108,6 +110,6 @@ jobs:
|
|||||||
context: "${{ matrix.context }}"
|
context: "${{ matrix.context }}"
|
||||||
file: ${{ matrix.dockerfile }}
|
file: ${{ matrix.dockerfile }}
|
||||||
platforms: ${{ matrix.platforms || 'linux/amd64,linux/arm64,linux/arm/v7' }}
|
platforms: ${{ matrix.platforms || 'linux/amd64,linux/arm64,linux/arm/v7' }}
|
||||||
push: ${{ github.ref_type == 'tag' }}
|
push: ${{ github.ref_type == 'tag' && secrets[matrix.password_secret] != '' }}
|
||||||
tags: ${{ steps.metadata.outputs.tags }}
|
tags: ${{ steps.metadata.outputs.tags }}
|
||||||
labels: ${{ steps.metadata.outputs.labels }}
|
labels: ${{ steps.metadata.outputs.labels }}
|
||||||
|
|||||||
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@@ -51,3 +51,4 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.TOKEN || secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
WINGET_TOKEN: ${{ secrets.WINGET_TOKEN }}
|
WINGET_TOKEN: ${{ secrets.WINGET_TOKEN }}
|
||||||
|
IS_FORK: ${{ github.repository_owner != 'henrygd' }}
|
||||||
|
|||||||
6
.github/workflows/vulncheck.yml
vendored
6
.github/workflows/vulncheck.yml
vendored
@@ -15,7 +15,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
vulncheck:
|
vulncheck:
|
||||||
name: Analysis
|
name: VulnCheck
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
@@ -23,8 +23,8 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.24.x
|
go-version: 1.25.x
|
||||||
cached: false
|
# cached: false
|
||||||
- name: Get official govulncheck
|
- name: Get official govulncheck
|
||||||
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -38,12 +38,25 @@ builds:
|
|||||||
- mips64
|
- mips64
|
||||||
- riscv64
|
- riscv64
|
||||||
- mipsle
|
- mipsle
|
||||||
|
- mips
|
||||||
- ppc64le
|
- ppc64le
|
||||||
|
gomips:
|
||||||
|
- hardfloat
|
||||||
|
- softfloat
|
||||||
ignore:
|
ignore:
|
||||||
- goos: freebsd
|
- goos: freebsd
|
||||||
goarch: arm
|
goarch: arm
|
||||||
- goos: openbsd
|
- goos: openbsd
|
||||||
goarch: arm
|
goarch: arm
|
||||||
|
- goos: linux
|
||||||
|
goarch: mips64
|
||||||
|
gomips: softfloat
|
||||||
|
- goos: linux
|
||||||
|
goarch: mipsle
|
||||||
|
gomips: hardfloat
|
||||||
|
- goos: linux
|
||||||
|
goarch: mips
|
||||||
|
gomips: hardfloat
|
||||||
- goos: windows
|
- goos: windows
|
||||||
goarch: arm
|
goarch: arm
|
||||||
- goos: darwin
|
- goos: darwin
|
||||||
@@ -54,7 +67,7 @@ builds:
|
|||||||
archives:
|
archives:
|
||||||
- id: beszel-agent
|
- id: beszel-agent
|
||||||
formats: [tar.gz]
|
formats: [tar.gz]
|
||||||
builds:
|
ids:
|
||||||
- beszel-agent
|
- beszel-agent
|
||||||
name_template: >-
|
name_template: >-
|
||||||
{{ .Binary }}_
|
{{ .Binary }}_
|
||||||
@@ -66,7 +79,7 @@ archives:
|
|||||||
|
|
||||||
- id: beszel
|
- id: beszel
|
||||||
formats: [tar.gz]
|
formats: [tar.gz]
|
||||||
builds:
|
ids:
|
||||||
- beszel
|
- beszel
|
||||||
name_template: >-
|
name_template: >-
|
||||||
{{ .Binary }}_
|
{{ .Binary }}_
|
||||||
@@ -85,7 +98,7 @@ nfpms:
|
|||||||
API access.
|
API access.
|
||||||
maintainer: henrygd <hank@henrygd.me>
|
maintainer: henrygd <hank@henrygd.me>
|
||||||
section: net
|
section: net
|
||||||
builds:
|
ids:
|
||||||
- beszel-agent
|
- beszel-agent
|
||||||
formats:
|
formats:
|
||||||
- deb
|
- deb
|
||||||
@@ -122,6 +135,7 @@ 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
|
||||||
|
skip_upload: "{{ if .Env.IS_FORK }}true{{ else }}auto{{ end }}"
|
||||||
|
|
||||||
# # Needs choco installed, so doesn't build on linux / default gh workflow :(
|
# # Needs choco installed, so doesn't build on linux / default gh workflow :(
|
||||||
# chocolateys:
|
# chocolateys:
|
||||||
@@ -155,7 +169,7 @@ brews:
|
|||||||
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
|
skip_upload: "{{ if .Env.IS_FORK }}true{{ else }}auto{{ end }}"
|
||||||
extra_install: |
|
extra_install: |
|
||||||
(bin/"beszel-agent-launcher").write <<~EOS
|
(bin/"beszel-agent-launcher").write <<~EOS
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
@@ -187,7 +201,7 @@ winget:
|
|||||||
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: "{{ if .Env.IS_FORK }}true{{ else }}auto{{ end }}"
|
||||||
description: |
|
description: |
|
||||||
Beszel is a lightweight server monitoring platform that includes Docker
|
Beszel is a lightweight server monitoring platform that includes Docker
|
||||||
statistics, historical data, and alert functions. It has a friendly web
|
statistics, historical data, and alert functions. It has a friendly web
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ SKIP_WEB ?= false
|
|||||||
# Set executable extension based on target OS
|
# Set executable extension based on target OS
|
||||||
EXE_EXT := $(if $(filter windows,$(OS)),.exe,)
|
EXE_EXT := $(if $(filter windows,$(OS)),.exe,)
|
||||||
|
|
||||||
.PHONY: tidy build-agent build-hub build clean lint dev-server dev-agent dev-hub dev generate-locales
|
.PHONY: tidy build-agent build-hub build-hub-dev build clean lint dev-server dev-agent dev-hub dev generate-locales
|
||||||
.DEFAULT_GOAL := build
|
.DEFAULT_GOAL := build
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@@ -53,6 +53,10 @@ build-agent: tidy build-dotnet-conditional
|
|||||||
build-hub: tidy $(if $(filter false,$(SKIP_WEB)),build-web-ui)
|
build-hub: tidy $(if $(filter false,$(SKIP_WEB)),build-web-ui)
|
||||||
GOOS=$(OS) GOARCH=$(ARCH) go build -o ./build/beszel_$(OS)_$(ARCH)$(EXE_EXT) -ldflags "-w -s" beszel/cmd/hub
|
GOOS=$(OS) GOARCH=$(ARCH) go build -o ./build/beszel_$(OS)_$(ARCH)$(EXE_EXT) -ldflags "-w -s" beszel/cmd/hub
|
||||||
|
|
||||||
|
build-hub-dev: tidy
|
||||||
|
mkdir -p ./site/dist && touch ./site/dist/index.html
|
||||||
|
GOOS=$(OS) GOARCH=$(ARCH) go build -tags development -o ./build/beszel-dev_$(OS)_$(ARCH)$(EXE_EXT) -ldflags "-w -s" beszel/cmd/hub
|
||||||
|
|
||||||
build: build-agent build-hub
|
build: build-agent build-hub
|
||||||
|
|
||||||
generate-locales:
|
generate-locales:
|
||||||
@@ -73,9 +77,9 @@ dev-hub: export ENV=dev
|
|||||||
dev-hub:
|
dev-hub:
|
||||||
mkdir -p ./site/dist && touch ./site/dist/index.html
|
mkdir -p ./site/dist && touch ./site/dist/index.html
|
||||||
@if command -v entr >/dev/null 2>&1; then \
|
@if command -v entr >/dev/null 2>&1; then \
|
||||||
find ./cmd/hub/*.go ./internal/{alerts,hub,records,users}/*.go | entr -r -s "cd ./cmd/hub && go run . serve --http 0.0.0.0:8090"; \
|
find ./cmd/hub/*.go ./internal/{alerts,hub,records,users}/*.go | entr -r -s "cd ./cmd/hub && go run -tags development . serve --http 0.0.0.0:8090"; \
|
||||||
else \
|
else \
|
||||||
cd ./cmd/hub && go run . serve --http 0.0.0.0:8090; \
|
cd ./cmd/hub && go run -tags development . serve --http 0.0.0.0:8090; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
dev-agent:
|
dev-agent:
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import (
|
|||||||
"beszel"
|
"beszel"
|
||||||
"beszel/internal/agent"
|
"beszel/internal/agent"
|
||||||
"beszel/internal/agent/health"
|
"beszel/internal/agent/health"
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/pflag"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,43 +17,24 @@ import (
|
|||||||
type cmdOptions struct {
|
type cmdOptions struct {
|
||||||
key string // key is the public key(s) for SSH authentication.
|
key string // key is the public key(s) for SSH authentication.
|
||||||
listen string // listen is the address or port to listen on.
|
listen string // listen is the address or port to listen on.
|
||||||
|
// TODO: add hubURL and token
|
||||||
|
// hubURL string // hubURL is the URL of the hub to use.
|
||||||
|
// token string // token is the token to use for authentication.
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse parses the command line flags and populates the config struct.
|
// parse parses the command line flags and populates the config struct.
|
||||||
// It returns true if a subcommand was handled and the program should exit.
|
// It returns true if a subcommand was handled and the program should exit.
|
||||||
func (opts *cmdOptions) parse() bool {
|
func (opts *cmdOptions) parse() bool {
|
||||||
flag.StringVar(&opts.key, "key", "", "Public key(s) for SSH authentication")
|
|
||||||
flag.StringVar(&opts.listen, "listen", "", "Address or port to listen on")
|
|
||||||
|
|
||||||
flag.Usage = func() {
|
|
||||||
builder := strings.Builder{}
|
|
||||||
builder.WriteString("Usage: ")
|
|
||||||
builder.WriteString(os.Args[0])
|
|
||||||
builder.WriteString(" [command] [flags]\n")
|
|
||||||
builder.WriteString("\nCommands:\n")
|
|
||||||
builder.WriteString(" health Check if the agent is running\n")
|
|
||||||
builder.WriteString(" help Display this help message\n")
|
|
||||||
builder.WriteString(" update Update to the latest version\n")
|
|
||||||
builder.WriteString("\nFlags:\n")
|
|
||||||
fmt.Print(builder.String())
|
|
||||||
flag.PrintDefaults()
|
|
||||||
}
|
|
||||||
|
|
||||||
subcommand := ""
|
subcommand := ""
|
||||||
if len(os.Args) > 1 {
|
if len(os.Args) > 1 {
|
||||||
subcommand = os.Args[1]
|
subcommand = os.Args[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Subcommands that don't require any pflag parsing
|
||||||
switch subcommand {
|
switch subcommand {
|
||||||
case "-v", "version":
|
case "-v", "version":
|
||||||
fmt.Println(beszel.AppName+"-agent", beszel.Version)
|
fmt.Println(beszel.AppName+"-agent", beszel.Version)
|
||||||
return true
|
return true
|
||||||
case "help":
|
|
||||||
flag.Usage()
|
|
||||||
return true
|
|
||||||
case "update":
|
|
||||||
agent.Update()
|
|
||||||
return true
|
|
||||||
case "health":
|
case "health":
|
||||||
err := health.Check()
|
err := health.Check()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -63,7 +44,57 @@ func (opts *cmdOptions) parse() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
flag.Parse()
|
// pflag.CommandLine.ParseErrorsWhitelist.UnknownFlags = true
|
||||||
|
pflag.StringVarP(&opts.key, "key", "k", "", "Public key(s) for SSH authentication")
|
||||||
|
pflag.StringVarP(&opts.listen, "listen", "l", "", "Address or port to listen on")
|
||||||
|
// pflag.StringVarP(&opts.hubURL, "hub-url", "u", "", "URL of the hub to use")
|
||||||
|
// pflag.StringVarP(&opts.token, "token", "t", "", "Token to use for authentication")
|
||||||
|
chinaMirrors := pflag.BoolP("china-mirrors", "c", false, "Use mirror for update (gh.beszel.dev) instead of GitHub")
|
||||||
|
help := pflag.BoolP("help", "h", false, "Show this help message")
|
||||||
|
|
||||||
|
// Convert old single-dash long flags to double-dash for backward compatibility
|
||||||
|
flagsToConvert := []string{"key", "listen"}
|
||||||
|
for i, arg := range os.Args {
|
||||||
|
for _, flag := range flagsToConvert {
|
||||||
|
singleDash := "-" + flag
|
||||||
|
doubleDash := "--" + flag
|
||||||
|
if arg == singleDash {
|
||||||
|
os.Args[i] = doubleDash
|
||||||
|
break
|
||||||
|
} else if strings.HasPrefix(arg, singleDash+"=") {
|
||||||
|
os.Args[i] = doubleDash + arg[len(singleDash):]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pflag.Usage = func() {
|
||||||
|
builder := strings.Builder{}
|
||||||
|
builder.WriteString("Usage: ")
|
||||||
|
builder.WriteString(os.Args[0])
|
||||||
|
builder.WriteString(" [command] [flags]\n")
|
||||||
|
builder.WriteString("\nCommands:\n")
|
||||||
|
builder.WriteString(" health Check if the agent is running\n")
|
||||||
|
// builder.WriteString(" help Display this help message\n")
|
||||||
|
builder.WriteString(" update Update to the latest version\n")
|
||||||
|
builder.WriteString("\nFlags:\n")
|
||||||
|
fmt.Print(builder.String())
|
||||||
|
pflag.PrintDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse all arguments with pflag
|
||||||
|
pflag.Parse()
|
||||||
|
|
||||||
|
// Must run after pflag.Parse()
|
||||||
|
switch {
|
||||||
|
case *help || subcommand == "help":
|
||||||
|
pflag.Usage()
|
||||||
|
return true
|
||||||
|
case subcommand == "update":
|
||||||
|
agent.Update(*chinaMirrors)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ package main
|
|||||||
import (
|
import (
|
||||||
"beszel/internal/agent"
|
"beszel/internal/agent"
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"flag"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/pflag"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
@@ -245,7 +245,7 @@ func TestParseFlags(t *testing.T) {
|
|||||||
oldArgs := os.Args
|
oldArgs := os.Args
|
||||||
defer func() {
|
defer func() {
|
||||||
os.Args = oldArgs
|
os.Args = oldArgs
|
||||||
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
|
pflag.CommandLine = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -269,6 +269,22 @@ func TestParseFlags(t *testing.T) {
|
|||||||
listen: "",
|
listen: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "key flag double dash",
|
||||||
|
args: []string{"cmd", "--key", "testkey"},
|
||||||
|
expected: cmdOptions{
|
||||||
|
key: "testkey",
|
||||||
|
listen: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "key flag short",
|
||||||
|
args: []string{"cmd", "-k", "testkey"},
|
||||||
|
expected: cmdOptions{
|
||||||
|
key: "testkey",
|
||||||
|
listen: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "addr flag only",
|
name: "addr flag only",
|
||||||
args: []string{"cmd", "-listen", ":8080"},
|
args: []string{"cmd", "-listen", ":8080"},
|
||||||
@@ -277,6 +293,22 @@ func TestParseFlags(t *testing.T) {
|
|||||||
listen: ":8080",
|
listen: ":8080",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "addr flag double dash",
|
||||||
|
args: []string{"cmd", "--listen", ":8080"},
|
||||||
|
expected: cmdOptions{
|
||||||
|
key: "",
|
||||||
|
listen: ":8080",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "addr flag short",
|
||||||
|
args: []string{"cmd", "-l", ":8080"},
|
||||||
|
expected: cmdOptions{
|
||||||
|
key: "",
|
||||||
|
listen: ":8080",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "both flags",
|
name: "both flags",
|
||||||
args: []string{"cmd", "-key", "testkey", "-listen", ":8080"},
|
args: []string{"cmd", "-key", "testkey", "-listen", ":8080"},
|
||||||
@@ -290,12 +322,12 @@ func TestParseFlags(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
// Reset flags for each test
|
// Reset flags for each test
|
||||||
flag.CommandLine = flag.NewFlagSet(tt.args[0], flag.ExitOnError)
|
pflag.CommandLine = pflag.NewFlagSet(tt.args[0], pflag.ExitOnError)
|
||||||
os.Args = tt.args
|
os.Args = tt.args
|
||||||
|
|
||||||
var opts cmdOptions
|
var opts cmdOptions
|
||||||
opts.parse()
|
opts.parse()
|
||||||
flag.Parse()
|
pflag.Parse()
|
||||||
|
|
||||||
assert.Equal(t, tt.expected, opts)
|
assert.Equal(t, tt.expected, opts)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -45,11 +45,13 @@ func getBaseApp() *pocketbase.PocketBase {
|
|||||||
baseApp.RootCmd.Use = beszel.AppName
|
baseApp.RootCmd.Use = beszel.AppName
|
||||||
baseApp.RootCmd.Short = ""
|
baseApp.RootCmd.Short = ""
|
||||||
// add update command
|
// add update command
|
||||||
baseApp.RootCmd.AddCommand(&cobra.Command{
|
updateCmd := &cobra.Command{
|
||||||
Use: "update",
|
Use: "update",
|
||||||
Short: "Update " + beszel.AppName + " to the latest version",
|
Short: "Update " + beszel.AppName + " to the latest version",
|
||||||
Run: hub.Update,
|
Run: hub.Update,
|
||||||
})
|
}
|
||||||
|
updateCmd.Flags().Bool("china-mirrors", false, "Use mirror (gh.beszel.dev) instead of GitHub")
|
||||||
|
baseApp.RootCmd.AddCommand(updateCmd)
|
||||||
// add health command
|
// add health command
|
||||||
baseApp.RootCmd.AddCommand(newHealthCmd())
|
baseApp.RootCmd.AddCommand(newHealthCmd())
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ RUN CGO_ENABLED=0 GOGC=75 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags "-
|
|||||||
# --------------------------
|
# --------------------------
|
||||||
# Final image: GPU-enabled agent with nvidia-smi
|
# Final image: GPU-enabled agent with nvidia-smi
|
||||||
# --------------------------
|
# --------------------------
|
||||||
FROM nvidia/cuda:12.9.1-base-ubuntu22.04
|
FROM nvidia/cuda:12.2.2-base-ubuntu22.04
|
||||||
COPY --from=builder /agent /agent
|
COPY --from=builder /agent /agent
|
||||||
|
|
||||||
ENTRYPOINT ["/agent"]
|
ENTRYPOINT ["/agent"]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module beszel
|
module beszel
|
||||||
|
|
||||||
go 1.24.4
|
go 1.25.1
|
||||||
|
|
||||||
// lock shoutrrr to specific version to allow review before updating
|
// lock shoutrrr to specific version to allow review before updating
|
||||||
replace github.com/nicholas-fedor/shoutrrr => github.com/nicholas-fedor/shoutrrr v0.8.8
|
replace github.com/nicholas-fedor/shoutrrr => github.com/nicholas-fedor/shoutrrr v0.8.8
|
||||||
@@ -15,9 +15,10 @@ require (
|
|||||||
github.com/nicholas-fedor/shoutrrr v0.8.17
|
github.com/nicholas-fedor/shoutrrr v0.8.17
|
||||||
github.com/pocketbase/dbx v1.11.0
|
github.com/pocketbase/dbx v1.11.0
|
||||||
github.com/pocketbase/pocketbase v0.29.3
|
github.com/pocketbase/pocketbase v0.29.3
|
||||||
github.com/shirou/gopsutil/v4 v4.25.7
|
github.com/shirou/gopsutil/v4 v4.25.6
|
||||||
github.com/spf13/cast v1.9.2
|
github.com/spf13/cast v1.9.2
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.9.1
|
||||||
|
github.com/spf13/pflag v1.0.7
|
||||||
github.com/stretchr/testify v1.11.0
|
github.com/stretchr/testify v1.11.0
|
||||||
golang.org/x/crypto v0.41.0
|
golang.org/x/crypto v0.41.0
|
||||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b
|
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b
|
||||||
@@ -49,7 +50,6 @@ require (
|
|||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/spf13/pflag v1.0.7 // indirect
|
|
||||||
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
||||||
github.com/tklauser/numcpus v0.10.0 // indirect
|
github.com/tklauser/numcpus v0.10.0 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
|
|||||||
@@ -97,8 +97,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq
|
|||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM=
|
github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs=
|
||||||
github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U=
|
github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
|
||||||
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
||||||
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ type Agent struct {
|
|||||||
server *ssh.Server // SSH server
|
server *ssh.Server // SSH server
|
||||||
dataDir string // Directory for persisting data
|
dataDir string // Directory for persisting data
|
||||||
keys []gossh.PublicKey // SSH public keys
|
keys []gossh.PublicKey // SSH public keys
|
||||||
hasBattery bool // true if agent has access to battery stats
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAgent creates a new agent with the given data directory for persisting data.
|
// NewAgent creates a new agent with the given data directory for persisting data.
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
package agent
|
|
||||||
|
|
||||||
import "github.com/distatus/battery"
|
|
||||||
|
|
||||||
// getBatteryStats returns the current battery percent and charge state
|
|
||||||
func getBatteryStats() (batteryPercent uint8, batteryState uint8, err error) {
|
|
||||||
batteries, err := battery.GetAll()
|
|
||||||
if err != nil || len(batteries) == 0 {
|
|
||||||
return batteryPercent, batteryState, err
|
|
||||||
}
|
|
||||||
totalCapacity := float64(0)
|
|
||||||
totalCharge := float64(0)
|
|
||||||
for _, bat := range batteries {
|
|
||||||
if bat.Design != 0 {
|
|
||||||
totalCapacity += bat.Design
|
|
||||||
} else {
|
|
||||||
totalCapacity += bat.Full
|
|
||||||
}
|
|
||||||
totalCharge += bat.Current
|
|
||||||
}
|
|
||||||
batteryPercent = uint8(totalCharge / totalCapacity * 100)
|
|
||||||
batteryState = uint8(batteries[0].State.Raw)
|
|
||||||
return batteryPercent, batteryState, nil
|
|
||||||
}
|
|
||||||
53
beszel/internal/agent/battery/battery.go
Normal file
53
beszel/internal/agent/battery/battery.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
//go:build !freebsd
|
||||||
|
|
||||||
|
// Package battery provides functions to check if the system has a battery and to get the battery stats.
|
||||||
|
package battery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"github.com/distatus/battery"
|
||||||
|
)
|
||||||
|
|
||||||
|
var systemHasBattery = false
|
||||||
|
var haveCheckedBattery = false
|
||||||
|
|
||||||
|
// HasReadableBattery checks if the system has a battery and returns true if it does.
|
||||||
|
func HasReadableBattery() bool {
|
||||||
|
if haveCheckedBattery {
|
||||||
|
return systemHasBattery
|
||||||
|
}
|
||||||
|
haveCheckedBattery = true
|
||||||
|
bat, err := battery.Get(0)
|
||||||
|
if err == nil && bat != nil {
|
||||||
|
systemHasBattery = true
|
||||||
|
} else {
|
||||||
|
slog.Debug("No battery found", "err", err)
|
||||||
|
}
|
||||||
|
return systemHasBattery
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBatteryStats returns the current battery percent and charge state
|
||||||
|
func GetBatteryStats() (batteryPercent uint8, batteryState uint8, err error) {
|
||||||
|
if !systemHasBattery {
|
||||||
|
return batteryPercent, batteryState, errors.ErrUnsupported
|
||||||
|
}
|
||||||
|
batteries, err := battery.GetAll()
|
||||||
|
if err != nil || len(batteries) == 0 {
|
||||||
|
return batteryPercent, batteryState, err
|
||||||
|
}
|
||||||
|
totalCapacity := float64(0)
|
||||||
|
totalCharge := float64(0)
|
||||||
|
for _, bat := range batteries {
|
||||||
|
if bat.Design != 0 {
|
||||||
|
totalCapacity += bat.Design
|
||||||
|
} else {
|
||||||
|
totalCapacity += bat.Full
|
||||||
|
}
|
||||||
|
totalCharge += bat.Current
|
||||||
|
}
|
||||||
|
batteryPercent = uint8(totalCharge / totalCapacity * 100)
|
||||||
|
batteryState = uint8(batteries[0].State.Raw)
|
||||||
|
return batteryPercent, batteryState, nil
|
||||||
|
}
|
||||||
13
beszel/internal/agent/battery/battery_freebsd.go
Normal file
13
beszel/internal/agent/battery/battery_freebsd.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
//go:build freebsd
|
||||||
|
|
||||||
|
package battery
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
func HasReadableBattery() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBatteryStats() (uint8, uint8, error) {
|
||||||
|
return 0, 0, errors.ErrUnsupported
|
||||||
|
}
|
||||||
@@ -46,9 +46,10 @@ var lhmFs embed.FS
|
|||||||
var (
|
var (
|
||||||
beszelLhm *lhmProcess
|
beszelLhm *lhmProcess
|
||||||
beszelLhmOnce sync.Once
|
beszelLhmOnce sync.Once
|
||||||
|
useLHM = os.Getenv("LHM") == "true"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errNoSensors = errors.New("no sensors found (try running as admin)")
|
var errNoSensors = errors.New("no sensors found (try running as admin with LHM=true)")
|
||||||
|
|
||||||
// newlhmProcess copies the embedded LHM executable to a temporary directory and starts it.
|
// newlhmProcess copies the embedded LHM executable to a temporary directory and starts it.
|
||||||
func newlhmProcess() (*lhmProcess, error) {
|
func newlhmProcess() (*lhmProcess, error) {
|
||||||
@@ -139,7 +140,7 @@ func (lhm *lhmProcess) cleanupProcess() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (lhm *lhmProcess) getTemps(ctx context.Context) (temps []sensors.TemperatureStat, err error) {
|
func (lhm *lhmProcess) getTemps(ctx context.Context) (temps []sensors.TemperatureStat, err error) {
|
||||||
if lhm.stoppedNoSensors {
|
if !useLHM || lhm.stoppedNoSensors {
|
||||||
// Fall back to gopsutil if we can't get sensors from LHM
|
// Fall back to gopsutil if we can't get sensors from LHM
|
||||||
return sensors.TemperaturesWithContext(ctx)
|
return sensors.TemperaturesWithContext(ctx)
|
||||||
}
|
}
|
||||||
@@ -222,6 +223,10 @@ func getSensorTemps(ctx context.Context) (temps []sensors.TemperatureStat, err e
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
if !useLHM {
|
||||||
|
return sensors.TemperaturesWithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize process once
|
// Initialize process once
|
||||||
beszelLhmOnce.Do(func() {
|
beszelLhmOnce.Do(func() {
|
||||||
beszelLhm, err = newlhmProcess()
|
beszelLhm, err = newlhmProcess()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package agent
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"beszel"
|
"beszel"
|
||||||
|
"beszel/internal/agent/battery"
|
||||||
"beszel/internal/entities/system"
|
"beszel/internal/entities/system"
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -64,13 +65,6 @@ func (a *Agent) initializeSystemInfo() {
|
|||||||
} else {
|
} else {
|
||||||
a.zfs = true
|
a.zfs = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// battery
|
|
||||||
if _, _, err := getBatteryStats(); err != nil {
|
|
||||||
slog.Debug("No battery detected", "err", err)
|
|
||||||
} else {
|
|
||||||
a.hasBattery = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns current info, stats about the host system
|
// Returns current info, stats about the host system
|
||||||
@@ -78,8 +72,8 @@ func (a *Agent) getSystemStats() system.Stats {
|
|||||||
systemStats := system.Stats{}
|
systemStats := system.Stats{}
|
||||||
|
|
||||||
// battery
|
// battery
|
||||||
if a.hasBattery {
|
if battery.HasReadableBattery() {
|
||||||
systemStats.Battery[0], systemStats.Battery[1], _ = getBatteryStats()
|
systemStats.Battery[0], systemStats.Battery[1], _ = battery.GetBatteryStats()
|
||||||
}
|
}
|
||||||
|
|
||||||
// cpu percent
|
// cpu percent
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ func detectRestarter() restarter {
|
|||||||
|
|
||||||
// Update checks GitHub for a newer release of beszel-agent, applies it,
|
// Update checks GitHub for a newer release of beszel-agent, applies it,
|
||||||
// fixes SELinux context if needed, and restarts the service.
|
// fixes SELinux context if needed, and restarts the service.
|
||||||
func Update() error {
|
func Update(useMirror bool) error {
|
||||||
exePath, _ := os.Executable()
|
exePath, _ := os.Executable()
|
||||||
|
|
||||||
dataDir, err := getDataDir()
|
dataDir, err := getDataDir()
|
||||||
@@ -70,6 +70,7 @@ func Update() error {
|
|||||||
updated, err := ghupdate.Update(ghupdate.Config{
|
updated, err := ghupdate.Update(ghupdate.Config{
|
||||||
ArchiveExecutable: "beszel-agent",
|
ArchiveExecutable: "beszel-agent",
|
||||||
DataDir: dataDir,
|
DataDir: dataDir,
|
||||||
|
UseMirror: useMirror,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@@ -99,6 +100,8 @@ func Update() error {
|
|||||||
if err := r.Restart(); err != nil {
|
if err := r.Restart(); err != nil {
|
||||||
ghupdate.ColorPrintf(ghupdate.ColorYellow, "Warning: failed to restart service: %v", err)
|
ghupdate.ColorPrintf(ghupdate.ColorYellow, "Warning: failed to restart service: %v", err)
|
||||||
ghupdate.ColorPrint(ghupdate.ColorYellow, "Please restart the service manually.")
|
ghupdate.ColorPrint(ghupdate.ColorYellow, "Please restart the service manually.")
|
||||||
|
} else {
|
||||||
|
ghupdate.ColorPrint(ghupdate.ColorGreen, "Service restarted successfully")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ghupdate.ColorPrint(ghupdate.ColorYellow, "No supported init system detected; please restart manually if needed.")
|
ghupdate.ColorPrint(ghupdate.ColorYellow, "No supported init system detected; please restart manually if needed.")
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ var supportsTitle = map[string]struct{}{
|
|||||||
func NewAlertManager(app hubLike) *AlertManager {
|
func NewAlertManager(app hubLike) *AlertManager {
|
||||||
am := &AlertManager{
|
am := &AlertManager{
|
||||||
hub: app,
|
hub: app,
|
||||||
alertQueue: make(chan alertTask),
|
alertQueue: make(chan alertTask, 5),
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
}
|
}
|
||||||
am.bindEvents()
|
am.bindEvents()
|
||||||
|
|||||||
@@ -42,21 +42,10 @@ func updateHistoryOnAlertUpdate(e *core.RecordEvent) error {
|
|||||||
|
|
||||||
// resolveAlertHistoryRecord sets the resolved field to the current time
|
// resolveAlertHistoryRecord sets the resolved field to the current time
|
||||||
func resolveAlertHistoryRecord(app core.App, alertRecordID string) error {
|
func resolveAlertHistoryRecord(app core.App, alertRecordID string) error {
|
||||||
alertHistoryRecords, err := app.FindRecordsByFilter(
|
alertHistoryRecord, err := app.FindFirstRecordByFilter("alerts_history", "alert_id={:alert_id} && resolved=null", dbx.Params{"alert_id": alertRecordID})
|
||||||
"alerts_history",
|
if err != nil || alertHistoryRecord == nil {
|
||||||
"alert_id={:alert_id} && resolved=null",
|
|
||||||
"-created",
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
dbx.Params{"alert_id": alertRecordID},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(alertHistoryRecords) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
alertHistoryRecord := alertHistoryRecords[0] // there should be only one record
|
|
||||||
alertHistoryRecord.Set("resolved", time.Now().UTC())
|
alertHistoryRecord.Set("resolved", time.Now().UTC())
|
||||||
err = app.Save(alertHistoryRecord)
|
err = app.Save(alertHistoryRecord)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"testing/synctest"
|
||||||
|
"time"
|
||||||
|
|
||||||
beszelTests "beszel/internal/tests"
|
beszelTests "beszel/internal/tests"
|
||||||
|
|
||||||
@@ -63,14 +65,14 @@ func TestUserAlertsApi(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
scenarios := []beszelTests.ApiScenario{
|
scenarios := []beszelTests.ApiScenario{
|
||||||
{
|
// {
|
||||||
Name: "GET not implemented - returns index",
|
// Name: "GET not implemented - returns index",
|
||||||
Method: http.MethodGet,
|
// Method: http.MethodGet,
|
||||||
URL: "/api/beszel/user-alerts",
|
// URL: "/api/beszel/user-alerts",
|
||||||
ExpectedStatus: 200,
|
// ExpectedStatus: 200,
|
||||||
ExpectedContent: []string{"<html ", "globalThis.BESZEL"},
|
// ExpectedContent: []string{"<html ", "globalThis.BESZEL"},
|
||||||
TestAppFactory: testAppFactory,
|
// TestAppFactory: testAppFactory,
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
Name: "POST no auth",
|
Name: "POST no auth",
|
||||||
Method: http.MethodPost,
|
Method: http.MethodPost,
|
||||||
@@ -366,3 +368,237 @@ func TestUserAlertsApi(t *testing.T) {
|
|||||||
scenario.Test(t)
|
scenario.Test(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getHubWithUser(t *testing.T) (*beszelTests.TestHub, *core.Record) {
|
||||||
|
hub, err := beszelTests.NewTestHub(t.TempDir())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
hub.StartHub()
|
||||||
|
|
||||||
|
// Manually initialize the system manager to bind event hooks
|
||||||
|
err = hub.GetSystemManager().Initialize()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Create a test user
|
||||||
|
user, err := beszelTests.CreateUser(hub, "test@example.com", "password")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Create user settings for the test user (required for alert notifications)
|
||||||
|
userSettingsData := map[string]any{
|
||||||
|
"user": user.Id,
|
||||||
|
"settings": `{"emails":[test@example.com],"webhooks":[]}`,
|
||||||
|
}
|
||||||
|
_, err = beszelTests.CreateRecord(hub, "user_settings", userSettingsData)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
return hub, user
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatusAlerts(t *testing.T) {
|
||||||
|
synctest.Test(t, func(t *testing.T) {
|
||||||
|
hub, user := getHubWithUser(t)
|
||||||
|
defer hub.Cleanup()
|
||||||
|
|
||||||
|
systems, err := beszelTests.CreateSystems(hub, 4, user.Id, "paused")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var alerts []*core.Record
|
||||||
|
for i, system := range systems {
|
||||||
|
alert, err := beszelTests.CreateRecord(hub, "alerts", map[string]any{
|
||||||
|
"name": "Status",
|
||||||
|
"system": system.Id,
|
||||||
|
"user": user.Id,
|
||||||
|
"min": i + 1,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
alerts = append(alerts, alert)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
|
||||||
|
for _, alert := range alerts {
|
||||||
|
assert.False(t, alert.GetBool("triggered"), "Alert should not be triggered immediately")
|
||||||
|
}
|
||||||
|
if hub.TestMailer.TotalSend() != 0 {
|
||||||
|
assert.Zero(t, hub.TestMailer.TotalSend(), "Expected 0 messages, got %d", hub.TestMailer.TotalSend())
|
||||||
|
}
|
||||||
|
for _, system := range systems {
|
||||||
|
assert.EqualValues(t, "paused", system.GetString("status"), "System should be paused")
|
||||||
|
}
|
||||||
|
for _, system := range systems {
|
||||||
|
system.Set("status", "up")
|
||||||
|
err = hub.SaveNoValidate(system)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
assert.EqualValues(t, 0, hub.GetPendingAlertsCount(), "should have 0 alerts in the pendingAlerts map")
|
||||||
|
for _, system := range systems {
|
||||||
|
system.Set("status", "down")
|
||||||
|
err = hub.SaveNoValidate(system)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
// after 30 seconds, should have 4 alerts in the pendingAlerts map, no triggered alerts
|
||||||
|
time.Sleep(time.Second * 30)
|
||||||
|
assert.EqualValues(t, 4, hub.GetPendingAlertsCount(), "should have 4 alerts in the pendingAlerts map")
|
||||||
|
triggeredCount, err := hub.CountRecords("alerts", dbx.HashExp{"triggered": true})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 0, triggeredCount, "should have 0 alert triggered")
|
||||||
|
assert.EqualValues(t, 0, hub.TestMailer.TotalSend(), "should have 0 messages sent")
|
||||||
|
// after 1:30 seconds, should have 1 triggered alert and 3 pending alerts
|
||||||
|
time.Sleep(time.Second * 60)
|
||||||
|
assert.EqualValues(t, 3, hub.GetPendingAlertsCount(), "should have 3 alerts in the pendingAlerts map")
|
||||||
|
triggeredCount, err = hub.CountRecords("alerts", dbx.HashExp{"triggered": true})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, triggeredCount, "should have 1 alert triggered")
|
||||||
|
assert.EqualValues(t, 1, hub.TestMailer.TotalSend(), "should have 1 messages sent")
|
||||||
|
// after 2:30 seconds, should have 2 triggered alerts and 2 pending alerts
|
||||||
|
time.Sleep(time.Second * 60)
|
||||||
|
assert.EqualValues(t, 2, hub.GetPendingAlertsCount(), "should have 2 alerts in the pendingAlerts map")
|
||||||
|
triggeredCount, err = hub.CountRecords("alerts", dbx.HashExp{"triggered": true})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 2, triggeredCount, "should have 2 alert triggered")
|
||||||
|
assert.EqualValues(t, 2, hub.TestMailer.TotalSend(), "should have 2 messages sent")
|
||||||
|
// now we will bring the remaning systems back up
|
||||||
|
for _, system := range systems {
|
||||||
|
system.Set("status", "up")
|
||||||
|
err = hub.SaveNoValidate(system)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
// should have 0 alerts in the pendingAlerts map and 0 alerts triggered
|
||||||
|
assert.EqualValues(t, 0, hub.GetPendingAlertsCount(), "should have 0 alerts in the pendingAlerts map")
|
||||||
|
triggeredCount, err = hub.CountRecords("alerts", dbx.HashExp{"triggered": true})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Zero(t, triggeredCount, "should have 0 alert triggered")
|
||||||
|
// 4 messages sent, 2 down alerts and 2 up alerts for first 2 systems
|
||||||
|
assert.EqualValues(t, 4, hub.TestMailer.TotalSend(), "should have 4 messages sent")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlertsHistory(t *testing.T) {
|
||||||
|
synctest.Test(t, func(t *testing.T) {
|
||||||
|
hub, user := getHubWithUser(t)
|
||||||
|
defer hub.Cleanup()
|
||||||
|
|
||||||
|
// Create systems and alerts
|
||||||
|
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "up")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
system := systems[0]
|
||||||
|
|
||||||
|
alert, err := beszelTests.CreateRecord(hub, "alerts", map[string]any{
|
||||||
|
"name": "Status",
|
||||||
|
"system": system.Id,
|
||||||
|
"user": user.Id,
|
||||||
|
"min": 1,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Initially, no alert history records should exist
|
||||||
|
initialHistoryCount, err := hub.CountRecords("alerts_history", nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Zero(t, initialHistoryCount, "Should have 0 alert history records initially")
|
||||||
|
|
||||||
|
// Set system to up initially
|
||||||
|
system.Set("status", "up")
|
||||||
|
err = hub.SaveNoValidate(system)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
|
||||||
|
// Set system to down to trigger alert
|
||||||
|
system.Set("status", "down")
|
||||||
|
err = hub.SaveNoValidate(system)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Wait for alert to trigger (after the downtime delay)
|
||||||
|
// With 1 minute delay, we need to wait at least 1 minute + some buffer
|
||||||
|
time.Sleep(time.Second * 75)
|
||||||
|
|
||||||
|
// Check that alert is triggered
|
||||||
|
triggeredCount, err := hub.CountRecords("alerts", dbx.HashExp{"triggered": true, "id": alert.Id})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, triggeredCount, "Alert should be triggered")
|
||||||
|
|
||||||
|
// Check that alert history record was created
|
||||||
|
historyCount, err := hub.CountRecords("alerts_history", dbx.HashExp{"alert_id": alert.Id})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, historyCount, "Should have 1 alert history record for triggered alert")
|
||||||
|
|
||||||
|
// Get the alert history record and verify it's not resolved immediately
|
||||||
|
historyRecord, err := hub.FindFirstRecordByFilter("alerts_history", "alert_id={:alert_id}", dbx.Params{"alert_id": alert.Id})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, historyRecord, "Alert history record should exist")
|
||||||
|
assert.Equal(t, alert.Id, historyRecord.GetString("alert_id"), "Alert history should reference correct alert")
|
||||||
|
assert.Equal(t, system.Id, historyRecord.GetString("system"), "Alert history should reference correct system")
|
||||||
|
assert.Equal(t, "Status", historyRecord.GetString("name"), "Alert history should have correct name")
|
||||||
|
|
||||||
|
// The alert history might be resolved immediately in some cases, so let's check the alert's triggered status
|
||||||
|
alertRecord, err := hub.FindFirstRecordByFilter("alerts", "id={:id}", dbx.Params{"id": alert.Id})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, alertRecord.GetBool("triggered"), "Alert should still be triggered when checking history")
|
||||||
|
|
||||||
|
// Now resolve the alert by setting system back to up
|
||||||
|
system.Set("status", "up")
|
||||||
|
err = hub.SaveNoValidate(system)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
|
// Check that alert is no longer triggered
|
||||||
|
triggeredCount, err = hub.CountRecords("alerts", dbx.HashExp{"triggered": true, "id": alert.Id})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Zero(t, triggeredCount, "Alert should not be triggered after system is back up")
|
||||||
|
|
||||||
|
// Check that alert history record is now resolved
|
||||||
|
historyRecord, err = hub.FindFirstRecordByFilter("alerts_history", "alert_id={:alert_id}", dbx.Params{"alert_id": alert.Id})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, historyRecord, "Alert history record should still exist")
|
||||||
|
assert.NotNil(t, historyRecord.Get("resolved"), "Alert history should be resolved")
|
||||||
|
|
||||||
|
// Test deleting a triggered alert resolves its history
|
||||||
|
// Create another system and alert
|
||||||
|
systems2, err := beszelTests.CreateSystems(hub, 1, user.Id, "up")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
system2 := systems2[0]
|
||||||
|
system2.Set("name", "test-system-2") // Rename for clarity
|
||||||
|
err = hub.SaveNoValidate(system2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
alert2, err := beszelTests.CreateRecord(hub, "alerts", map[string]any{
|
||||||
|
"name": "Status",
|
||||||
|
"system": system2.Id,
|
||||||
|
"user": user.Id,
|
||||||
|
"min": 1,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Set system2 to down to trigger alert
|
||||||
|
system2.Set("status", "down")
|
||||||
|
err = hub.SaveNoValidate(system2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Wait for alert to trigger
|
||||||
|
time.Sleep(time.Second * 75)
|
||||||
|
|
||||||
|
// Verify alert is triggered and history record exists
|
||||||
|
triggeredCount, err = hub.CountRecords("alerts", dbx.HashExp{"triggered": true, "id": alert2.Id})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, triggeredCount, "Second alert should be triggered")
|
||||||
|
|
||||||
|
historyCount, err = hub.CountRecords("alerts_history", dbx.HashExp{"alert_id": alert2.Id})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, historyCount, "Should have 1 alert history record for second alert")
|
||||||
|
|
||||||
|
// Delete the triggered alert
|
||||||
|
err = hub.Delete(alert2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Check that alert history record is resolved after deletion
|
||||||
|
historyRecord2, err := hub.FindFirstRecordByFilter("alerts_history", "alert_id={:alert_id}", dbx.Params{"alert_id": alert2.Id})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, historyRecord2, "Alert history record should still exist after alert deletion")
|
||||||
|
assert.NotNil(t, historyRecord2.Get("resolved"), "Alert history should be resolved after alert deletion")
|
||||||
|
|
||||||
|
// Verify total history count is correct (2 records total)
|
||||||
|
totalHistoryCount, err := hub.CountRecords("alerts_history", nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 2, totalHistoryCount, "Should have 2 total alert history records")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
55
beszel/internal/alerts/alerts_test_helpers.go
Normal file
55
beszel/internal/alerts/alerts_test_helpers.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package alerts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pocketbase/pocketbase/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (am *AlertManager) GetAlertManager() *AlertManager {
|
||||||
|
return am
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *AlertManager) GetPendingAlerts() *sync.Map {
|
||||||
|
return &am.pendingAlerts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *AlertManager) GetPendingAlertsCount() int {
|
||||||
|
count := 0
|
||||||
|
am.pendingAlerts.Range(func(key, value any) bool {
|
||||||
|
count++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessPendingAlerts manually processes all expired alerts (for testing)
|
||||||
|
func (am *AlertManager) ProcessPendingAlerts() ([]*core.Record, error) {
|
||||||
|
now := time.Now()
|
||||||
|
var lastErr error
|
||||||
|
var processedAlerts []*core.Record
|
||||||
|
am.pendingAlerts.Range(func(key, value any) bool {
|
||||||
|
info := value.(*alertInfo)
|
||||||
|
if now.After(info.expireTime) {
|
||||||
|
// Downtime delay has passed, process alert
|
||||||
|
if err := am.sendStatusAlert("down", info.systemName, info.alertRecord); err != nil {
|
||||||
|
lastErr = err
|
||||||
|
}
|
||||||
|
processedAlerts = append(processedAlerts, info.alertRecord)
|
||||||
|
am.pendingAlerts.Delete(key)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return processedAlerts, lastErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForceExpirePendingAlerts sets all pending alerts to expire immediately (for testing)
|
||||||
|
func (am *AlertManager) ForceExpirePendingAlerts() {
|
||||||
|
now := time.Now()
|
||||||
|
am.pendingAlerts.Range(func(key, value any) bool {
|
||||||
|
info := value.(*alertInfo)
|
||||||
|
info.expireTime = now.Add(-time.Second) // Set to 1 second ago
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -39,6 +39,7 @@ type Stats struct {
|
|||||||
// TODO: remove other load fields in future release in favor of load avg array
|
// TODO: remove other load fields in future release in favor of load avg array
|
||||||
LoadAvg [3]float64 `json:"la,omitempty" cbor:"28,keyasint"`
|
LoadAvg [3]float64 `json:"la,omitempty" cbor:"28,keyasint"`
|
||||||
Battery [2]uint8 `json:"bat,omitzero" cbor:"29,keyasint,omitzero"` // [percent, charge state, current]
|
Battery [2]uint8 `json:"bat,omitzero" cbor:"29,keyasint,omitzero"` // [percent, charge state, current]
|
||||||
|
MaxMem float64 `json:"mm,omitempty" cbor:"30,keyasint,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GPUData struct {
|
type GPUData struct {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
colorReset = "\033[0m"
|
colorReset = "\033[0m"
|
||||||
ColorYellow = "\033[33m"
|
ColorYellow = "\033[33m"
|
||||||
colorGreen = "\033[32m"
|
ColorGreen = "\033[32m"
|
||||||
colorCyan = "\033[36m"
|
colorCyan = "\033[36m"
|
||||||
colorGray = "\033[90m"
|
colorGray = "\033[90m"
|
||||||
)
|
)
|
||||||
@@ -64,10 +64,19 @@ type Config struct {
|
|||||||
|
|
||||||
// The data directory to use when fetching and downloading the latest release.
|
// The data directory to use when fetching and downloading the latest release.
|
||||||
DataDir string
|
DataDir string
|
||||||
|
|
||||||
|
// UseMirror specifies whether to use the beszel.dev mirror instead of GitHub API.
|
||||||
|
// When false (default), always uses api.github.com. When true, uses gh.beszel.dev.
|
||||||
|
UseMirror bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type updater struct {
|
||||||
|
config Config
|
||||||
|
currentVersion string
|
||||||
}
|
}
|
||||||
|
|
||||||
func Update(config Config) (updated bool, err error) {
|
func Update(config Config) (updated bool, err error) {
|
||||||
p := &plugin{
|
p := &updater{
|
||||||
currentVersion: beszel.Version,
|
currentVersion: beszel.Version,
|
||||||
config: config,
|
config: config,
|
||||||
}
|
}
|
||||||
@@ -75,12 +84,7 @@ func Update(config Config) (updated bool, err error) {
|
|||||||
return p.update()
|
return p.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
type plugin struct {
|
func (p *updater) update() (updated bool, err error) {
|
||||||
config Config
|
|
||||||
currentVersion string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *plugin) update() (updated bool, err error) {
|
|
||||||
ColorPrint(ColorYellow, "Fetching release information...")
|
ColorPrint(ColorYellow, "Fetching release information...")
|
||||||
|
|
||||||
if p.config.DataDir == "" {
|
if p.config.DataDir == "" {
|
||||||
@@ -106,21 +110,19 @@ func (p *plugin) update() (updated bool, err error) {
|
|||||||
var latest *release
|
var latest *release
|
||||||
var useMirror bool
|
var useMirror bool
|
||||||
|
|
||||||
|
// Determine the API endpoint based on UseMirror flag
|
||||||
|
apiURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", p.config.Owner, p.config.Repo)
|
||||||
|
if p.config.UseMirror {
|
||||||
|
useMirror = true
|
||||||
|
apiURL = fmt.Sprintf("https://gh.beszel.dev/repos/%s/%s/releases/latest?api=true", p.config.Owner, p.config.Repo)
|
||||||
|
ColorPrint(ColorYellow, "Using mirror for update.")
|
||||||
|
}
|
||||||
|
|
||||||
latest, err = fetchLatestRelease(
|
latest, err = fetchLatestRelease(
|
||||||
p.config.Context,
|
p.config.Context,
|
||||||
p.config.HttpClient,
|
p.config.HttpClient,
|
||||||
fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", p.config.Owner, p.config.Repo),
|
apiURL,
|
||||||
)
|
)
|
||||||
// if the first fetch fails, try the beszel.dev API (fallback for China)
|
|
||||||
if err != nil {
|
|
||||||
ColorPrint(ColorYellow, "Failed to fetch release. Trying beszel.dev mirror...")
|
|
||||||
useMirror = true
|
|
||||||
latest, err = fetchLatestRelease(
|
|
||||||
p.config.Context,
|
|
||||||
p.config.HttpClient,
|
|
||||||
fmt.Sprintf("https://gh.beszel.dev/repos/%s/%s/releases/latest?api=true", p.config.Owner, p.config.Repo),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -129,7 +131,7 @@ func (p *plugin) update() (updated bool, err error) {
|
|||||||
newVersion := semver.MustParse(strings.TrimPrefix(latest.Tag, "v"))
|
newVersion := semver.MustParse(strings.TrimPrefix(latest.Tag, "v"))
|
||||||
|
|
||||||
if newVersion.LTE(currentVersion) {
|
if newVersion.LTE(currentVersion) {
|
||||||
ColorPrintf(colorGreen, "You already have the latest version %s.", p.currentVersion)
|
ColorPrintf(ColorGreen, "You already have the latest version %s.", p.currentVersion)
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,14 +211,11 @@ func (p *plugin) update() (updated bool, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ColorPrint(colorGray, "---")
|
ColorPrint(colorGray, "---")
|
||||||
ColorPrint(colorGreen, "Update completed successfully! You can start the executable as usual.")
|
ColorPrint(ColorGreen, "Update completed successfully!")
|
||||||
|
|
||||||
// print the release notes
|
// print the release notes
|
||||||
if latest.Body != "" {
|
if latest.Body != "" {
|
||||||
fmt.Print("\n")
|
fmt.Print("\n")
|
||||||
ColorPrintf(colorCyan, "Here is a list with some of the %s changes:", latest.Tag)
|
|
||||||
// remove the update command note to avoid "stuttering"
|
|
||||||
// (@todo consider moving to a config option)
|
|
||||||
releaseNotes := strings.TrimSpace(strings.Replace(latest.Body, "> _To update the prebuilt executable you can run `./"+p.config.ArchiveExecutable+" update`._", "", 1))
|
releaseNotes := strings.TrimSpace(strings.Replace(latest.Body, "> _To update the prebuilt executable you can run `./"+p.config.ArchiveExecutable+" update`._", "", 1))
|
||||||
ColorPrint(colorCyan, releaseNotes)
|
ColorPrint(colorCyan, releaseNotes)
|
||||||
fmt.Print("\n")
|
fmt.Print("\n")
|
||||||
|
|||||||
@@ -8,13 +8,10 @@ import (
|
|||||||
"beszel/internal/hub/systems"
|
"beszel/internal/hub/systems"
|
||||||
"beszel/internal/records"
|
"beszel/internal/records"
|
||||||
"beszel/internal/users"
|
"beszel/internal/users"
|
||||||
"beszel/site"
|
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@@ -115,6 +112,8 @@ func (h *Hub) initialize(e *core.ServeEvent) error {
|
|||||||
// set URL if BASE_URL env is set
|
// set URL if BASE_URL env is set
|
||||||
if h.appURL != "" {
|
if h.appURL != "" {
|
||||||
settings.Meta.AppURL = h.appURL
|
settings.Meta.AppURL = h.appURL
|
||||||
|
} else {
|
||||||
|
h.appURL = settings.Meta.AppURL
|
||||||
}
|
}
|
||||||
if err := e.App.Save(settings); err != nil {
|
if err := e.App.Save(settings); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -164,55 +163,6 @@ func (h *Hub) initialize(e *core.ServeEvent) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// startServer sets up the server for Beszel
|
|
||||||
func (h *Hub) startServer(se *core.ServeEvent) error {
|
|
||||||
// TODO: exclude dev server from production binary
|
|
||||||
switch h.IsDev() {
|
|
||||||
case true:
|
|
||||||
proxy := httputil.NewSingleHostReverseProxy(&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: "localhost:5173",
|
|
||||||
})
|
|
||||||
se.Router.GET("/{path...}", func(e *core.RequestEvent) error {
|
|
||||||
proxy.ServeHTTP(e.Response, e.Request)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
// parse app url
|
|
||||||
parsedURL, err := url.Parse(h.appURL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// fix base paths in html if using subpath
|
|
||||||
basePath := strings.TrimSuffix(parsedURL.Path, "/") + "/"
|
|
||||||
indexFile, _ := fs.ReadFile(site.DistDirFS, "index.html")
|
|
||||||
indexContent := strings.ReplaceAll(string(indexFile), "./", basePath)
|
|
||||||
indexContent = strings.Replace(indexContent, "{{V}}", beszel.Version, 1)
|
|
||||||
indexContent = strings.Replace(indexContent, "{{HUB_URL}}", h.appURL, 1)
|
|
||||||
// set up static asset serving
|
|
||||||
staticPaths := [2]string{"/static/", "/assets/"}
|
|
||||||
serveStatic := apis.Static(site.DistDirFS, false)
|
|
||||||
// get CSP configuration
|
|
||||||
csp, cspExists := GetEnv("CSP")
|
|
||||||
// add route
|
|
||||||
se.Router.GET("/{path...}", func(e *core.RequestEvent) error {
|
|
||||||
// serve static assets if path is in staticPaths
|
|
||||||
for i := range staticPaths {
|
|
||||||
if strings.Contains(e.Request.URL.Path, staticPaths[i]) {
|
|
||||||
e.Response.Header().Set("Cache-Control", "public, max-age=2592000")
|
|
||||||
return serveStatic(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if cspExists {
|
|
||||||
e.Response.Header().Del("X-Frame-Options")
|
|
||||||
e.Response.Header().Set("Content-Security-Policy", csp)
|
|
||||||
}
|
|
||||||
return e.HTML(http.StatusOK, indexContent)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// registerCronJobs sets up scheduled tasks
|
// registerCronJobs sets up scheduled tasks
|
||||||
func (h *Hub) registerCronJobs(_ *core.ServeEvent) error {
|
func (h *Hub) registerCronJobs(_ *core.ServeEvent) error {
|
||||||
// delete old system_stats and alerts_history records once every hour
|
// delete old system_stats and alerts_history records once every hour
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package hub_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
beszelTests "beszel/internal/tests"
|
beszelTests "beszel/internal/tests"
|
||||||
|
"beszel/migrations"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -534,6 +535,115 @@ func TestApiRoutesAuthentication(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFirstUserCreation(t *testing.T) {
|
||||||
|
t.Run("CreateUserEndpoint available when no users exist", func(t *testing.T) {
|
||||||
|
hub, _ := beszelTests.NewTestHub(t.TempDir())
|
||||||
|
defer hub.Cleanup()
|
||||||
|
|
||||||
|
hub.StartHub()
|
||||||
|
|
||||||
|
testAppFactoryExisting := func(t testing.TB) *pbTests.TestApp {
|
||||||
|
return hub.TestApp
|
||||||
|
}
|
||||||
|
|
||||||
|
scenarios := []beszelTests.ApiScenario{
|
||||||
|
{
|
||||||
|
Name: "POST /create-user - should be available when no users exist",
|
||||||
|
Method: http.MethodPost,
|
||||||
|
URL: "/api/beszel/create-user",
|
||||||
|
Body: jsonReader(map[string]any{
|
||||||
|
"email": "firstuser@example.com",
|
||||||
|
"password": "password123",
|
||||||
|
}),
|
||||||
|
ExpectedStatus: 200,
|
||||||
|
ExpectedContent: []string{"User created"},
|
||||||
|
TestAppFactory: testAppFactoryExisting,
|
||||||
|
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
|
||||||
|
userCount, err := hub.CountRecords("users")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Zero(t, userCount, "Should start with no users")
|
||||||
|
superusers, err := hub.FindAllRecords(core.CollectionNameSuperusers)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, 1, len(superusers), "Should start with one temporary superuser")
|
||||||
|
require.EqualValues(t, migrations.TempAdminEmail, superusers[0].GetString("email"), "Should have created one temporary superuser")
|
||||||
|
},
|
||||||
|
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
|
||||||
|
userCount, err := hub.CountRecords("users")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, 1, userCount, "Should have created one user")
|
||||||
|
superusers, err := hub.FindAllRecords(core.CollectionNameSuperusers)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, 1, len(superusers), "Should have created one superuser")
|
||||||
|
require.EqualValues(t, "firstuser@example.com", superusers[0].GetString("email"), "Should have created one superuser")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "POST /create-user - should not be available when users exist",
|
||||||
|
Method: http.MethodPost,
|
||||||
|
URL: "/api/beszel/create-user",
|
||||||
|
Body: jsonReader(map[string]any{
|
||||||
|
"email": "firstuser@example.com",
|
||||||
|
"password": "password123",
|
||||||
|
}),
|
||||||
|
ExpectedStatus: 404,
|
||||||
|
ExpectedContent: []string{"wasn't found"},
|
||||||
|
TestAppFactory: testAppFactoryExisting,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, scenario := range scenarios {
|
||||||
|
scenario.Test(t)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("CreateUserEndpoint not available when USER_EMAIL, USER_PASSWORD are set", func(t *testing.T) {
|
||||||
|
os.Setenv("BESZEL_HUB_USER_EMAIL", "me@example.com")
|
||||||
|
os.Setenv("BESZEL_HUB_USER_PASSWORD", "password123")
|
||||||
|
defer os.Unsetenv("BESZEL_HUB_USER_EMAIL")
|
||||||
|
defer os.Unsetenv("BESZEL_HUB_USER_PASSWORD")
|
||||||
|
|
||||||
|
hub, _ := beszelTests.NewTestHub(t.TempDir())
|
||||||
|
defer hub.Cleanup()
|
||||||
|
|
||||||
|
hub.StartHub()
|
||||||
|
|
||||||
|
testAppFactory := func(t testing.TB) *pbTests.TestApp {
|
||||||
|
return hub.TestApp
|
||||||
|
}
|
||||||
|
|
||||||
|
scenario := beszelTests.ApiScenario{
|
||||||
|
Name: "POST /create-user - should not be available when USER_EMAIL, USER_PASSWORD are set",
|
||||||
|
Method: http.MethodPost,
|
||||||
|
URL: "/api/beszel/create-user",
|
||||||
|
ExpectedStatus: 404,
|
||||||
|
ExpectedContent: []string{"wasn't found"},
|
||||||
|
TestAppFactory: testAppFactory,
|
||||||
|
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
|
||||||
|
users, err := hub.FindAllRecords("users")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, 1, len(users), "Should start with one user")
|
||||||
|
require.EqualValues(t, "me@example.com", users[0].GetString("email"), "Should have created one user")
|
||||||
|
superusers, err := hub.FindAllRecords(core.CollectionNameSuperusers)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, 1, len(superusers), "Should start with one superuser")
|
||||||
|
require.EqualValues(t, "me@example.com", superusers[0].GetString("email"), "Should have created one superuser")
|
||||||
|
},
|
||||||
|
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
|
||||||
|
users, err := hub.FindAllRecords("users")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, 1, len(users), "Should still have one user")
|
||||||
|
require.EqualValues(t, "me@example.com", users[0].GetString("email"), "Should have created one user")
|
||||||
|
superusers, err := hub.FindAllRecords(core.CollectionNameSuperusers)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, 1, len(superusers), "Should still have one superuser")
|
||||||
|
require.EqualValues(t, "me@example.com", superusers[0].GetString("email"), "Should have created one superuser")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
scenario.Test(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
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())
|
||||||
|
|||||||
79
beszel/internal/hub/server_development.go
Normal file
79
beszel/internal/hub/server_development.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
//go:build development
|
||||||
|
|
||||||
|
package hub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"beszel"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pocketbase/pocketbase/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Wraps http.RoundTripper to modify dev proxy HTML responses
|
||||||
|
type responseModifier struct {
|
||||||
|
transport http.RoundTripper
|
||||||
|
hub *Hub
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rm *responseModifier) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
resp, err := rm.transport.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
// Only modify HTML responses
|
||||||
|
contentType := resp.Header.Get("Content-Type")
|
||||||
|
if !strings.Contains(contentType, "text/html") {
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
// Create a new response with the modified body
|
||||||
|
modifiedBody := rm.modifyHTML(string(body))
|
||||||
|
resp.Body = io.NopCloser(strings.NewReader(modifiedBody))
|
||||||
|
resp.ContentLength = int64(len(modifiedBody))
|
||||||
|
resp.Header.Set("Content-Length", fmt.Sprintf("%d", len(modifiedBody)))
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rm *responseModifier) modifyHTML(html string) string {
|
||||||
|
parsedURL, err := url.Parse(rm.hub.appURL)
|
||||||
|
if err != nil {
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
// fix base paths in html if using subpath
|
||||||
|
basePath := strings.TrimSuffix(parsedURL.Path, "/") + "/"
|
||||||
|
html = strings.ReplaceAll(html, "./", basePath)
|
||||||
|
html = strings.Replace(html, "{{V}}", beszel.Version, 1)
|
||||||
|
html = strings.Replace(html, "{{HUB_URL}}", rm.hub.appURL, 1)
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
|
||||||
|
// startServer sets up the development server for Beszel
|
||||||
|
func (h *Hub) startServer(se *core.ServeEvent) error {
|
||||||
|
slog.Info("starting server", "appURL", h.appURL)
|
||||||
|
proxy := httputil.NewSingleHostReverseProxy(&url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: "localhost:5173",
|
||||||
|
})
|
||||||
|
|
||||||
|
proxy.Transport = &responseModifier{
|
||||||
|
transport: http.DefaultTransport,
|
||||||
|
hub: h,
|
||||||
|
}
|
||||||
|
|
||||||
|
se.Router.GET("/{path...}", func(e *core.RequestEvent) error {
|
||||||
|
proxy.ServeHTTP(e.Response, e.Request)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
51
beszel/internal/hub/server_production.go
Normal file
51
beszel/internal/hub/server_production.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
//go:build !development
|
||||||
|
|
||||||
|
package hub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"beszel"
|
||||||
|
"beszel/site"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pocketbase/pocketbase/apis"
|
||||||
|
"github.com/pocketbase/pocketbase/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
// startServer sets up the production server for Beszel
|
||||||
|
func (h *Hub) startServer(se *core.ServeEvent) error {
|
||||||
|
// parse app url
|
||||||
|
parsedURL, err := url.Parse(h.appURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// fix base paths in html if using subpath
|
||||||
|
basePath := strings.TrimSuffix(parsedURL.Path, "/") + "/"
|
||||||
|
indexFile, _ := fs.ReadFile(site.DistDirFS, "index.html")
|
||||||
|
html := strings.ReplaceAll(string(indexFile), "./", basePath)
|
||||||
|
html = strings.Replace(html, "{{V}}", beszel.Version, 1)
|
||||||
|
html = strings.Replace(html, "{{HUB_URL}}", h.appURL, 1)
|
||||||
|
// set up static asset serving
|
||||||
|
staticPaths := [2]string{"/static/", "/assets/"}
|
||||||
|
serveStatic := apis.Static(site.DistDirFS, false)
|
||||||
|
// get CSP configuration
|
||||||
|
csp, cspExists := GetEnv("CSP")
|
||||||
|
// add route
|
||||||
|
se.Router.GET("/{path...}", func(e *core.RequestEvent) error {
|
||||||
|
// serve static assets if path is in staticPaths
|
||||||
|
for i := range staticPaths {
|
||||||
|
if strings.Contains(e.Request.URL.Path, staticPaths[i]) {
|
||||||
|
e.Response.Header().Set("Cache-Control", "public, max-age=2592000")
|
||||||
|
return serveStatic(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cspExists {
|
||||||
|
e.Response.Header().Del("X-Frame-Options")
|
||||||
|
e.Response.Header().Set("Content-Security-Policy", csp)
|
||||||
|
}
|
||||||
|
return e.HTML(http.StatusOK, html)
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -100,3 +100,10 @@ func (sm *SystemManager) SetSystemStatusInDB(systemID string, status string) boo
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TESTING ONLY: RemoveAllSystems removes all systems from the store
|
||||||
|
func (sm *SystemManager) RemoveAllSystems() {
|
||||||
|
for _, system := range sm.systems.GetAll() {
|
||||||
|
sm.RemoveSystem(system.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Update updates beszel to the latest version
|
// Update updates beszel to the latest version
|
||||||
func Update(_ *cobra.Command, _ []string) {
|
func Update(cmd *cobra.Command, _ []string) {
|
||||||
dataDir := os.TempDir()
|
dataDir := os.TempDir()
|
||||||
|
|
||||||
// set dataDir to ./beszel_data if it exists
|
// set dataDir to ./beszel_data if it exists
|
||||||
@@ -19,9 +19,13 @@ func Update(_ *cobra.Command, _ []string) {
|
|||||||
dataDir = "./beszel_data"
|
dataDir = "./beszel_data"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if china-mirrors flag is set
|
||||||
|
useMirror, _ := cmd.Flags().GetBool("china-mirrors")
|
||||||
|
|
||||||
updated, err := ghupdate.Update(ghupdate.Config{
|
updated, err := ghupdate.Update(ghupdate.Config{
|
||||||
ArchiveExecutable: "beszel",
|
ArchiveExecutable: "beszel",
|
||||||
DataDir: dataDir,
|
DataDir: dataDir,
|
||||||
|
UseMirror: useMirror,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@@ -30,6 +34,14 @@ func Update(_ *cobra.Command, _ []string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make sure the file is executable
|
||||||
|
exePath, err := os.Executable()
|
||||||
|
if err == nil {
|
||||||
|
if err := os.Chmod(exePath, 0755); err != nil {
|
||||||
|
fmt.Printf("Warning: failed to set executable permissions: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Try to restart the service if it's running
|
// Try to restart the service if it's running
|
||||||
restartService()
|
restartService()
|
||||||
}
|
}
|
||||||
@@ -41,13 +53,13 @@ func restartService() {
|
|||||||
// Check if beszel service exists and is active
|
// Check if beszel service exists and is active
|
||||||
cmd := exec.Command("systemctl", "is-active", "beszel.service")
|
cmd := exec.Command("systemctl", "is-active", "beszel.service")
|
||||||
if err := cmd.Run(); err == nil {
|
if err := cmd.Run(); err == nil {
|
||||||
fmt.Println("Restarting beszel service...")
|
ghupdate.ColorPrint(ghupdate.ColorYellow, "Restarting beszel service...")
|
||||||
restartCmd := exec.Command("systemctl", "restart", "beszel.service")
|
restartCmd := exec.Command("systemctl", "restart", "beszel.service")
|
||||||
if err := restartCmd.Run(); err != nil {
|
if err := restartCmd.Run(); err != nil {
|
||||||
fmt.Printf("Warning: Failed to restart service: %v\n", err)
|
ghupdate.ColorPrintf(ghupdate.ColorYellow, "Warning: Failed to restart service: %v\n", err)
|
||||||
fmt.Println("Please restart the service manually: sudo systemctl restart beszel")
|
ghupdate.ColorPrint(ghupdate.ColorYellow, "Please restart the service manually: sudo systemctl restart beszel")
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("Service restarted successfully")
|
ghupdate.ColorPrint(ghupdate.ColorGreen, "Service restarted successfully")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -57,17 +69,17 @@ func restartService() {
|
|||||||
if _, err := exec.LookPath("rc-service"); err == nil {
|
if _, err := exec.LookPath("rc-service"); err == nil {
|
||||||
cmd := exec.Command("rc-service", "beszel", "status")
|
cmd := exec.Command("rc-service", "beszel", "status")
|
||||||
if err := cmd.Run(); err == nil {
|
if err := cmd.Run(); err == nil {
|
||||||
fmt.Println("Restarting beszel service...")
|
ghupdate.ColorPrint(ghupdate.ColorYellow, "Restarting beszel service...")
|
||||||
restartCmd := exec.Command("rc-service", "beszel", "restart")
|
restartCmd := exec.Command("rc-service", "beszel", "restart")
|
||||||
if err := restartCmd.Run(); err != nil {
|
if err := restartCmd.Run(); err != nil {
|
||||||
fmt.Printf("Warning: Failed to restart service: %v\n", err)
|
ghupdate.ColorPrintf(ghupdate.ColorYellow, "Warning: Failed to restart service: %v\n", err)
|
||||||
fmt.Println("Please restart the service manually: sudo rc-service beszel restart")
|
ghupdate.ColorPrint(ghupdate.ColorYellow, "Please restart the service manually: sudo rc-service beszel restart")
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("Service restarted successfully")
|
ghupdate.ColorPrint(ghupdate.ColorGreen, "Service restarted successfully")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Note: Service restart not attempted. If running as a service, restart manually.")
|
ghupdate.ColorPrint(ghupdate.ColorYellow, "Service restart not attempted. If running as a service, restart manually.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,6 +214,7 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
|
|||||||
sum.Battery[1] = stats.Battery[1]
|
sum.Battery[1] = stats.Battery[1]
|
||||||
// Set peak values
|
// Set peak values
|
||||||
sum.MaxCpu = max(sum.MaxCpu, stats.MaxCpu, stats.Cpu)
|
sum.MaxCpu = max(sum.MaxCpu, stats.MaxCpu, stats.Cpu)
|
||||||
|
sum.MaxMem = max(sum.MaxMem, stats.MaxMem, stats.MemUsed)
|
||||||
sum.MaxNetworkSent = max(sum.MaxNetworkSent, stats.MaxNetworkSent, stats.NetworkSent)
|
sum.MaxNetworkSent = max(sum.MaxNetworkSent, stats.MaxNetworkSent, stats.NetworkSent)
|
||||||
sum.MaxNetworkRecv = max(sum.MaxNetworkRecv, stats.MaxNetworkRecv, stats.NetworkRecv)
|
sum.MaxNetworkRecv = max(sum.MaxNetworkRecv, stats.MaxNetworkRecv, stats.NetworkRecv)
|
||||||
sum.MaxDiskReadPs = max(sum.MaxDiskReadPs, stats.MaxDiskReadPs, stats.DiskReadPs)
|
sum.MaxDiskReadPs = max(sum.MaxDiskReadPs, stats.MaxDiskReadPs, stats.DiskReadPs)
|
||||||
|
|||||||
@@ -96,3 +96,31 @@ func ClearCollection(t testing.TB, app core.App, collectionName string) error {
|
|||||||
assert.EqualValues(t, recordCount, 0, "should have 0 records after clearing")
|
assert.EqualValues(t, recordCount, 0, "should have 0 records after clearing")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *TestHub) Cleanup() {
|
||||||
|
h.GetAlertManager().StopWorker()
|
||||||
|
h.GetSystemManager().RemoveAllSystems()
|
||||||
|
h.TestApp.Cleanup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateSystems(app core.App, count int, userId string, status string) ([]*core.Record, error) {
|
||||||
|
systems := make([]*core.Record, 0, count)
|
||||||
|
for i := range count {
|
||||||
|
system, err := CreateRecord(app, "systems", map[string]any{
|
||||||
|
"name": fmt.Sprintf("test-system-%d", i),
|
||||||
|
"host": fmt.Sprintf("127.0.0.%d", i),
|
||||||
|
"port": "33914",
|
||||||
|
"users": []string{userId},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
system.Set("status", status)
|
||||||
|
err = app.SaveNoValidate(system)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
systems = append(systems, system)
|
||||||
|
}
|
||||||
|
return systems, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/pocketbase/pocketbase/core"
|
"github.com/pocketbase/pocketbase/core"
|
||||||
m "github.com/pocketbase/pocketbase/migrations"
|
m "github.com/pocketbase/pocketbase/migrations"
|
||||||
)
|
)
|
||||||
@@ -19,11 +21,51 @@ func init() {
|
|||||||
if err := app.Save(settings); err != nil {
|
if err := app.Save(settings); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// create superuser
|
// create superuser
|
||||||
collection, _ := app.FindCollectionByNameOrId(core.CollectionNameSuperusers)
|
superuserCollection, _ := app.FindCollectionByNameOrId(core.CollectionNameSuperusers)
|
||||||
user := core.NewRecord(collection)
|
superUser := core.NewRecord(superuserCollection)
|
||||||
user.SetEmail(TempAdminEmail)
|
|
||||||
user.SetRandomPassword()
|
// set email
|
||||||
return app.Save(user)
|
email, _ := GetEnv("USER_EMAIL")
|
||||||
|
password, _ := GetEnv("USER_PASSWORD")
|
||||||
|
didProvideUserDetails := email != "" && password != ""
|
||||||
|
|
||||||
|
// set superuser email
|
||||||
|
if email == "" {
|
||||||
|
email = TempAdminEmail
|
||||||
|
}
|
||||||
|
superUser.SetEmail(email)
|
||||||
|
|
||||||
|
// set superuser password
|
||||||
|
if password != "" {
|
||||||
|
superUser.SetPassword(password)
|
||||||
|
} else {
|
||||||
|
superUser.SetRandomPassword()
|
||||||
|
}
|
||||||
|
|
||||||
|
// if user details are provided, we create a regular user as well
|
||||||
|
if didProvideUserDetails {
|
||||||
|
usersCollection, _ := app.FindCollectionByNameOrId("users")
|
||||||
|
user := core.NewRecord(usersCollection)
|
||||||
|
user.SetEmail(email)
|
||||||
|
user.SetPassword(password)
|
||||||
|
user.SetVerified(true)
|
||||||
|
err := app.Save(user)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Save(superUser)
|
||||||
}, nil)
|
}, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetEnv retrieves an environment variable with a "BESZEL_HUB_" prefix, or falls back to the unprefixed key.
|
||||||
|
func GetEnv(key string) (value string, exists bool) {
|
||||||
|
if value, exists = os.LookupEnv("BESZEL_HUB_" + key); exists {
|
||||||
|
return value, exists
|
||||||
|
}
|
||||||
|
// Fallback to the old unprefixed key
|
||||||
|
return os.LookupEnv(key)
|
||||||
|
}
|
||||||
|
|||||||
Binary file not shown.
32
beszel/site/package-lock.json
generated
32
beszel/site/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "beszel",
|
"name": "beszel",
|
||||||
"version": "0.12.4",
|
"version": "0.12.7",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "beszel",
|
"name": "beszel",
|
||||||
"version": "0.12.4",
|
"version": "0.12.7",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@henrygd/queue": "^1.0.7",
|
"@henrygd/queue": "^1.0.7",
|
||||||
"@henrygd/semaphore": "^0.0.2",
|
"@henrygd/semaphore": "^0.0.2",
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
"@radix-ui/react-toast": "^1.2.15",
|
"@radix-ui/react-toast": "^1.2.15",
|
||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
|
"@tanstack/react-virtual": "^3.13.12",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
@@ -3130,6 +3131,23 @@
|
|||||||
"react-dom": ">=16.8"
|
"react-dom": ">=16.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tanstack/react-virtual": {
|
||||||
|
"version": "3.13.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.12.tgz",
|
||||||
|
"integrity": "sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@tanstack/virtual-core": "3.13.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tanstack/table-core": {
|
"node_modules/@tanstack/table-core": {
|
||||||
"version": "8.21.3",
|
"version": "8.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz",
|
||||||
@@ -3143,6 +3161,16 @@
|
|||||||
"url": "https://github.com/sponsors/tannerlinsley"
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tanstack/virtual-core": {
|
||||||
|
"version": "3.13.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz",
|
||||||
|
"integrity": "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/bun": {
|
"node_modules/@types/bun": {
|
||||||
"version": "1.2.20",
|
"version": "1.2.20",
|
||||||
"resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.2.20.tgz",
|
"resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.2.20.tgz",
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "beszel",
|
"name": "beszel",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.12.4",
|
"version": "0.12.7",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite --host",
|
||||||
"build": "lingui extract --overwrite && lingui compile && vite build",
|
"build": "lingui extract --overwrite && lingui compile && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"sync": "lingui extract --overwrite && lingui compile",
|
"sync": "lingui extract --overwrite && lingui compile",
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
"@radix-ui/react-toast": "^1.2.15",
|
"@radix-ui/react-toast": "^1.2.15",
|
||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
|
"@tanstack/react-virtual": "^3.13.12",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
|
|||||||
@@ -13,8 +13,9 @@ import {
|
|||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
import { $publicKey, pb } from "@/lib/stores"
|
import { $publicKey } from "@/lib/stores"
|
||||||
import { cn, generateToken, isReadOnlyUser, tokenMap, useLocalStorage } from "@/lib/utils"
|
import { cn, generateToken, tokenMap, useBrowserStorage } from "@/lib/utils"
|
||||||
|
import { pb, isReadOnlyUser } from "@/lib/api"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import { ChevronDownIcon, ExternalLinkIcon, PlusIcon } from "lucide-react"
|
import { ChevronDownIcon, ExternalLinkIcon, PlusIcon } from "lucide-react"
|
||||||
import { memo, useEffect, useRef, useState } from "react"
|
import { memo, useEffect, useRef, useState } from "react"
|
||||||
@@ -76,7 +77,7 @@ export const SystemDialog = ({ setOpen, system }: { setOpen: (open: boolean) =>
|
|||||||
const port = useRef<HTMLInputElement>(null)
|
const port = useRef<HTMLInputElement>(null)
|
||||||
const [hostValue, setHostValue] = useState(system?.host ?? "")
|
const [hostValue, setHostValue] = useState(system?.host ?? "")
|
||||||
const isUnixSocket = hostValue.startsWith("/")
|
const isUnixSocket = hostValue.startsWith("/")
|
||||||
const [tab, setTab] = useLocalStorage("as-tab", "docker")
|
const [tab, setTab] = useBrowserStorage("as-tab", "docker")
|
||||||
const [token, setToken] = useState(system?.token ?? "")
|
const [token, setToken] = useState(system?.token ?? "")
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -96,7 +97,7 @@ export const SystemDialog = ({ setOpen, system }: { setOpen: (open: boolean) =>
|
|||||||
tokenMap.set(system.id, token)
|
tokenMap.set(system.id, token)
|
||||||
setToken(token)
|
setToken(token)
|
||||||
})()
|
})()
|
||||||
}, [system?.id])
|
}, [system?.id, nextSystemToken])
|
||||||
|
|
||||||
async function handleSubmit(e: SubmitEvent) {
|
async function handleSubmit(e: SubmitEvent) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -132,7 +133,7 @@ export const SystemDialog = ({ setOpen, system }: { setOpen: (open: boolean) =>
|
|||||||
>
|
>
|
||||||
<Tabs defaultValue={tab} onValueChange={setTab}>
|
<Tabs defaultValue={tab} onValueChange={setTab}>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="mb-2">
|
<DialogTitle className="mb-2 max-w-100 truncate pr-8">
|
||||||
{system ? `${t`Edit`} ${system?.name}` : <Trans>Add New System</Trans>}
|
{system ? `${t`Edit`} ${system?.name}` : <Trans>Add New System</Trans>}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<TabsList className="grid w-full grid-cols-2">
|
<TabsList className="grid w-full grid-cols-2">
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ export const alertsHistoryColumns: ColumnDef<AlertsHistoryRecord>[] = [
|
|||||||
<Trans>System</Trans>
|
<Trans>System</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
),
|
),
|
||||||
cell: ({ row }) => <span className="ps-2">{row.original.expand?.system?.name || row.original.system}</span>,
|
cell: ({ row }) => (
|
||||||
|
<div className="ps-2 max-w-60 truncate">{row.original.expand?.system?.name || row.original.system}</div>
|
||||||
|
),
|
||||||
filterFn: (row, _, filterValue) => {
|
filterFn: (row, _, filterValue) => {
|
||||||
const display = row.original.expand?.system?.name || row.original.system || ""
|
const display = row.original.expand?.system?.name || row.original.system || ""
|
||||||
return display.toLowerCase().includes(filterValue.toLowerCase())
|
return display.toLowerCase().includes(filterValue.toLowerCase())
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { t } from "@lingui/core/macro"
|
import { t } from "@lingui/core/macro"
|
||||||
import { Trans, Plural } from "@lingui/react/macro"
|
import { Trans, Plural } from "@lingui/react/macro"
|
||||||
import { $alerts, $systems, pb } from "@/lib/stores"
|
import { $alerts, $systems } from "@/lib/stores"
|
||||||
import { cn, debounce } from "@/lib/utils"
|
import { cn, debounce } from "@/lib/utils"
|
||||||
import { alertInfo } from "@/lib/alerts"
|
import { alertInfo } from "@/lib/alerts"
|
||||||
import { Switch } from "@/components/ui/switch"
|
import { Switch } from "@/components/ui/switch"
|
||||||
@@ -15,6 +15,7 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
|
|||||||
import { ServerIcon, GlobeIcon } from "lucide-react"
|
import { ServerIcon, GlobeIcon } from "lucide-react"
|
||||||
import { $router, Link } from "@/components/router"
|
import { $router, Link } from "@/components/router"
|
||||||
import { DialogHeader } from "@/components/ui/dialog"
|
import { DialogHeader } from "@/components/ui/dialog"
|
||||||
|
import { pb } from "@/lib/api"
|
||||||
|
|
||||||
const Slider = lazy(() => import("@/components/ui/slider"))
|
const Slider = lazy(() => import("@/components/ui/slider"))
|
||||||
|
|
||||||
@@ -95,7 +96,7 @@ export const AlertDialogContent = memo(function AlertDialogContent({ system }: {
|
|||||||
<TabsList className="mb-1 -mt-0.5">
|
<TabsList className="mb-1 -mt-0.5">
|
||||||
<TabsTrigger value="system">
|
<TabsTrigger value="system">
|
||||||
<ServerIcon className="me-2 h-3.5 w-3.5" />
|
<ServerIcon className="me-2 h-3.5 w-3.5" />
|
||||||
{system.name}
|
<span className="truncate max-w-60">{system.name}</span>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="global">
|
<TabsTrigger value="global">
|
||||||
<GlobeIcon className="me-1.5 h-3.5 w-3.5" />
|
<GlobeIcon className="me-1.5 h-3.5 w-3.5" />
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
||||||
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
||||||
import { useYAxisWidth, cn, formatShortDate, chartMargin } from "@/lib/utils"
|
import { cn, formatShortDate, chartMargin } from "@/lib/utils"
|
||||||
|
import { useYAxisWidth } from "./hooks"
|
||||||
import { ChartData, SystemStatsRecord } from "@/types"
|
import { ChartData, SystemStatsRecord } from "@/types"
|
||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
|
|
||||||
export type DataPoint = {
|
export type DataPoint = {
|
||||||
label: string
|
label: string
|
||||||
dataKey: (data: SystemStatsRecord) => number | undefined
|
dataKey: (data: SystemStatsRecord) => number | undefined
|
||||||
color: string
|
color: number | string
|
||||||
opacity: number
|
opacity: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,26 @@
|
|||||||
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
||||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
||||||
import { memo, useMemo } from "react"
|
import { memo, useMemo } from "react"
|
||||||
import { useYAxisWidth, cn, formatShortDate, chartMargin, toFixedFloat, formatBytes, decimalString } from "@/lib/utils"
|
import { cn, formatShortDate, chartMargin, toFixedFloat, formatBytes, decimalString } from "@/lib/utils"
|
||||||
// import Spinner from '../spinner'
|
// import Spinner from '../spinner'
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import { $containerFilter, $userSettings } from "@/lib/stores"
|
import { $containerFilter, $userSettings } from "@/lib/stores"
|
||||||
import { ChartData } from "@/types"
|
import { ChartData } from "@/types"
|
||||||
import { Separator } from "../ui/separator"
|
import { Separator } from "../ui/separator"
|
||||||
import { ChartType, Unit } from "@/lib/enums"
|
import { ChartType, Unit } from "@/lib/enums"
|
||||||
|
import { useYAxisWidth } from "./hooks"
|
||||||
|
|
||||||
export default memo(function ContainerChart({
|
export default memo(function ContainerChart({
|
||||||
dataKey,
|
dataKey,
|
||||||
chartData,
|
chartData,
|
||||||
chartType,
|
chartType,
|
||||||
|
chartConfig,
|
||||||
unit = "%",
|
unit = "%",
|
||||||
}: {
|
}: {
|
||||||
dataKey: string
|
dataKey: string
|
||||||
chartData: ChartData
|
chartData: ChartData
|
||||||
chartType: ChartType
|
chartType: ChartType
|
||||||
|
chartConfig: ChartConfig
|
||||||
unit?: string
|
unit?: string
|
||||||
}) {
|
}) {
|
||||||
const filter = useStore($containerFilter)
|
const filter = useStore($containerFilter)
|
||||||
@@ -28,40 +31,6 @@ export default memo(function ContainerChart({
|
|||||||
|
|
||||||
const isNetChart = chartType === ChartType.Network
|
const isNetChart = chartType === ChartType.Network
|
||||||
|
|
||||||
const chartConfig = useMemo(() => {
|
|
||||||
const config = {} as Record<string, { label: string; color: string }>
|
|
||||||
const totalUsage = new Map<string, number>()
|
|
||||||
|
|
||||||
// calculate total usage of each container
|
|
||||||
for (const stats of containerData) {
|
|
||||||
for (const key in stats) {
|
|
||||||
if (!key || key === "created") continue
|
|
||||||
|
|
||||||
const currentTotal = totalUsage.get(key) ?? 0
|
|
||||||
const increment = isNetChart
|
|
||||||
? (stats[key]?.nr ?? 0) + (stats[key]?.ns ?? 0)
|
|
||||||
: // @ts-ignore
|
|
||||||
stats[key]?.[dataKey] ?? 0
|
|
||||||
|
|
||||||
totalUsage.set(key, currentTotal + increment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort keys and generate colors based on usage
|
|
||||||
const sortedEntries = Array.from(totalUsage.entries()).sort(([, a], [, b]) => b - a)
|
|
||||||
|
|
||||||
const length = sortedEntries.length
|
|
||||||
sortedEntries.forEach(([key], i) => {
|
|
||||||
const hue = ((i * 360) / length) % 360
|
|
||||||
config[key] = {
|
|
||||||
label: key,
|
|
||||||
color: `hsl(${hue}, 60%, 55%)`,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return config satisfies ChartConfig
|
|
||||||
}, [chartData])
|
|
||||||
|
|
||||||
const { toolTipFormatter, dataFunction, tickFormatter } = useMemo(() => {
|
const { toolTipFormatter, dataFunction, tickFormatter } = useMemo(() => {
|
||||||
const obj = {} as {
|
const obj = {} as {
|
||||||
toolTipFormatter: (item: any, key: string) => React.ReactNode | string
|
toolTipFormatter: (item: any, key: string) => React.ReactNode | string
|
||||||
@@ -119,7 +88,14 @@ export default memo(function ContainerChart({
|
|||||||
return obj
|
return obj
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const filterLower = filter?.toLowerCase()
|
// Filter with set lookup
|
||||||
|
const filteredKeys = useMemo(() => {
|
||||||
|
if (!filter) {
|
||||||
|
return new Set<string>()
|
||||||
|
}
|
||||||
|
const filterLower = filter.toLowerCase()
|
||||||
|
return new Set(Object.keys(chartConfig).filter((key) => !key.toLowerCase().includes(filterLower)))
|
||||||
|
}, [chartConfig, filter])
|
||||||
|
|
||||||
// console.log('rendered at', new Date())
|
// console.log('rendered at', new Date())
|
||||||
|
|
||||||
@@ -162,9 +138,9 @@ export default memo(function ContainerChart({
|
|||||||
content={<ChartTooltipContent filter={filter} contentFormatter={toolTipFormatter} />}
|
content={<ChartTooltipContent filter={filter} contentFormatter={toolTipFormatter} />}
|
||||||
/>
|
/>
|
||||||
{Object.keys(chartConfig).map((key) => {
|
{Object.keys(chartConfig).map((key) => {
|
||||||
const filtered = filterLower && !key.toLowerCase().includes(filterLower)
|
const filtered = filteredKeys.has(key)
|
||||||
let fillOpacity = filtered ? 0.05 : 0.4
|
const fillOpacity = filtered ? 0.05 : 0.4
|
||||||
let strokeOpacity = filtered ? 0.1 : 1
|
const strokeOpacity = filtered ? 0.1 : 1
|
||||||
return (
|
return (
|
||||||
<Area
|
<Area
|
||||||
key={key}
|
key={key}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
||||||
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
||||||
import { useYAxisWidth, cn, formatShortDate, decimalString, chartMargin, formatBytes, toFixedFloat } from "@/lib/utils"
|
import { cn, formatShortDate, decimalString, chartMargin, formatBytes, toFixedFloat } from "@/lib/utils"
|
||||||
import { ChartData } from "@/types"
|
import { ChartData } from "@/types"
|
||||||
import { memo } from "react"
|
import { memo } from "react"
|
||||||
import { useLingui } from "@lingui/react/macro"
|
import { useLingui } from "@lingui/react/macro"
|
||||||
import { Unit } from "@/lib/enums"
|
import { Unit } from "@/lib/enums"
|
||||||
|
import { useYAxisWidth } from "./hooks"
|
||||||
|
|
||||||
export default memo(function DiskChart({
|
export default memo(function DiskChart({
|
||||||
dataKey,
|
dataKey,
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ import {
|
|||||||
ChartTooltipContent,
|
ChartTooltipContent,
|
||||||
xAxis,
|
xAxis,
|
||||||
} from "@/components/ui/chart"
|
} from "@/components/ui/chart"
|
||||||
import { useYAxisWidth, cn, formatShortDate, toFixedFloat, decimalString, chartMargin } from "@/lib/utils"
|
import { cn, formatShortDate, toFixedFloat, decimalString, chartMargin } from "@/lib/utils"
|
||||||
import { ChartData } from "@/types"
|
import { ChartData } from "@/types"
|
||||||
import { memo, useMemo } from "react"
|
import { memo, useMemo } from "react"
|
||||||
|
import { useYAxisWidth } from "./hooks"
|
||||||
|
|
||||||
export default memo(function GpuPowerChart({ chartData }: { chartData: ChartData }) {
|
export default memo(function GpuPowerChart({ chartData }: { chartData: ChartData }) {
|
||||||
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
||||||
|
|||||||
107
beszel/site/src/components/charts/hooks.ts
Normal file
107
beszel/site/src/components/charts/hooks.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import { useMemo, useState } from "react"
|
||||||
|
import { ChartConfig } from "@/components/ui/chart"
|
||||||
|
import { ChartData } from "@/types"
|
||||||
|
|
||||||
|
/** Chart configurations for CPU, memory, and network usage charts */
|
||||||
|
export interface ContainerChartConfigs {
|
||||||
|
cpu: ChartConfig
|
||||||
|
memory: ChartConfig
|
||||||
|
network: ChartConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates chart configurations for container metrics visualization
|
||||||
|
* @param containerData - Array of container statistics data points
|
||||||
|
* @returns Chart configurations for CPU, memory, and network metrics
|
||||||
|
*/
|
||||||
|
export function useContainerChartConfigs(containerData: ChartData["containerData"]): ContainerChartConfigs {
|
||||||
|
return useMemo(() => {
|
||||||
|
const configs = {
|
||||||
|
cpu: {} as ChartConfig,
|
||||||
|
memory: {} as ChartConfig,
|
||||||
|
network: {} as ChartConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregate usage metrics for each container
|
||||||
|
const totalUsage = {
|
||||||
|
cpu: new Map<string, number>(),
|
||||||
|
memory: new Map<string, number>(),
|
||||||
|
network: new Map<string, number>(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each data point to calculate totals
|
||||||
|
for (let i = 0; i < containerData.length; i++) {
|
||||||
|
const stats = containerData[i]
|
||||||
|
const containerNames = Object.keys(stats)
|
||||||
|
|
||||||
|
for (let j = 0; j < containerNames.length; j++) {
|
||||||
|
const containerName = containerNames[j]
|
||||||
|
// Skip metadata field
|
||||||
|
if (containerName === "created") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const containerStats = stats[containerName]
|
||||||
|
if (!containerStats) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accumulate metrics for CPU, memory, and network
|
||||||
|
const currentCpu = totalUsage.cpu.get(containerName) ?? 0
|
||||||
|
const currentMemory = totalUsage.memory.get(containerName) ?? 0
|
||||||
|
const currentNetwork = totalUsage.network.get(containerName) ?? 0
|
||||||
|
|
||||||
|
totalUsage.cpu.set(containerName, currentCpu + (containerStats.c ?? 0))
|
||||||
|
totalUsage.memory.set(containerName, currentMemory + (containerStats.m ?? 0))
|
||||||
|
totalUsage.network.set(containerName, currentNetwork + (containerStats.nr ?? 0) + (containerStats.ns ?? 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate chart configurations for each metric type
|
||||||
|
Object.entries(totalUsage).forEach(([chartType, usageMap]) => {
|
||||||
|
const sortedContainers = Array.from(usageMap.entries()).sort(([, a], [, b]) => b - a)
|
||||||
|
const chartConfig = {} as Record<string, { label: string; color: string }>
|
||||||
|
const count = sortedContainers.length
|
||||||
|
|
||||||
|
// Generate colors for each container
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const [containerName] = sortedContainers[i]
|
||||||
|
const hue = ((i * 360) / count) % 360
|
||||||
|
chartConfig[containerName] = {
|
||||||
|
label: containerName,
|
||||||
|
color: `hsl(${hue}, 60%, 55%)`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configs[chartType as keyof typeof configs] = chartConfig
|
||||||
|
})
|
||||||
|
|
||||||
|
return configs
|
||||||
|
}, [containerData])
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the correct width of the y axis in recharts based on the longest label */
|
||||||
|
export function useYAxisWidth() {
|
||||||
|
const [yAxisWidth, setYAxisWidth] = useState(0)
|
||||||
|
let maxChars = 0
|
||||||
|
let timeout: ReturnType<typeof setTimeout>
|
||||||
|
function updateYAxisWidth(str: string) {
|
||||||
|
if (str.length > maxChars) {
|
||||||
|
maxChars = str.length
|
||||||
|
const div = document.createElement("div")
|
||||||
|
div.className = "text-xs tabular-nums tracking-tighter table sr-only"
|
||||||
|
div.innerHTML = str
|
||||||
|
clearTimeout(timeout)
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
document.body.appendChild(div)
|
||||||
|
const width = div.offsetWidth + 24
|
||||||
|
if (width > yAxisWidth) {
|
||||||
|
setYAxisWidth(div.offsetWidth + 24)
|
||||||
|
}
|
||||||
|
document.body.removeChild(div)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
return { yAxisWidth, updateYAxisWidth }
|
||||||
|
}
|
||||||
@@ -8,10 +8,11 @@ import {
|
|||||||
ChartTooltipContent,
|
ChartTooltipContent,
|
||||||
xAxis,
|
xAxis,
|
||||||
} from "@/components/ui/chart"
|
} from "@/components/ui/chart"
|
||||||
import { useYAxisWidth, cn, formatShortDate, toFixedFloat, decimalString, chartMargin } from "@/lib/utils"
|
import { cn, formatShortDate, toFixedFloat, decimalString, chartMargin } from "@/lib/utils"
|
||||||
import { ChartData, SystemStats } from "@/types"
|
import { ChartData, SystemStats } from "@/types"
|
||||||
import { memo } from "react"
|
import { memo } from "react"
|
||||||
import { t } from "@lingui/core/macro"
|
import { t } from "@lingui/core/macro"
|
||||||
|
import { useYAxisWidth } from "./hooks"
|
||||||
|
|
||||||
export default memo(function LoadAverageChart({ chartData }: { chartData: ChartData }) {
|
export default memo(function LoadAverageChart({ chartData }: { chartData: ChartData }) {
|
||||||
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
||||||
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
||||||
import { useYAxisWidth, cn, decimalString, formatShortDate, chartMargin, formatBytes, toFixedFloat } from "@/lib/utils"
|
import { cn, decimalString, formatShortDate, chartMargin, formatBytes, toFixedFloat } from "@/lib/utils"
|
||||||
import { memo } from "react"
|
import { memo } from "react"
|
||||||
import { ChartData } from "@/types"
|
import { ChartData } from "@/types"
|
||||||
import { useLingui } from "@lingui/react/macro"
|
import { useLingui } from "@lingui/react/macro"
|
||||||
import { Unit } from "@/lib/enums"
|
import { Unit } from "@/lib/enums"
|
||||||
|
import { useYAxisWidth } from "./hooks"
|
||||||
|
|
||||||
export default memo(function MemChart({ chartData }: { chartData: ChartData }) {
|
export default memo(function MemChart({ chartData, showMax }: { chartData: ChartData; showMax: boolean }) {
|
||||||
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
||||||
const { t } = useLingui()
|
const { t } = useLingui()
|
||||||
|
|
||||||
@@ -66,7 +67,7 @@ export default memo(function MemChart({ chartData }: { chartData: ChartData }) {
|
|||||||
<Area
|
<Area
|
||||||
name={t`Used`}
|
name={t`Used`}
|
||||||
order={3}
|
order={3}
|
||||||
dataKey="stats.mu"
|
dataKey={({ stats }) => (showMax ? stats?.mm : stats?.mu)}
|
||||||
type="monotoneX"
|
type="monotoneX"
|
||||||
fill="var(--chart-2)"
|
fill="var(--chart-2)"
|
||||||
fillOpacity={0.4}
|
fillOpacity={0.4}
|
||||||
@@ -74,31 +75,31 @@ export default memo(function MemChart({ chartData }: { chartData: ChartData }) {
|
|||||||
stackId="1"
|
stackId="1"
|
||||||
isAnimationActive={false}
|
isAnimationActive={false}
|
||||||
/>
|
/>
|
||||||
{chartData.systemStats.at(-1)?.stats.mz && (
|
{/* {chartData.systemStats.at(-1)?.stats.mz && ( */}
|
||||||
<Area
|
<Area
|
||||||
name="ZFS ARC"
|
name="ZFS ARC"
|
||||||
order={2}
|
order={2}
|
||||||
dataKey="stats.mz"
|
dataKey={({ stats }) => (showMax ? null : stats?.mz)}
|
||||||
type="monotoneX"
|
type="monotoneX"
|
||||||
fill="hsla(175 60% 45% / 0.8)"
|
fill="hsla(175 60% 45% / 0.8)"
|
||||||
fillOpacity={0.5}
|
fillOpacity={0.5}
|
||||||
stroke="hsla(175 60% 45% / 0.8)"
|
stroke="hsla(175 60% 45% / 0.8)"
|
||||||
stackId="1"
|
stackId="1"
|
||||||
isAnimationActive={false}
|
isAnimationActive={false}
|
||||||
/>
|
/>
|
||||||
)}
|
{/* )} */}
|
||||||
<Area
|
<Area
|
||||||
name={t`Cache / Buffers`}
|
name={t`Cache / Buffers`}
|
||||||
order={1}
|
order={1}
|
||||||
dataKey="stats.mb"
|
dataKey={({ stats }) => (showMax ? null : stats?.mb)}
|
||||||
type="monotoneX"
|
type="monotoneX"
|
||||||
fill="hsla(160 60% 45% / 0.5)"
|
fill="hsla(160 60% 45% / 0.5)"
|
||||||
fillOpacity={0.4}
|
fillOpacity={0.4}
|
||||||
// strokeOpacity={1}
|
|
||||||
stroke="hsla(160 60% 45% / 0.5)"
|
stroke="hsla(160 60% 45% / 0.5)"
|
||||||
stackId="1"
|
stackId="1"
|
||||||
isAnimationActive={false}
|
isAnimationActive={false}
|
||||||
/>
|
/>
|
||||||
|
{/* <ChartLegend content={<ChartLegendContent />} /> */}
|
||||||
</AreaChart>
|
</AreaChart>
|
||||||
</ChartContainer>
|
</ChartContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import { t } from "@lingui/core/macro"
|
|||||||
|
|
||||||
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
||||||
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
||||||
import { useYAxisWidth, cn, formatShortDate, decimalString, chartMargin, formatBytes, toFixedFloat } from "@/lib/utils"
|
import { cn, formatShortDate, decimalString, chartMargin, formatBytes, toFixedFloat } from "@/lib/utils"
|
||||||
import { ChartData } from "@/types"
|
import { ChartData } from "@/types"
|
||||||
import { memo } from "react"
|
import { memo } from "react"
|
||||||
import { $userSettings } from "@/lib/stores"
|
import { $userSettings } from "@/lib/stores"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
|
import { useYAxisWidth } from "./hooks"
|
||||||
|
|
||||||
export default memo(function SwapChart({ chartData }: { chartData: ChartData }) {
|
export default memo(function SwapChart({ chartData }: { chartData: ChartData }) {
|
||||||
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
||||||
|
|||||||
@@ -8,19 +8,12 @@ import {
|
|||||||
ChartTooltipContent,
|
ChartTooltipContent,
|
||||||
xAxis,
|
xAxis,
|
||||||
} from "@/components/ui/chart"
|
} from "@/components/ui/chart"
|
||||||
import {
|
import { cn, formatShortDate, toFixedFloat, chartMargin, formatTemperature, decimalString } from "@/lib/utils"
|
||||||
useYAxisWidth,
|
|
||||||
cn,
|
|
||||||
formatShortDate,
|
|
||||||
toFixedFloat,
|
|
||||||
chartMargin,
|
|
||||||
formatTemperature,
|
|
||||||
decimalString,
|
|
||||||
} from "@/lib/utils"
|
|
||||||
import { ChartData } from "@/types"
|
import { ChartData } from "@/types"
|
||||||
import { memo, useMemo } from "react"
|
import { memo, useMemo } from "react"
|
||||||
import { $temperatureFilter, $userSettings } from "@/lib/stores"
|
import { $temperatureFilter, $userSettings } from "@/lib/stores"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
|
import { useYAxisWidth } from "./hooks"
|
||||||
|
|
||||||
export default memo(function TemperatureChart({ chartData }: { chartData: ChartData }) {
|
export default memo(function TemperatureChart({ chartData }: { chartData: ChartData }) {
|
||||||
const filter = useStore($temperatureFilter)
|
const filter = useStore($temperatureFilter)
|
||||||
|
|||||||
@@ -23,11 +23,13 @@ import {
|
|||||||
} from "@/components/ui/command"
|
} from "@/components/ui/command"
|
||||||
import { memo, useEffect, useMemo } from "react"
|
import { memo, useEffect, useMemo } from "react"
|
||||||
import { $systems } from "@/lib/stores"
|
import { $systems } from "@/lib/stores"
|
||||||
import { getHostDisplayValue, isAdmin, listen } from "@/lib/utils"
|
import { getHostDisplayValue, listen } from "@/lib/utils"
|
||||||
import { $router, basePath, navigate, prependBasePath } from "./router"
|
import { $router, basePath, navigate, prependBasePath } from "./router"
|
||||||
import { Trans } from "@lingui/react/macro"
|
import { Trans } from "@lingui/react/macro"
|
||||||
import { t } from "@lingui/core/macro"
|
import { t } from "@lingui/core/macro"
|
||||||
import { getPagePath } from "@nanostores/router"
|
import { getPagePath } from "@nanostores/router"
|
||||||
|
import { DialogDescription } from "@radix-ui/react-dialog"
|
||||||
|
import { isAdmin } from "@/lib/api"
|
||||||
|
|
||||||
export default memo(function CommandPalette({ open, setOpen }: { open: boolean; setOpen: (open: boolean) => void }) {
|
export default memo(function CommandPalette({ open, setOpen }: { open: boolean; setOpen: (open: boolean) => void }) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -54,11 +56,9 @@ export default memo(function CommandPalette({ open, setOpen }: { open: boolean;
|
|||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
<CommandDialog open={open} onOpenChange={setOpen}>
|
<CommandDialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogDescription className="sr-only">Command palette</DialogDescription>
|
||||||
<CommandInput placeholder={t`Search for systems or settings...`} />
|
<CommandInput placeholder={t`Search for systems or settings...`} />
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandEmpty>
|
|
||||||
<Trans>No results found.</Trans>
|
|
||||||
</CommandEmpty>
|
|
||||||
{systems.length > 0 && (
|
{systems.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<CommandGroup>
|
<CommandGroup>
|
||||||
@@ -71,7 +71,7 @@ export default memo(function CommandPalette({ open, setOpen }: { open: boolean;
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Server className="me-2 size-4" />
|
<Server className="me-2 size-4" />
|
||||||
<span>{system.name}</span>
|
<span className="max-w-60 truncate">{system.name}</span>
|
||||||
<CommandShortcut>{getHostDisplayValue(system)}</CommandShortcut>
|
<CommandShortcut>{getHostDisplayValue(system)}</CommandShortcut>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
))}
|
))}
|
||||||
@@ -121,6 +121,7 @@ export default memo(function CommandPalette({ open, setOpen }: { open: boolean;
|
|||||||
{SettingsShortcut}
|
{SettingsShortcut}
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
|
keywords={[t`Universal token`]}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
navigate(getPagePath($router, "settings", { name: "tokens" }))
|
navigate(getPagePath($router, "settings", { name: "tokens" }))
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
@@ -214,6 +215,9 @@ export default memo(function CommandPalette({ open, setOpen }: { open: boolean;
|
|||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
<CommandEmpty>
|
||||||
|
<Trans>No results found.</Trans>
|
||||||
|
</CommandEmpty>
|
||||||
</CommandList>
|
</CommandList>
|
||||||
</CommandDialog>
|
</CommandDialog>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export function LangToggle() {
|
|||||||
{languages.map(({ lang, label, e }) => (
|
{languages.map(({ lang, label, e }) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={lang}
|
key={lang}
|
||||||
className={cn("px-2.5 flex gap-2.5", lang === i18n.locale && "font-semibold")}
|
className={cn("px-2.5 flex gap-2.5 cursor-pointer", lang === i18n.locale && "bg-accent/70 font-medium")}
|
||||||
onClick={() => dynamicActivate(lang)}
|
onClick={() => dynamicActivate(lang)}
|
||||||
>
|
>
|
||||||
<span>{e}</span> {label}
|
<span>{e}</span> {label}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { buttonVariants } from "@/components/ui/button"
|
|||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
import { LoaderCircle, LockIcon, LogInIcon, MailIcon } from "lucide-react"
|
import { LoaderCircle, LockIcon, LogInIcon, MailIcon } from "lucide-react"
|
||||||
import { $authenticated, pb } from "@/lib/stores"
|
import { $authenticated } from "@/lib/stores"
|
||||||
import * as v from "valibot"
|
import * as v from "valibot"
|
||||||
import { toast } from "../ui/use-toast"
|
import { toast } from "../ui/use-toast"
|
||||||
import { Dialog, DialogContent, DialogTrigger, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
import { Dialog, DialogContent, DialogTrigger, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||||
@@ -13,6 +13,7 @@ import { useCallback, useEffect, useState } from "react"
|
|||||||
import { AuthMethodsList, AuthProviderInfo, OAuth2AuthConfig } from "pocketbase"
|
import { AuthMethodsList, AuthProviderInfo, OAuth2AuthConfig } from "pocketbase"
|
||||||
import { $router, Link, prependBasePath } from "../router"
|
import { $router, Link, prependBasePath } from "../router"
|
||||||
import { getPagePath } from "@nanostores/router"
|
import { getPagePath } from "@nanostores/router"
|
||||||
|
import { pb } from "@/lib/api"
|
||||||
|
|
||||||
const honeypot = v.literal("")
|
const honeypot = v.literal("")
|
||||||
const emailSchema = v.pipe(v.string(), v.email(t`Invalid email address.`))
|
const emailSchema = v.pipe(v.string(), v.email(t`Invalid email address.`))
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Trans } from "@lingui/react/macro";
|
import { Trans } from "@lingui/react/macro"
|
||||||
import { t } from "@lingui/core/macro";
|
import { t } from "@lingui/core/macro"
|
||||||
import { LoaderCircle, MailIcon, SendHorizonalIcon } from "lucide-react"
|
import { LoaderCircle, MailIcon, SendHorizonalIcon } from "lucide-react"
|
||||||
import { Input } from "../ui/input"
|
import { Input } from "../ui/input"
|
||||||
import { Label } from "../ui/label"
|
import { Label } from "../ui/label"
|
||||||
@@ -7,9 +7,9 @@ import { useCallback, useState } from "react"
|
|||||||
import { toast } from "../ui/use-toast"
|
import { toast } from "../ui/use-toast"
|
||||||
import { buttonVariants } from "../ui/button"
|
import { buttonVariants } from "../ui/button"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { pb } from "@/lib/stores"
|
|
||||||
import { Dialog, DialogHeader } from "../ui/dialog"
|
import { Dialog, DialogHeader } from "../ui/dialog"
|
||||||
import { DialogContent, DialogTrigger, DialogTitle } from "../ui/dialog"
|
import { DialogContent, DialogTrigger, DialogTitle } from "../ui/dialog"
|
||||||
|
import { pb } from "@/lib/api"
|
||||||
|
|
||||||
const showLoginFaliedToast = () => {
|
const showLoginFaliedToast = () => {
|
||||||
toast({
|
toast({
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { t } from "@lingui/core/macro";
|
import { t } from "@lingui/core/macro"
|
||||||
import { UserAuthForm } from "@/components/login/auth-form"
|
import { UserAuthForm } from "@/components/login/auth-form"
|
||||||
import { Logo } from "../logo"
|
import { Logo } from "../logo"
|
||||||
import { useEffect, useMemo, useState } from "react"
|
import { useEffect, useMemo, useState } from "react"
|
||||||
import { pb } from "@/lib/stores"
|
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import ForgotPassword from "./forgot-pass-form"
|
import ForgotPassword from "./forgot-pass-form"
|
||||||
import { $router } from "../router"
|
import { $router } from "../router"
|
||||||
import { AuthMethodsList } from "pocketbase"
|
import { AuthMethodsList } from "pocketbase"
|
||||||
import { useTheme } from "../theme-provider"
|
import { useTheme } from "../theme-provider"
|
||||||
|
import { pb } from "@/lib/api"
|
||||||
|
import { ModeToggle } from "../mode-toggle"
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
const page = useStore($router)
|
const page = useStore($router)
|
||||||
@@ -50,8 +51,11 @@ 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-ignore
|
// @ts-ignore
|
||||||
style={{ maxWidth: "22em", "--border": theme == "light" ? "30 8% 80%" : "220 3% 20%" }}
|
style={{ maxWidth: "22em", "--border": theme == "light" ? "hsl(30, 8%, 70%)" : "hsl(220, 3%, 25%)" }}
|
||||||
>
|
>
|
||||||
|
<div className="absolute top-3 right-3">
|
||||||
|
<ModeToggle />
|
||||||
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h1 className="mb-3">
|
<h1 className="mb-3">
|
||||||
<Logo className="h-7 fill-foreground mx-auto" />
|
<Logo className="h-7 fill-foreground mx-auto" />
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import { $router, basePath, Link, prependBasePath } from "./router"
|
|||||||
import { LangToggle } from "./lang-toggle"
|
import { LangToggle } from "./lang-toggle"
|
||||||
import { ModeToggle } from "./mode-toggle"
|
import { ModeToggle } from "./mode-toggle"
|
||||||
import { Logo } from "./logo"
|
import { Logo } from "./logo"
|
||||||
import { pb } from "@/lib/stores"
|
import { cn, runOnce } from "@/lib/utils"
|
||||||
import { cn, isReadOnlyUser, isAdmin, logOut } from "@/lib/utils"
|
import { isReadOnlyUser, isAdmin, logOut, pb } from "@/lib/api"
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
@@ -36,12 +36,17 @@ const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0
|
|||||||
export default function Navbar() {
|
export default function Navbar() {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center h-14 md:h-16 bg-card px-4 pe-3 sm:px-6 border border-border/60 bt-0 rounded-md my-4">
|
<div className="flex items-center h-14 md:h-16 bg-card px-4 pe-3 sm:px-6 border border-border/60 bt-0 rounded-md my-4">
|
||||||
<Link href={basePath} aria-label="Home" className="p-2 ps-0 me-3">
|
<Link
|
||||||
|
href={basePath}
|
||||||
|
aria-label="Home"
|
||||||
|
className="p-2 ps-0 me-3"
|
||||||
|
onMouseEnter={runOnce(() => import("@/components/routes/home"))}
|
||||||
|
>
|
||||||
<Logo className="h-[1.1rem] md:h-5 fill-foreground" />
|
<Logo className="h-[1.1rem] md:h-5 fill-foreground" />
|
||||||
</Link>
|
</Link>
|
||||||
<SearchButton />
|
<SearchButton />
|
||||||
|
|
||||||
<div className="flex items-center ms-auto">
|
<div className="flex items-center ms-auto" onMouseEnter={() => import("@/components/routes/settings/general")}>
|
||||||
<LangToggle />
|
<LangToggle />
|
||||||
<ModeToggle />
|
<ModeToggle />
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
import { Suspense, lazy, memo, useEffect, useMemo } from "react"
|
import { Suspense, memo, useEffect, useMemo } from "react"
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"
|
||||||
import { $alerts, $systems, pb } from "@/lib/stores"
|
import { $alerts, $allSystemsById } from "@/lib/stores"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import { GithubIcon } from "lucide-react"
|
import { GithubIcon } from "lucide-react"
|
||||||
import { Separator } from "../ui/separator"
|
import { Separator } from "../ui/separator"
|
||||||
import { getSystemNameFromId, updateRecordList, updateSystemList } from "@/lib/utils"
|
import { AlertRecord } from "@/types"
|
||||||
import { AlertRecord, SystemRecord } from "@/types"
|
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
||||||
import { $router, Link } from "../router"
|
import { $router, Link } from "../router"
|
||||||
import { Plural, Trans, useLingui } from "@lingui/react/macro"
|
import { Plural, Trans, useLingui } from "@lingui/react/macro"
|
||||||
import { getPagePath } from "@nanostores/router"
|
import { getPagePath } from "@nanostores/router"
|
||||||
import { alertInfo } from "@/lib/alerts"
|
import { alertInfo } from "@/lib/alerts"
|
||||||
|
import SystemsTable from "@/components/systems-table/systems-table"
|
||||||
const SystemsTable = lazy(() => import("../systems-table/systems-table"))
|
|
||||||
|
|
||||||
export default memo(function () {
|
export default memo(function () {
|
||||||
const { t } = useLingui()
|
const { t } = useLingui()
|
||||||
@@ -21,19 +19,6 @@ export default memo(function () {
|
|||||||
document.title = t`Dashboard` + " / Beszel"
|
document.title = t`Dashboard` + " / Beszel"
|
||||||
}, [t])
|
}, [t])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// make sure we have the latest list of systems
|
|
||||||
updateSystemList()
|
|
||||||
|
|
||||||
// subscribe to real time updates for systems / alerts
|
|
||||||
pb.collection<SystemRecord>("systems").subscribe("*", (e) => {
|
|
||||||
updateRecordList(e, $systems)
|
|
||||||
})
|
|
||||||
return () => {
|
|
||||||
pb.collection("systems").unsubscribe("*")
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => (
|
() => (
|
||||||
<>
|
<>
|
||||||
@@ -42,7 +27,7 @@ export default memo(function () {
|
|||||||
<SystemsTable />
|
<SystemsTable />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
||||||
<div className="flex gap-1.5 justify-end items-center pe-3 sm:pe-6 mt-3.5 text-xs opacity-80">
|
<div className="flex gap-1.5 justify-end items-center pe-3 sm:pe-6 mt-3.5 mb-4 text-xs opacity-80">
|
||||||
<a
|
<a
|
||||||
href="https://github.com/henrygd/beszel"
|
href="https://github.com/henrygd/beszel"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -67,6 +52,7 @@ export default memo(function () {
|
|||||||
|
|
||||||
const ActiveAlerts = () => {
|
const ActiveAlerts = () => {
|
||||||
const alerts = useStore($alerts)
|
const alerts = useStore($alerts)
|
||||||
|
const systems = useStore($allSystemsById)
|
||||||
|
|
||||||
const { activeAlerts, alertsKey } = useMemo(() => {
|
const { activeAlerts, alertsKey } = useMemo(() => {
|
||||||
const activeAlerts: AlertRecord[] = []
|
const activeAlerts: AlertRecord[] = []
|
||||||
@@ -110,7 +96,7 @@ const ActiveAlerts = () => {
|
|||||||
>
|
>
|
||||||
<info.icon className="h-4 w-4" />
|
<info.icon className="h-4 w-4" />
|
||||||
<AlertTitle>
|
<AlertTitle>
|
||||||
{getSystemNameFromId(alert.system)} {info.name().toLowerCase().replace("cpu", "CPU")}
|
{systems[alert.system]?.name} {info.name().toLowerCase().replace("cpu", "CPU")}
|
||||||
</AlertTitle>
|
</AlertTitle>
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
{alert.name === "Status" ? (
|
{alert.name === "Status" ? (
|
||||||
@@ -123,7 +109,7 @@ const ActiveAlerts = () => {
|
|||||||
)}
|
)}
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
<Link
|
<Link
|
||||||
href={getPagePath($router, "system", { name: getSystemNameFromId(alert.system) })}
|
href={getPagePath($router, "system", { name: systems[alert.system]?.name })}
|
||||||
className="absolute inset-0 w-full h-full"
|
className="absolute inset-0 w-full h-full"
|
||||||
aria-label="View system"
|
aria-label="View system"
|
||||||
></Link>
|
></Link>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { pb } from "@/lib/stores"
|
import { pb } from "@/lib/api"
|
||||||
import { cn, formatDuration, formatShortDate } from "@/lib/utils"
|
import { cn, formatDuration, formatShortDate } from "@/lib/utils"
|
||||||
import { alertInfo } from "@/lib/alerts"
|
import { alertInfo } from "@/lib/alerts"
|
||||||
import { AlertsHistoryRecord } from "@/types"
|
import { AlertsHistoryRecord } from "@/types"
|
||||||
@@ -273,13 +273,13 @@ export default function AlertsHistoryDataTable() {
|
|||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<TableRow key={headerGroup.id}>
|
<tr key={headerGroup.id} className="border-border/50">
|
||||||
{headerGroup.headers.map((header) => (
|
{headerGroup.headers.map((header) => (
|
||||||
<TableHead className="px-2" key={header.id}>
|
<TableHead className="px-2" key={header.id}>
|
||||||
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
||||||
</TableHead>
|
</TableHead>
|
||||||
))}
|
))}
|
||||||
</TableRow>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
import { t } from "@lingui/core/macro"
|
import { t } from "@lingui/core/macro"
|
||||||
import { Trans } from "@lingui/react/macro"
|
import { Trans } from "@lingui/react/macro"
|
||||||
import { isAdmin } from "@/lib/utils"
|
|
||||||
import { Separator } from "@/components/ui/separator"
|
import { Separator } from "@/components/ui/separator"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { redirectPage } from "@nanostores/router"
|
import { redirectPage } from "@nanostores/router"
|
||||||
import { $router } from "@/components/router"
|
import { $router } from "@/components/router"
|
||||||
import { AlertCircleIcon, FileSlidersIcon, LoaderCircleIcon } from "lucide-react"
|
import { AlertCircleIcon, FileSlidersIcon, LoaderCircleIcon } from "lucide-react"
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
||||||
import { pb } from "@/lib/stores"
|
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { Textarea } from "@/components/ui/textarea"
|
import { Textarea } from "@/components/ui/textarea"
|
||||||
import { toast } from "@/components/ui/use-toast"
|
import { toast } from "@/components/ui/use-toast"
|
||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
|
import { isAdmin, pb } from "@/lib/api"
|
||||||
|
|
||||||
export default function ConfigYaml() {
|
export default function ConfigYaml() {
|
||||||
const [configContent, setConfigContent] = useState<string>("")
|
const [configContent, setConfigContent] = useState<string>("")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { t } from "@lingui/core/macro"
|
import { t } from "@lingui/core/macro"
|
||||||
import { Trans } from "@lingui/react/macro"
|
import { Trans } from "@lingui/react/macro"
|
||||||
import { useEffect } from "react"
|
import { lazy, useEffect } from "react"
|
||||||
import { Separator } from "../../ui/separator"
|
import { Separator } from "../../ui/separator"
|
||||||
import { SidebarNav } from "./sidebar-nav.tsx"
|
import { SidebarNav } from "./sidebar-nav.tsx"
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card.tsx"
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card.tsx"
|
||||||
@@ -8,15 +8,23 @@ import { useStore } from "@nanostores/react"
|
|||||||
import { $router } from "@/components/router.tsx"
|
import { $router } from "@/components/router.tsx"
|
||||||
import { getPagePath, redirectPage } from "@nanostores/router"
|
import { getPagePath, redirectPage } from "@nanostores/router"
|
||||||
import { BellIcon, FileSlidersIcon, FingerprintIcon, SettingsIcon, AlertOctagonIcon } from "lucide-react"
|
import { BellIcon, FileSlidersIcon, FingerprintIcon, SettingsIcon, AlertOctagonIcon } from "lucide-react"
|
||||||
import { $userSettings, pb } from "@/lib/stores.ts"
|
import { $userSettings } from "@/lib/stores.ts"
|
||||||
import { toast } from "@/components/ui/use-toast.ts"
|
import { toast } from "@/components/ui/use-toast.ts"
|
||||||
import { UserSettings } from "@/types"
|
import { UserSettings } from "@/types"
|
||||||
import General from "./general.tsx"
|
|
||||||
import Notifications from "./notifications.tsx"
|
|
||||||
import ConfigYaml from "./config-yaml.tsx"
|
|
||||||
import { useLingui } from "@lingui/react/macro"
|
import { useLingui } from "@lingui/react/macro"
|
||||||
import Fingerprints from "./tokens-fingerprints.tsx"
|
import { pb } from "@/lib/api"
|
||||||
import AlertsHistoryDataTable from "./alerts-history-data-table"
|
|
||||||
|
const generalSettingsImport = () => import("./general.tsx")
|
||||||
|
const notificationsSettingsImport = () => import("./notifications.tsx")
|
||||||
|
const configYamlSettingsImport = () => import("./config-yaml.tsx")
|
||||||
|
const fingerprintsSettingsImport = () => import("./tokens-fingerprints.tsx")
|
||||||
|
const alertsHistoryDataTableSettingsImport = () => import("./alerts-history-data-table.tsx")
|
||||||
|
|
||||||
|
const GeneralSettings = lazy(generalSettingsImport)
|
||||||
|
const NotificationsSettings = lazy(notificationsSettingsImport)
|
||||||
|
const ConfigYamlSettings = lazy(configYamlSettingsImport)
|
||||||
|
const FingerprintsSettings = lazy(fingerprintsSettingsImport)
|
||||||
|
const AlertsHistoryDataTableSettings = lazy(alertsHistoryDataTableSettingsImport)
|
||||||
|
|
||||||
export async function saveSettings(newSettings: Partial<UserSettings>) {
|
export async function saveSettings(newSettings: Partial<UserSettings>) {
|
||||||
try {
|
try {
|
||||||
@@ -59,23 +67,27 @@ export default function SettingsLayout() {
|
|||||||
title: t`Notifications`,
|
title: t`Notifications`,
|
||||||
href: getPagePath($router, "settings", { name: "notifications" }),
|
href: getPagePath($router, "settings", { name: "notifications" }),
|
||||||
icon: BellIcon,
|
icon: BellIcon,
|
||||||
|
preload: notificationsSettingsImport,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t`Tokens & Fingerprints`,
|
title: t`Tokens & Fingerprints`,
|
||||||
href: getPagePath($router, "settings", { name: "tokens" }),
|
href: getPagePath($router, "settings", { name: "tokens" }),
|
||||||
icon: FingerprintIcon,
|
icon: FingerprintIcon,
|
||||||
noReadOnly: true,
|
noReadOnly: true,
|
||||||
|
preload: fingerprintsSettingsImport,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t`Alert History`,
|
title: t`Alert History`,
|
||||||
href: getPagePath($router, "settings", { name: "alert-history" }),
|
href: getPagePath($router, "settings", { name: "alert-history" }),
|
||||||
icon: AlertOctagonIcon,
|
icon: AlertOctagonIcon,
|
||||||
|
preload: alertsHistoryDataTableSettingsImport,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t`YAML Config`,
|
title: t`YAML Config`,
|
||||||
href: getPagePath($router, "settings", { name: "config" }),
|
href: getPagePath($router, "settings", { name: "config" }),
|
||||||
icon: FileSlidersIcon,
|
icon: FileSlidersIcon,
|
||||||
admin: true,
|
admin: true,
|
||||||
|
preload: configYamlSettingsImport,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -90,7 +102,7 @@ export default function SettingsLayout() {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="pt-5 px-4 pb-8 min-h-96 sm:pt-6 sm:px-7">
|
<Card className="pt-5 px-4 pb-8 min-h-96 mb-14 sm:pt-6 sm:px-7">
|
||||||
<CardHeader className="p-0">
|
<CardHeader className="p-0">
|
||||||
<CardTitle className="mb-1">
|
<CardTitle className="mb-1">
|
||||||
<Trans>Settings</Trans>
|
<Trans>Settings</Trans>
|
||||||
@@ -120,14 +132,14 @@ function SettingsContent({ name }: { name: string }) {
|
|||||||
|
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case "general":
|
case "general":
|
||||||
return <General userSettings={userSettings} />
|
return <GeneralSettings userSettings={userSettings} />
|
||||||
case "notifications":
|
case "notifications":
|
||||||
return <Notifications userSettings={userSettings} />
|
return <NotificationsSettings userSettings={userSettings} />
|
||||||
case "config":
|
case "config":
|
||||||
return <ConfigYaml />
|
return <ConfigYamlSettings />
|
||||||
case "tokens":
|
case "tokens":
|
||||||
return <Fingerprints />
|
return <FingerprintsSettings />
|
||||||
case "alert-history":
|
case "alert-history":
|
||||||
return <AlertsHistoryDataTable />
|
return <AlertsHistoryDataTableSettings />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { Trans } from "@lingui/react/macro"
|
|||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
import { pb } from "@/lib/stores"
|
|
||||||
import { Separator } from "@/components/ui/separator"
|
import { Separator } from "@/components/ui/separator"
|
||||||
import { Card } from "@/components/ui/card"
|
import { Card } from "@/components/ui/card"
|
||||||
import { BellIcon, LoaderCircleIcon, PlusIcon, SaveIcon, Trash2Icon } from "lucide-react"
|
import { BellIcon, LoaderCircleIcon, PlusIcon, SaveIcon, Trash2Icon } from "lucide-react"
|
||||||
@@ -13,8 +12,8 @@ import { InputTags } from "@/components/ui/input-tags"
|
|||||||
import { UserSettings } from "@/types"
|
import { UserSettings } from "@/types"
|
||||||
import { saveSettings } from "./layout"
|
import { saveSettings } from "./layout"
|
||||||
import * as v from "valibot"
|
import * as v from "valibot"
|
||||||
import { isAdmin } from "@/lib/utils"
|
|
||||||
import { prependBasePath } from "@/components/router"
|
import { prependBasePath } from "@/components/router"
|
||||||
|
import { isAdmin, pb } from "@/lib/api"
|
||||||
|
|
||||||
interface ShoutrrrUrlCardProps {
|
interface ShoutrrrUrlCardProps {
|
||||||
url: string
|
url: string
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import { cn, isAdmin, isReadOnlyUser } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
import { isAdmin, isReadOnlyUser } from "@/lib/api"
|
||||||
import { buttonVariants } from "../../ui/button"
|
import { buttonVariants } from "../../ui/button"
|
||||||
import { $router, Link, navigate } from "../../router"
|
import { $router, Link, navigate } from "../../router"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
@@ -13,6 +14,7 @@ interface SidebarNavProps extends React.HTMLAttributes<HTMLElement> {
|
|||||||
icon?: React.FC<React.SVGProps<SVGSVGElement>>
|
icon?: React.FC<React.SVGProps<SVGSVGElement>>
|
||||||
admin?: boolean
|
admin?: boolean
|
||||||
noReadOnly?: boolean
|
noReadOnly?: boolean
|
||||||
|
preload?: () => Promise<{ default: React.ComponentType<any> }>
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +54,7 @@ export function SidebarNav({ className, items, ...props }: SidebarNavProps) {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
|
onMouseEnter={() => item.preload?.()}
|
||||||
key={item.href}
|
key={item.href}
|
||||||
href={item.href}
|
href={item.href}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Trans, useLingui } from "@lingui/react/macro"
|
import { Trans, useLingui } from "@lingui/react/macro"
|
||||||
import { t } from "@lingui/core/macro"
|
import { t } from "@lingui/core/macro"
|
||||||
import { $publicKey, pb } from "@/lib/stores"
|
import { $publicKey } from "@/lib/stores"
|
||||||
import { memo, useEffect, useMemo, useState } from "react"
|
import { memo, useEffect, useMemo, useState } from "react"
|
||||||
import { Table, TableCell, TableHead, TableBody, TableRow, TableHeader } from "@/components/ui/table"
|
import { Table, TableCell, TableHead, TableBody, TableRow, TableHeader } from "@/components/ui/table"
|
||||||
import { FingerprintRecord } from "@/types"
|
import { FingerprintRecord } from "@/types"
|
||||||
@@ -14,7 +14,8 @@ import {
|
|||||||
Trash2Icon,
|
Trash2Icon,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { toast } from "@/components/ui/use-toast"
|
import { toast } from "@/components/ui/use-toast"
|
||||||
import { cn, copyToClipboard, generateToken, getHubURL, isReadOnlyUser, tokenMap } from "@/lib/utils"
|
import { cn, copyToClipboard, generateToken, getHubURL, tokenMap } from "@/lib/utils"
|
||||||
|
import { isReadOnlyUser, pb } from "@/lib/api"
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -271,7 +272,7 @@ const SectionTable = memo(({ fingerprints = [] }: { fingerprints: FingerprintRec
|
|||||||
<div className="rounded-md border overflow-hidden w-full mt-4">
|
<div className="rounded-md border overflow-hidden w-full mt-4">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<tr className="border-border/50">
|
||||||
{headerCols.map((col) => (
|
{headerCols.map((col) => (
|
||||||
<TableHead key={col.label} style={{ minWidth: col.w }}>
|
<TableHead key={col.label} style={{ minWidth: col.w }}>
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
@@ -287,12 +288,14 @@ const SectionTable = memo(({ fingerprints = [] }: { fingerprints: FingerprintRec
|
|||||||
</span>
|
</span>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
)}
|
)}
|
||||||
</TableRow>
|
</tr>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody className="whitespace-pre">
|
<TableBody className="whitespace-pre">
|
||||||
{fingerprints.map((fingerprint, i) => (
|
{fingerprints.map((fingerprint, i) => (
|
||||||
<TableRow key={i}>
|
<TableRow key={i}>
|
||||||
<TableCell className="font-medium ps-5 py-2">{fingerprint.expand.system.name}</TableCell>
|
<TableCell className="font-medium ps-5 py-2 max-w-60 truncate">
|
||||||
|
{fingerprint.expand.system.name}
|
||||||
|
</TableCell>
|
||||||
<TableCell className="font-mono text-[0.95em] py-2">{fingerprint.token}</TableCell>
|
<TableCell className="font-mono text-[0.95em] py-2">{fingerprint.token}</TableCell>
|
||||||
<TableCell className="font-mono text-[0.95em] py-2">{fingerprint.fingerprint}</TableCell>
|
<TableCell className="font-mono text-[0.95em] py-2">{fingerprint.fingerprint}</TableCell>
|
||||||
{!isReadOnly && (
|
{!isReadOnly && (
|
||||||
|
|||||||
@@ -2,17 +2,18 @@ import { t } from "@lingui/core/macro"
|
|||||||
import { Plural, Trans } from "@lingui/react/macro"
|
import { Plural, Trans } from "@lingui/react/macro"
|
||||||
import {
|
import {
|
||||||
$systems,
|
$systems,
|
||||||
pb,
|
|
||||||
$chartTime,
|
$chartTime,
|
||||||
$containerFilter,
|
$containerFilter,
|
||||||
$userSettings,
|
$userSettings,
|
||||||
$direction,
|
$direction,
|
||||||
$maxValues,
|
$maxValues,
|
||||||
$temperatureFilter,
|
$temperatureFilter,
|
||||||
|
$allSystemsByName,
|
||||||
} from "@/lib/stores"
|
} from "@/lib/stores"
|
||||||
import { ChartData, ChartTimes, ContainerStatsRecord, GPUData, SystemRecord, SystemStatsRecord } from "@/types"
|
import { ChartData, ChartTimes, ContainerStatsRecord, GPUData, SystemRecord, SystemStatsRecord } from "@/types"
|
||||||
|
import { useContainerChartConfigs } from "@/components/charts/hooks"
|
||||||
import { ChartType, Unit, Os, SystemStatus } from "@/lib/enums"
|
import { ChartType, Unit, Os, SystemStatus } from "@/lib/enums"
|
||||||
import React, { lazy, memo, useCallback, useEffect, useMemo, useRef, useState, type JSX } from "react"
|
import React, { memo, useCallback, useEffect, useMemo, useRef, useState, type JSX } from "react"
|
||||||
import { Card, CardHeader, CardTitle, CardDescription } from "../ui/card"
|
import { Card, CardHeader, CardTitle, CardDescription } from "../ui/card"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import Spinner from "../spinner"
|
import Spinner from "../spinner"
|
||||||
@@ -24,12 +25,12 @@ import {
|
|||||||
decimalString,
|
decimalString,
|
||||||
formatBytes,
|
formatBytes,
|
||||||
getHostDisplayValue,
|
getHostDisplayValue,
|
||||||
getPbTimestamp,
|
|
||||||
listen,
|
listen,
|
||||||
parseSemVer,
|
parseSemVer,
|
||||||
toFixedFloat,
|
toFixedFloat,
|
||||||
useLocalStorage,
|
useBrowserStorage,
|
||||||
} from "@/lib/utils"
|
} from "@/lib/utils"
|
||||||
|
import { getPbTimestamp, pb } from "@/lib/api"
|
||||||
import { Separator } from "../ui/separator"
|
import { Separator } from "../ui/separator"
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip"
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip"
|
||||||
import { Button } from "../ui/button"
|
import { Button } from "../ui/button"
|
||||||
@@ -42,15 +43,15 @@ import { useLingui } from "@lingui/react/macro"
|
|||||||
import { $router, navigate } from "../router"
|
import { $router, navigate } from "../router"
|
||||||
import { getPagePath } from "@nanostores/router"
|
import { getPagePath } from "@nanostores/router"
|
||||||
import { batteryStateTranslations } from "@/lib/i18n"
|
import { batteryStateTranslations } from "@/lib/i18n"
|
||||||
|
import AreaChartDefault from "@/components/charts/area-chart"
|
||||||
const AreaChartDefault = lazy(() => import("../charts/area-chart"))
|
import ContainerChart from "@/components/charts/container-chart"
|
||||||
const ContainerChart = lazy(() => import("../charts/container-chart"))
|
import MemChart from "@/components/charts/mem-chart"
|
||||||
const MemChart = lazy(() => import("../charts/mem-chart"))
|
import DiskChart from "@/components/charts/disk-chart"
|
||||||
const DiskChart = lazy(() => import("../charts/disk-chart"))
|
import SwapChart from "@/components/charts/swap-chart"
|
||||||
const SwapChart = lazy(() => import("../charts/swap-chart"))
|
import TemperatureChart from "@/components/charts/temperature-chart"
|
||||||
const TemperatureChart = lazy(() => import("../charts/temperature-chart"))
|
import GpuPowerChart from "@/components/charts/gpu-power-chart"
|
||||||
const GpuPowerChart = lazy(() => import("../charts/gpu-power-chart"))
|
import LoadAverageChart from "@/components/charts/load-average-chart"
|
||||||
const LoadAverageChart = lazy(() => import("../charts/load-average-chart"))
|
import { subscribeKeys } from "nanostores"
|
||||||
|
|
||||||
const cache = new Map<string, any>()
|
const cache = new Map<string, any>()
|
||||||
|
|
||||||
@@ -119,13 +120,13 @@ function dockerOrPodman(str: string, system: SystemRecord) {
|
|||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SystemDetail({ name }: { name: string }) {
|
export default memo(function SystemDetail({ name }: { name: string }) {
|
||||||
const direction = useStore($direction)
|
const direction = useStore($direction)
|
||||||
const { t } = useLingui()
|
const { t } = useLingui()
|
||||||
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] = useLocalStorage("grid", true)
|
const [grid, setGrid] = useBrowserStorage("grid", true)
|
||||||
const [system, setSystem] = useState({} as SystemRecord)
|
const [system, setSystem] = useState({} as SystemRecord)
|
||||||
const [systemStats, setSystemStats] = useState([] as SystemStatsRecord[])
|
const [systemStats, setSystemStats] = useState([] as SystemStatsRecord[])
|
||||||
const [containerData, setContainerData] = useState([] as ChartData["containerData"])
|
const [containerData, setContainerData] = useState([] as ChartData["containerData"])
|
||||||
@@ -151,36 +152,13 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
}
|
}
|
||||||
}, [name])
|
}, [name])
|
||||||
|
|
||||||
// function resetCharts() {
|
// find matching system and update when it changes
|
||||||
// setSystemStats([])
|
|
||||||
// setContainerData([])
|
|
||||||
// }
|
|
||||||
|
|
||||||
// useEffect(resetCharts, [chartTime])
|
|
||||||
|
|
||||||
// find matching system
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (system.id && system.name === name) {
|
return subscribeKeys($allSystemsByName, [name], (newSystems) => {
|
||||||
return
|
const sys = newSystems[name]
|
||||||
}
|
sys?.id && setSystem(sys)
|
||||||
const matchingSystem = systems.find((s) => s.name === name) as SystemRecord
|
|
||||||
if (matchingSystem) {
|
|
||||||
setSystem(matchingSystem)
|
|
||||||
}
|
|
||||||
}, [name, system, systems])
|
|
||||||
|
|
||||||
// update system when new data is available
|
|
||||||
useEffect(() => {
|
|
||||||
if (!system.id) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pb.collection<SystemRecord>("systems").subscribe(system.id, (e) => {
|
|
||||||
setSystem(e.record)
|
|
||||||
})
|
})
|
||||||
return () => {
|
}, [name])
|
||||||
pb.collection("systems").unsubscribe(system.id)
|
|
||||||
}
|
|
||||||
}, [system.id])
|
|
||||||
|
|
||||||
const chartData: ChartData = useMemo(() => {
|
const chartData: ChartData = useMemo(() => {
|
||||||
const lastCreated = Math.max(
|
const lastCreated = Math.max(
|
||||||
@@ -197,6 +175,9 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
}
|
}
|
||||||
}, [systemStats, containerData, direction])
|
}, [systemStats, containerData, direction])
|
||||||
|
|
||||||
|
// Share chart config computation for all container charts
|
||||||
|
const containerChartConfigs = useContainerChartConfigs(containerData)
|
||||||
|
|
||||||
// get stats
|
// get stats
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!system.id || !chartTime) {
|
if (!system.id || !chartTime) {
|
||||||
@@ -287,11 +268,19 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
value: system.info.k,
|
value: system.info.k,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
let uptime: React.ReactNode
|
let uptime: React.ReactNode
|
||||||
if (system.info.u < 172800) {
|
if (system.info.u < 3600) {
|
||||||
const hours = Math.trunc(system.info.u / 3600)
|
uptime = (
|
||||||
uptime = <Plural value={hours} one="# hour" other="# hours" />
|
<Plural
|
||||||
|
value={Math.trunc(system.info.u / 60)}
|
||||||
|
one="# minute"
|
||||||
|
few="# minutes"
|
||||||
|
many="# minutes"
|
||||||
|
other="# minutes"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
} else if (system.info.u < 172800) {
|
||||||
|
uptime = <Plural value={Math.trunc(system.info.u / 3600)} one="# hour" other="# hours" />
|
||||||
} else {
|
} else {
|
||||||
uptime = <Plural value={Math.trunc(system.info?.u / 86400)} one="# day" other="# days" />
|
uptime = <Plural value={Math.trunc(system.info?.u / 86400)} one="# day" other="# days" />
|
||||||
}
|
}
|
||||||
@@ -317,7 +306,7 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
Icon: any
|
Icon: any
|
||||||
hide?: boolean
|
hide?: boolean
|
||||||
}[]
|
}[]
|
||||||
}, [system.info])
|
}, [system.info, t])
|
||||||
|
|
||||||
/** Space for tooltip if more than 12 containers */
|
/** Space for tooltip if more than 12 containers */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -391,7 +380,7 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div id="chartwrap" className="grid gap-4 mb-10 overflow-x-clip">
|
<div id="chartwrap" className="grid gap-4 mb-14 overflow-x-clip">
|
||||||
{/* system info */}
|
{/* system info */}
|
||||||
<Card>
|
<Card>
|
||||||
<div className="grid xl:flex gap-4 px-4 sm:px-6 pt-3 sm:pt-4 pb-5">
|
<div className="grid xl:flex gap-4 px-4 sm:px-6 pt-3 sm:pt-4 pb-5">
|
||||||
@@ -486,7 +475,7 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
{
|
{
|
||||||
label: t`CPU Usage`,
|
label: t`CPU Usage`,
|
||||||
dataKey: ({ stats }) => (showMax ? stats?.cpum : stats?.cpu),
|
dataKey: ({ stats }) => (showMax ? stats?.cpum : stats?.cpu),
|
||||||
color: "1",
|
color: 1,
|
||||||
opacity: 0.4,
|
opacity: 0.4,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
@@ -503,7 +492,12 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
description={t`Average CPU utilization of containers`}
|
description={t`Average CPU utilization of containers`}
|
||||||
cornerEl={containerFilterBar}
|
cornerEl={containerFilterBar}
|
||||||
>
|
>
|
||||||
<ContainerChart chartData={chartData} dataKey="c" chartType={ChartType.CPU} />
|
<ContainerChart
|
||||||
|
chartData={chartData}
|
||||||
|
dataKey="c"
|
||||||
|
chartType={ChartType.CPU}
|
||||||
|
chartConfig={containerChartConfigs.cpu}
|
||||||
|
/>
|
||||||
</ChartCard>
|
</ChartCard>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -512,8 +506,9 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
grid={grid}
|
grid={grid}
|
||||||
title={t`Memory Usage`}
|
title={t`Memory Usage`}
|
||||||
description={t`Precise utilization at the recorded time`}
|
description={t`Precise utilization at the recorded time`}
|
||||||
|
cornerEl={maxValSelect}
|
||||||
>
|
>
|
||||||
<MemChart chartData={chartData} />
|
<MemChart chartData={chartData} showMax={showMax} />
|
||||||
</ChartCard>
|
</ChartCard>
|
||||||
|
|
||||||
{containerFilterBar && (
|
{containerFilterBar && (
|
||||||
@@ -524,7 +519,12 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
description={dockerOrPodman(t`Memory usage of docker containers`, system)}
|
description={dockerOrPodman(t`Memory usage of docker containers`, system)}
|
||||||
cornerEl={containerFilterBar}
|
cornerEl={containerFilterBar}
|
||||||
>
|
>
|
||||||
<ContainerChart chartData={chartData} dataKey="m" chartType={ChartType.Memory} />
|
<ContainerChart
|
||||||
|
chartData={chartData}
|
||||||
|
dataKey="m"
|
||||||
|
chartType={ChartType.Memory}
|
||||||
|
chartConfig={containerChartConfigs.memory}
|
||||||
|
/>
|
||||||
</ChartCard>
|
</ChartCard>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -546,13 +546,13 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
{
|
{
|
||||||
label: t({ message: "Write", comment: "Disk write" }),
|
label: t({ message: "Write", comment: "Disk write" }),
|
||||||
dataKey: ({ stats }) => (showMax ? stats?.dwm : stats?.dw),
|
dataKey: ({ stats }) => (showMax ? stats?.dwm : stats?.dw),
|
||||||
color: "3",
|
color: 3,
|
||||||
opacity: 0.3,
|
opacity: 0.3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t({ message: "Read", comment: "Disk read" }),
|
label: t({ message: "Read", comment: "Disk read" }),
|
||||||
dataKey: ({ stats }) => (showMax ? stats?.drm : stats?.dr),
|
dataKey: ({ stats }) => (showMax ? stats?.drm : stats?.dr),
|
||||||
color: "1",
|
color: 1,
|
||||||
opacity: 0.3,
|
opacity: 0.3,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
@@ -587,7 +587,7 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
}
|
}
|
||||||
return data?.stats?.b?.[0] ?? data?.stats?.ns * 1024 * 1024
|
return data?.stats?.b?.[0] ?? data?.stats?.ns * 1024 * 1024
|
||||||
},
|
},
|
||||||
color: "5",
|
color: 5,
|
||||||
opacity: 0.2,
|
opacity: 0.2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -598,7 +598,7 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
}
|
}
|
||||||
return data?.stats?.b?.[1] ?? data?.stats?.nr * 1024 * 1024
|
return data?.stats?.b?.[1] ?? data?.stats?.nr * 1024 * 1024
|
||||||
},
|
},
|
||||||
color: "2",
|
color: 2,
|
||||||
opacity: 0.2,
|
opacity: 0.2,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
@@ -626,8 +626,12 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
description={dockerOrPodman(t`Network traffic of docker containers`, system)}
|
description={dockerOrPodman(t`Network traffic of docker containers`, system)}
|
||||||
cornerEl={containerFilterBar}
|
cornerEl={containerFilterBar}
|
||||||
>
|
>
|
||||||
{/* @ts-ignore */}
|
<ContainerChart
|
||||||
<ContainerChart chartData={chartData} chartType={ChartType.Network} dataKey="n" />
|
chartData={chartData}
|
||||||
|
chartType={ChartType.Network}
|
||||||
|
dataKey="n"
|
||||||
|
chartConfig={containerChartConfigs.network}
|
||||||
|
/>
|
||||||
</ChartCard>
|
</ChartCard>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -687,7 +691,7 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
{
|
{
|
||||||
label: t`Charge`,
|
label: t`Charge`,
|
||||||
dataKey: ({ stats }) => stats?.bat?.[0],
|
dataKey: ({ stats }) => stats?.bat?.[0],
|
||||||
color: "1",
|
color: 1,
|
||||||
opacity: 0.35,
|
opacity: 0.35,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
@@ -730,7 +734,7 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
{
|
{
|
||||||
label: t`Usage`,
|
label: t`Usage`,
|
||||||
dataKey: ({ stats }) => stats?.g?.[id]?.u ?? 0,
|
dataKey: ({ stats }) => stats?.g?.[id]?.u ?? 0,
|
||||||
color: "1",
|
color: 1,
|
||||||
opacity: 0.35,
|
opacity: 0.35,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
@@ -750,7 +754,7 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
{
|
{
|
||||||
label: t`Usage`,
|
label: t`Usage`,
|
||||||
dataKey: ({ stats }) => stats?.g?.[id]?.mu ?? 0,
|
dataKey: ({ stats }) => stats?.g?.[id]?.mu ?? 0,
|
||||||
color: "2",
|
color: 2,
|
||||||
opacity: 0.25,
|
opacity: 0.25,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
@@ -802,13 +806,13 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
{
|
{
|
||||||
label: t`Write`,
|
label: t`Write`,
|
||||||
dataKey: ({ stats }) => stats?.efs?.[extraFsName]?.[showMax ? "wm" : "w"] ?? 0,
|
dataKey: ({ stats }) => stats?.efs?.[extraFsName]?.[showMax ? "wm" : "w"] ?? 0,
|
||||||
color: "3",
|
color: 3,
|
||||||
opacity: 0.3,
|
opacity: 0.3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t`Read`,
|
label: t`Read`,
|
||||||
dataKey: ({ stats }) => stats?.efs?.[extraFsName]?.[showMax ? "rm" : "r"] ?? 0,
|
dataKey: ({ stats }) => stats?.efs?.[extraFsName]?.[showMax ? "rm" : "r"] ?? 0,
|
||||||
color: "1",
|
color: 1,
|
||||||
opacity: 0.3,
|
opacity: 0.3,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
@@ -834,7 +838,7 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
{bottomSpacing > 0 && <span className="block" style={{ height: bottomSpacing }} />}
|
{bottomSpacing > 0 && <span className="block" style={{ height: bottomSpacing }} />}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
||||||
function FilterBar({ store = $containerFilter }: { store?: typeof $containerFilter }) {
|
function FilterBar({ store = $containerFilter }: { store?: typeof $containerFilter }) {
|
||||||
const containerFilter = useStore(store)
|
const containerFilter = useStore(store)
|
||||||
|
|||||||
@@ -23,12 +23,11 @@ import {
|
|||||||
formatBytes,
|
formatBytes,
|
||||||
formatTemperature,
|
formatTemperature,
|
||||||
getMeterState,
|
getMeterState,
|
||||||
isReadOnlyUser,
|
|
||||||
parseSemVer,
|
parseSemVer,
|
||||||
} from "@/lib/utils"
|
} from "@/lib/utils"
|
||||||
import { EthernetIcon, GpuIcon, HourglassIcon, ThermometerIcon } from "../ui/icons"
|
import { EthernetIcon, GpuIcon, HourglassIcon, ThermometerIcon } from "../ui/icons"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import { $userSettings, pb } from "@/lib/stores"
|
import { $longestSystemNameLen, $userSettings } from "@/lib/stores"
|
||||||
import { Trans, useLingui } from "@lingui/react/macro"
|
import { Trans, useLingui } from "@lingui/react/macro"
|
||||||
import { useMemo, useRef, useState } from "react"
|
import { useMemo, useRef, useState } from "react"
|
||||||
import { memo } from "react"
|
import { memo } from "react"
|
||||||
@@ -57,6 +56,7 @@ import { t } from "@lingui/core/macro"
|
|||||||
import { MeterState, SystemStatus } from "@/lib/enums"
|
import { MeterState, SystemStatus } from "@/lib/enums"
|
||||||
import { $router, Link } from "../router"
|
import { $router, Link } from "../router"
|
||||||
import { getPagePath } from "@nanostores/router"
|
import { getPagePath } from "@nanostores/router"
|
||||||
|
import { isReadOnlyUser, pb } from "@/lib/api"
|
||||||
|
|
||||||
const STATUS_COLORS = {
|
const STATUS_COLORS = {
|
||||||
[SystemStatus.Up]: "bg-green-500",
|
[SystemStatus.Up]: "bg-green-500",
|
||||||
@@ -72,7 +72,8 @@ const STATUS_COLORS = {
|
|||||||
export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef<SystemRecord>[] {
|
export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef<SystemRecord>[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
size: 200,
|
// size: 200,
|
||||||
|
size: 100,
|
||||||
minSize: 0,
|
minSize: 0,
|
||||||
accessorKey: "name",
|
accessorKey: "name",
|
||||||
id: "system",
|
id: "system",
|
||||||
@@ -111,11 +112,15 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
|
|||||||
Icon: ServerIcon,
|
Icon: ServerIcon,
|
||||||
cell: (info) => {
|
cell: (info) => {
|
||||||
const { name } = info.row.original
|
const { name } = info.row.original
|
||||||
|
const longestName = useStore($longestSystemNameLen)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span className="flex gap-2 items-center font-medium text-sm text-nowrap md:ps-1 md:pe-5">
|
<span className="flex gap-2 items-center font-medium text-sm text-nowrap md:ps-1">
|
||||||
<IndicatorDot system={info.row.original} />
|
<IndicatorDot system={info.row.original} />
|
||||||
{name}
|
{/* NOTE: change to 1 ch if switching to monospace font */}
|
||||||
|
<span className="truncate" style={{ width: `${longestName / 1.1}ch` }}>
|
||||||
|
{name}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<Link
|
<Link
|
||||||
href={getPagePath($router, "system", { name })}
|
href={getPagePath($router, "system", { name })}
|
||||||
@@ -318,22 +323,18 @@ function sortableHeader(context: HeaderContext<SystemRecord, unknown>) {
|
|||||||
function TableCellWithMeter(info: CellContext<SystemRecord, unknown>) {
|
function TableCellWithMeter(info: CellContext<SystemRecord, unknown>) {
|
||||||
const val = Number(info.getValue()) || 0
|
const val = Number(info.getValue()) || 0
|
||||||
const threshold = getMeterState(val)
|
const threshold = getMeterState(val)
|
||||||
|
const meterClass = cn(
|
||||||
|
"h-full",
|
||||||
|
(info.row.original.status !== SystemStatus.Up && STATUS_COLORS.paused) ||
|
||||||
|
(threshold === MeterState.Good && STATUS_COLORS.up) ||
|
||||||
|
(threshold === MeterState.Warn && STATUS_COLORS.pending) ||
|
||||||
|
STATUS_COLORS.down
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-2 items-center tabular-nums tracking-tight">
|
<div className="flex gap-2 items-center tabular-nums tracking-tight w-full">
|
||||||
<span className="min-w-8">{decimalString(val, val >= 10 ? 1 : 2)}%</span>
|
<span className="min-w-8 shrink-0">{decimalString(val, val >= 10 ? 1 : 2)}%</span>
|
||||||
<span className="grow min-w-8 block bg-muted h-[1em] relative rounded-sm overflow-hidden">
|
<span className="flex-1 min-w-8 grid bg-muted h-[1em] rounded-sm overflow-hidden">
|
||||||
<span
|
<span className={meterClass} style={{ width: `${val}%` }}></span>
|
||||||
className={cn(
|
|
||||||
"absolute inset-0 w-full h-full origin-left",
|
|
||||||
(info.row.original.status !== SystemStatus.Up && STATUS_COLORS.paused) ||
|
|
||||||
(threshold === MeterState.Good && STATUS_COLORS.up) ||
|
|
||||||
(threshold === MeterState.Warn && STATUS_COLORS.pending) ||
|
|
||||||
STATUS_COLORS.down
|
|
||||||
)}
|
|
||||||
style={{
|
|
||||||
transform: `scalex(${val / 100})`,
|
|
||||||
}}
|
|
||||||
></span>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,11 +11,8 @@ import {
|
|||||||
Row,
|
Row,
|
||||||
Table as TableType,
|
Table as TableType,
|
||||||
} from "@tanstack/react-table"
|
} from "@tanstack/react-table"
|
||||||
|
import { TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuCheckboxItem,
|
DropdownMenuCheckboxItem,
|
||||||
@@ -36,33 +33,65 @@ import {
|
|||||||
ArrowUpIcon,
|
ArrowUpIcon,
|
||||||
Settings2Icon,
|
Settings2Icon,
|
||||||
EyeIcon,
|
EyeIcon,
|
||||||
|
FilterIcon,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { memo, useEffect, useMemo, useState } from "react"
|
import { memo, useEffect, useMemo, useRef, useState } from "react"
|
||||||
import { $systems } from "@/lib/stores"
|
import { $pausedSystems, $downSystems, $upSystems, $systems } from "@/lib/stores"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import { cn, useLocalStorage } from "@/lib/utils"
|
import { cn, runOnce, useBrowserStorage } from "@/lib/utils"
|
||||||
import { $router, Link } from "../router"
|
import { $router, Link } from "../router"
|
||||||
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 "@/components/ui/input"
|
||||||
import { getPagePath } from "@nanostores/router"
|
import { getPagePath } from "@nanostores/router"
|
||||||
import SystemsTableColumns, { ActionsButton, IndicatorDot } from "./systems-table-columns"
|
import SystemsTableColumns, { ActionsButton, IndicatorDot } from "./systems-table-columns"
|
||||||
import AlertButton from "../alerts/alert-button"
|
import AlertButton from "../alerts/alert-button"
|
||||||
import { SystemStatus } from "@/lib/enums"
|
import { SystemStatus } from "@/lib/enums"
|
||||||
|
import { useVirtualizer, VirtualItem } from "@tanstack/react-virtual"
|
||||||
|
|
||||||
type ViewMode = "table" | "grid"
|
type ViewMode = "table" | "grid"
|
||||||
|
type StatusFilter = "all" | SystemRecord["status"]
|
||||||
|
|
||||||
|
const preloadSystemDetail = runOnce(() => import("@/components/routes/system.tsx"))
|
||||||
|
|
||||||
export default function SystemsTable() {
|
export default function SystemsTable() {
|
||||||
const data = useStore($systems)
|
const data = useStore($systems)
|
||||||
|
const downSystems = $downSystems.get()
|
||||||
|
const upSystems = $upSystems.get()
|
||||||
|
const pausedSystems = $pausedSystems.get()
|
||||||
const { i18n, t } = useLingui()
|
const { i18n, t } = useLingui()
|
||||||
const [filter, setFilter] = useState<string>()
|
const [filter, setFilter] = useState<string>()
|
||||||
const [sorting, setSorting] = useState<SortingState>([{ id: "system", desc: false }])
|
const [statusFilter, setStatusFilter] = useState<StatusFilter>("all")
|
||||||
|
const [sorting, setSorting] = useBrowserStorage<SortingState>(
|
||||||
|
"sortMode",
|
||||||
|
[{ id: "system", desc: false }],
|
||||||
|
sessionStorage
|
||||||
|
)
|
||||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
||||||
const [columnVisibility, setColumnVisibility] = useLocalStorage<VisibilityState>("cols", {})
|
const [columnVisibility, setColumnVisibility] = useBrowserStorage<VisibilityState>("cols", {})
|
||||||
const [viewMode, setViewMode] = useLocalStorage<ViewMode>("viewMode", window.innerWidth > 1024 ? "table" : "grid")
|
|
||||||
|
|
||||||
const locale = i18n.locale
|
const locale = i18n.locale
|
||||||
|
|
||||||
|
// Filter data based on status filter
|
||||||
|
const filteredData = useMemo(() => {
|
||||||
|
if (statusFilter === "all") {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
if (statusFilter === SystemStatus.Up) {
|
||||||
|
return Object.values(upSystems) ?? []
|
||||||
|
}
|
||||||
|
if (statusFilter === SystemStatus.Down) {
|
||||||
|
return Object.values(downSystems) ?? []
|
||||||
|
}
|
||||||
|
return Object.values(pausedSystems) ?? []
|
||||||
|
}, [data, statusFilter])
|
||||||
|
|
||||||
|
const [viewMode, setViewMode] = useBrowserStorage<ViewMode>(
|
||||||
|
"viewMode",
|
||||||
|
// 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(() => {
|
||||||
if (filter !== undefined) {
|
if (filter !== undefined) {
|
||||||
table.getColumn("system")?.setFilterValue(filter)
|
table.getColumn("system")?.setFilterValue(filter)
|
||||||
@@ -72,7 +101,7 @@ export default function SystemsTable() {
|
|||||||
const columnDefs = useMemo(() => SystemsTableColumns(viewMode), [viewMode])
|
const columnDefs = useMemo(() => SystemsTableColumns(viewMode), [viewMode])
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data,
|
data: filteredData,
|
||||||
columns: columnDefs,
|
columns: columnDefs,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
onSortingChange: setSorting,
|
onSortingChange: setSorting,
|
||||||
@@ -86,7 +115,6 @@ export default function SystemsTable() {
|
|||||||
columnVisibility,
|
columnVisibility,
|
||||||
},
|
},
|
||||||
defaultColumn: {
|
defaultColumn: {
|
||||||
// sortDescFirst: true,
|
|
||||||
invertSorting: true,
|
invertSorting: true,
|
||||||
sortUndefined: "last",
|
sortUndefined: "last",
|
||||||
minSize: 0,
|
minSize: 0,
|
||||||
@@ -98,19 +126,25 @@ export default function SystemsTable() {
|
|||||||
const rows = table.getRowModel().rows
|
const rows = table.getRowModel().rows
|
||||||
const columns = table.getAllColumns()
|
const columns = table.getAllColumns()
|
||||||
const visibleColumns = table.getVisibleLeafColumns()
|
const visibleColumns = table.getVisibleLeafColumns()
|
||||||
|
|
||||||
|
const [upSystemsLength, downSystemsLength, pausedSystemsLength] = useMemo(() => {
|
||||||
|
return [Object.values(upSystems).length, Object.values(downSystems).length, Object.values(pausedSystems).length]
|
||||||
|
}, [upSystems, downSystems, pausedSystems])
|
||||||
|
|
||||||
// TODO: hiding temp then gpu messes up table headers
|
// TODO: hiding temp then gpu messes up table headers
|
||||||
const CardHead = useMemo(() => {
|
const CardHead = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<CardHeader className="pb-5 px-2 sm:px-6 max-sm:pt-5 max-sm:pb-1">
|
<CardHeader className="pb-4.5 px-2 sm:px-6 max-sm:pt-5 max-sm:pb-1">
|
||||||
<div className="grid md:flex gap-5 w-full items-end">
|
<div className="grid md:flex gap-5 w-full items-end">
|
||||||
<div className="px-2 sm:px-1">
|
<div className="px-2 sm:px-1">
|
||||||
<CardTitle className="mb-2.5">
|
<CardTitle className="mb-2">
|
||||||
<Trans>All Systems</Trans>
|
<Trans>All Systems</Trans>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription className="flex">
|
||||||
<Trans>Updated in real time. Click on a system to view information.</Trans>
|
<Trans>Click on a system to view more information.</Trans>
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2 ms-auto w-full md:w-80">
|
<div className="flex gap-2 ms-auto w-full md:w-80">
|
||||||
<Input placeholder={t`Filter...`} onChange={(e) => setFilter(e.target.value)} className="px-4" />
|
<Input placeholder={t`Filter...`} onChange={(e) => setFilter(e.target.value)} className="px-4" />
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
@@ -121,8 +155,8 @@ export default function SystemsTable() {
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" className="h-72 md:h-auto min-w-48 md:min-w-auto overflow-y-auto">
|
<DropdownMenuContent align="end" className="h-72 md:h-auto min-w-48 md:min-w-auto overflow-y-auto">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 divide-y md:divide-s md:divide-y-0">
|
<div className="grid grid-cols-1 md:grid-cols-4 divide-y md:divide-s md:divide-y-0">
|
||||||
<div>
|
<div className="border-r">
|
||||||
<DropdownMenuLabel className="pt-2 px-3.5 flex items-center gap-2">
|
<DropdownMenuLabel className="pt-2 px-3.5 flex items-center gap-2">
|
||||||
<LayoutGridIcon className="size-4" />
|
<LayoutGridIcon className="size-4" />
|
||||||
<Trans>Layout</Trans>
|
<Trans>Layout</Trans>
|
||||||
@@ -144,7 +178,33 @@ export default function SystemsTable() {
|
|||||||
</DropdownMenuRadioGroup>
|
</DropdownMenuRadioGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div className="border-r">
|
||||||
|
<DropdownMenuLabel className="pt-2 px-3.5 flex items-center gap-2">
|
||||||
|
<FilterIcon className="size-4" />
|
||||||
|
<Trans>Status</Trans>
|
||||||
|
</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuRadioGroup
|
||||||
|
className="px-1 pb-1"
|
||||||
|
value={statusFilter}
|
||||||
|
onValueChange={(value) => setStatusFilter(value as StatusFilter)}
|
||||||
|
>
|
||||||
|
<DropdownMenuRadioItem value="all" onSelect={(e) => e.preventDefault()}>
|
||||||
|
<Trans>All Systems</Trans>
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
<DropdownMenuRadioItem value="up" onSelect={(e) => e.preventDefault()}>
|
||||||
|
<Trans>Up ({upSystemsLength})</Trans>
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
<DropdownMenuRadioItem value="down" onSelect={(e) => e.preventDefault()}>
|
||||||
|
<Trans>Down ({downSystemsLength})</Trans>
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
<DropdownMenuRadioItem value="paused" onSelect={(e) => e.preventDefault()}>
|
||||||
|
<Trans>Paused ({pausedSystemsLength})</Trans>
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
</DropdownMenuRadioGroup>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="border-r">
|
||||||
<DropdownMenuLabel className="pt-2 px-3.5 flex items-center gap-2">
|
<DropdownMenuLabel className="pt-2 px-3.5 flex items-center gap-2">
|
||||||
<ArrowUpDownIcon className="size-4" />
|
<ArrowUpDownIcon className="size-4" />
|
||||||
<Trans>Sort By</Trans>
|
<Trans>Sort By</Trans>
|
||||||
@@ -210,7 +270,16 @@ export default function SystemsTable() {
|
|||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
)
|
)
|
||||||
}, [visibleColumns.length, sorting, viewMode, locale])
|
}, [
|
||||||
|
visibleColumns.length,
|
||||||
|
sorting,
|
||||||
|
viewMode,
|
||||||
|
locale,
|
||||||
|
statusFilter,
|
||||||
|
upSystemsLength,
|
||||||
|
downSystemsLength,
|
||||||
|
pausedSystemsLength,
|
||||||
|
])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
@@ -218,7 +287,7 @@ export default function SystemsTable() {
|
|||||||
<div className="p-6 pt-0 max-sm:py-3 max-sm:px-2">
|
<div className="p-6 pt-0 max-sm:py-3 max-sm:px-2">
|
||||||
{viewMode === "table" ? (
|
{viewMode === "table" ? (
|
||||||
// table layout
|
// table layout
|
||||||
<div className="rounded-md border overflow-hidden">
|
<div className="rounded-md">
|
||||||
<AllSystemsTable table={table} rows={rows} colLength={visibleColumns.length} />
|
<AllSystemsTable table={table} rows={rows} colLength={visibleColumns.length} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -240,36 +309,78 @@ export default function SystemsTable() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const AllSystemsTable = memo(
|
const AllSystemsTable = memo(function ({
|
||||||
({ table, rows, colLength }: { table: TableType<SystemRecord>; rows: Row<SystemRecord>[]; colLength: number }) => {
|
table,
|
||||||
return (
|
rows,
|
||||||
<Table>
|
colLength,
|
||||||
<SystemsTableHead table={table} colLength={colLength} />
|
}: {
|
||||||
<TableBody>
|
table: TableType<SystemRecord>
|
||||||
{rows.length ? (
|
rows: Row<SystemRecord>[]
|
||||||
rows.map((row) => (
|
colLength: number
|
||||||
<SystemTableRow key={row.original.id} row={row} length={rows.length} colLength={colLength} />
|
}) {
|
||||||
))
|
// The virtualizer will need a reference to the scrollable container element
|
||||||
) : (
|
const scrollRef = useRef<HTMLDivElement>(null)
|
||||||
<TableRow>
|
|
||||||
<TableCell colSpan={colLength} className="h-24 text-center">
|
const virtualizer = useVirtualizer<HTMLDivElement, HTMLTableRowElement>({
|
||||||
<Trans>No systems found.</Trans>
|
count: rows.length,
|
||||||
</TableCell>
|
estimateSize: () => (rows.length > 10 ? 56 : 60),
|
||||||
</TableRow>
|
getScrollElement: () => scrollRef.current,
|
||||||
)}
|
overscan: 5,
|
||||||
</TableBody>
|
})
|
||||||
</Table>
|
const virtualRows = virtualizer.getVirtualItems()
|
||||||
)
|
|
||||||
}
|
const paddingTop = Math.max(0, virtualRows[0]?.start ?? 0 - virtualizer.options.scrollMargin)
|
||||||
)
|
const paddingBottom = Math.max(0, virtualizer.getTotalSize() - (virtualRows[virtualRows.length - 1]?.end ?? 0))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"h-min max-h-[calc(100dvh-17rem)] max-w-full relative overflow-auto border rounded-md",
|
||||||
|
// don't set min height if there are less than 2 rows, do set if we need to display the empty state
|
||||||
|
(!rows.length || rows.length > 2) && "min-h-50"
|
||||||
|
)}
|
||||||
|
ref={scrollRef}
|
||||||
|
>
|
||||||
|
{/* add header height to table size */}
|
||||||
|
<div style={{ height: `${virtualizer.getTotalSize() + 50}px`, paddingTop, paddingBottom }}>
|
||||||
|
<table className="text-sm w-full h-full">
|
||||||
|
<SystemsTableHead table={table} colLength={colLength} />
|
||||||
|
<TableBody onMouseEnter={preloadSystemDetail}>
|
||||||
|
{rows.length ? (
|
||||||
|
virtualRows.map((virtualRow) => {
|
||||||
|
const row = rows[virtualRow.index] as Row<SystemRecord>
|
||||||
|
return (
|
||||||
|
<SystemTableRow
|
||||||
|
key={row.id}
|
||||||
|
row={row}
|
||||||
|
virtualRow={virtualRow}
|
||||||
|
length={rows.length}
|
||||||
|
colLength={colLength}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={colLength} className="h-37 text-center pointer-events-none">
|
||||||
|
<Trans>No systems found.</Trans>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
function SystemsTableHead({ table, colLength }: { table: TableType<SystemRecord>; colLength: number }) {
|
function SystemsTableHead({ table, colLength }: { table: TableType<SystemRecord>; colLength: number }) {
|
||||||
const { i18n } = useLingui()
|
const { i18n } = useLingui()
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<TableHeader>
|
<TableHeader className="sticky top-0 z-20 w-full border-b-2">
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<TableRow key={headerGroup.id}>
|
<tr key={headerGroup.id}>
|
||||||
{headerGroup.headers.map((header) => {
|
{headerGroup.headers.map((header) => {
|
||||||
return (
|
return (
|
||||||
<TableHead className="px-1.5" key={header.id}>
|
<TableHead className="px-1.5" key={header.id}>
|
||||||
@@ -277,41 +388,49 @@ function SystemsTableHead({ table, colLength }: { table: TableType<SystemRecord>
|
|||||||
</TableHead>
|
</TableHead>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</TableRow>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
)
|
)
|
||||||
}, [i18n.locale, colLength])
|
}, [i18n.locale, colLength])
|
||||||
}
|
}
|
||||||
|
|
||||||
const SystemTableRow = memo(
|
const SystemTableRow = memo(function ({
|
||||||
({ row, length, colLength }: { row: Row<SystemRecord>; length: number; colLength: number }) => {
|
row,
|
||||||
const system = row.original
|
virtualRow,
|
||||||
const { t } = useLingui()
|
colLength,
|
||||||
return useMemo(() => {
|
}: {
|
||||||
return (
|
row: Row<SystemRecord>
|
||||||
<TableRow
|
virtualRow: VirtualItem
|
||||||
// data-state={row.getIsSelected() && "selected"}
|
length: number
|
||||||
className={cn("cursor-pointer transition-opacity relative", {
|
colLength: number
|
||||||
"opacity-50": system.status === SystemStatus.Paused,
|
}) {
|
||||||
})}
|
const system = row.original
|
||||||
>
|
const { t } = useLingui()
|
||||||
{row.getVisibleCells().map((cell) => (
|
return useMemo(() => {
|
||||||
<TableCell
|
return (
|
||||||
key={cell.id}
|
<TableRow
|
||||||
style={{
|
// data-state={row.getIsSelected() && "selected"}
|
||||||
width: cell.column.getSize(),
|
className={cn("cursor-pointer transition-opacity relative safari:transform-3d", {
|
||||||
}}
|
"opacity-50": system.status === SystemStatus.Paused,
|
||||||
className={length > 10 ? "py-2" : "py-2.5"}
|
})}
|
||||||
>
|
>
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
{row.getVisibleCells().map((cell) => (
|
||||||
</TableCell>
|
<TableCell
|
||||||
))}
|
key={cell.id}
|
||||||
</TableRow>
|
style={{
|
||||||
)
|
width: cell.column.getSize(),
|
||||||
}, [system, system.status, colLength, t])
|
height: virtualRow.size,
|
||||||
}
|
}}
|
||||||
)
|
className="py-0"
|
||||||
|
>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
)
|
||||||
|
}, [system, system.status, colLength, t])
|
||||||
|
})
|
||||||
|
|
||||||
const SystemCard = memo(
|
const SystemCard = memo(
|
||||||
({ row, table, colLength }: { row: Row<SystemRecord>; table: TableType<SystemRecord>; colLength: number }) => {
|
({ row, table, colLength }: { row: Row<SystemRecord>; table: TableType<SystemRecord>; colLength: number }) => {
|
||||||
@@ -321,6 +440,7 @@ const SystemCard = memo(
|
|||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
|
onMouseEnter={preloadSystemDetail}
|
||||||
key={system.id}
|
key={system.id}
|
||||||
className={cn(
|
className={cn(
|
||||||
"cursor-pointer hover:shadow-md transition-all bg-transparent w-full dark:border-border duration-200 relative",
|
"cursor-pointer hover:shadow-md transition-all bg-transparent w-full dark:border-border duration-200 relative",
|
||||||
@@ -330,13 +450,11 @@ const SystemCard = memo(
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CardHeader className="py-1 ps-5 pe-3 bg-muted/30 border-b border-border/60">
|
<CardHeader className="py-1 ps-5 pe-3 bg-muted/30 border-b border-border/60">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center gap-2 w-full overflow-hidden">
|
||||||
<CardTitle className="text-base tracking-normal shrink-1 text-primary/90 flex items-center min-h-10 gap-2.5 min-w-0">
|
<CardTitle className="text-base tracking-normal text-primary/90 flex items-center min-w-0 flex-1 gap-2.5">
|
||||||
<div className="flex items-center gap-2.5 min-w-0">
|
<div className="flex items-center gap-2.5 min-w-0 flex-1">
|
||||||
<IndicatorDot system={system} />
|
<IndicatorDot system={system} />
|
||||||
<CardTitle className="text-[.95em]/normal tracking-normal truncate text-primary/90">
|
<span className="text-[.95em]/normal tracking-normal text-primary/90 truncate">{system.name}</span>
|
||||||
{system.name}
|
|
||||||
</CardTitle>
|
|
||||||
</div>
|
</div>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
{table.getColumn("actions")?.getIsVisible() && (
|
{table.getColumn("actions")?.getIsVisible() && (
|
||||||
@@ -347,23 +465,33 @@ const SystemCard = memo(
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="grid gap-2.5 text-sm px-5 pt-3.5 pb-4">
|
<CardContent className="text-sm px-5 pt-3.5 pb-4">
|
||||||
{table.getAllColumns().map((column) => {
|
<div className="grid gap-2.5" style={{ gridTemplateColumns: "24px minmax(80px, max-content) 1fr" }}>
|
||||||
if (!column.getIsVisible() || column.id === "system" || column.id === "actions") return null
|
{table.getAllColumns().map((column) => {
|
||||||
const cell = row.getAllCells().find((cell) => cell.column.id === column.id)
|
if (!column.getIsVisible() || column.id === "system" || column.id === "actions") return null
|
||||||
if (!cell) return null
|
const cell = row.getAllCells().find((cell) => cell.column.id === column.id)
|
||||||
// @ts-ignore
|
if (!cell) return null
|
||||||
const { Icon, name } = column.columnDef as ColumnDef<SystemRecord, unknown>
|
// @ts-ignore
|
||||||
return (
|
const { Icon, name } = column.columnDef as ColumnDef<SystemRecord, unknown>
|
||||||
<div key={column.id} className="flex items-center gap-3">
|
return (
|
||||||
{Icon && <Icon className="size-4 text-muted-foreground" />}
|
<>
|
||||||
<div className="flex items-center gap-3 flex-1">
|
<div key={`${column.id}-icon`} className="flex items-center">
|
||||||
<span className="text-muted-foreground min-w-16">{name()}:</span>
|
{column.id === "lastSeen" ? (
|
||||||
<div className="flex-1">{flexRender(cell.column.columnDef.cell, cell.getContext())}</div>
|
<EyeIcon className="size-4 text-muted-foreground" />
|
||||||
</div>
|
) : (
|
||||||
</div>
|
Icon && <Icon className="size-4 text-muted-foreground" />
|
||||||
)
|
)}
|
||||||
})}
|
</div>
|
||||||
|
<div key={`${column.id}-label`} className="flex items-center text-muted-foreground pr-3">
|
||||||
|
{name()}:
|
||||||
|
</div>
|
||||||
|
<div key={`${column.id}-value`} className="flex items-center">
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<Link
|
<Link
|
||||||
href={getPagePath($router, "system", { name: row.original.name })}
|
href={getPagePath($router, "system", { name: row.original.name })}
|
||||||
|
|||||||
@@ -1,33 +1,41 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { DialogTitle, type DialogProps } from "@radix-ui/react-dialog"
|
|
||||||
import { Command as CommandPrimitive } from "cmdk"
|
import { Command as CommandPrimitive } from "cmdk"
|
||||||
import { Search } from "lucide-react"
|
import { SearchIcon } from "lucide-react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { Dialog, DialogContent } from "@/components/ui/dialog"
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||||
|
|
||||||
const Command = React.forwardRef<
|
function Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {
|
||||||
React.ElementRef<typeof CommandPrimitive>,
|
return (
|
||||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
<CommandPrimitive
|
||||||
>(({ className, ...props }, ref) => (
|
data-slot="command"
|
||||||
<CommandPrimitive
|
className={cn("bg-card flex h-full w-full flex-col overflow-hidden rounded-md", className)}
|
||||||
ref={ref}
|
{...props}
|
||||||
className={cn("flex h-full w-full flex-col overflow-hidden bg-card", className)}
|
/>
|
||||||
{...props}
|
)
|
||||||
/>
|
}
|
||||||
))
|
|
||||||
Command.displayName = CommandPrimitive.displayName
|
|
||||||
|
|
||||||
interface CommandDialogProps extends DialogProps {}
|
function CommandDialog({
|
||||||
|
title = "Command Palette",
|
||||||
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
description = "Search for a command to run...",
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
showCloseButton = true,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof Dialog> & {
|
||||||
|
title?: string
|
||||||
|
description?: string
|
||||||
|
className?: string
|
||||||
|
showCloseButton?: boolean
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<Dialog {...props}>
|
<Dialog {...props}>
|
||||||
<DialogContent className="overflow-hidden p-0 shadow-lg">
|
<DialogHeader className="sr-only">
|
||||||
<div className="sr-only">
|
<DialogTitle>{title}</DialogTitle>
|
||||||
<DialogTitle>Command</DialogTitle>
|
<DialogDescription>{description}</DialogDescription>
|
||||||
</div>
|
</DialogHeader>
|
||||||
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
<DialogContent className={cn("overflow-hidden p-0", className)}>
|
||||||
|
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
||||||
{children}
|
{children}
|
||||||
</Command>
|
</Command>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
@@ -35,89 +43,81 @@ const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const CommandInput = React.forwardRef<
|
function CommandInput({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Input>) {
|
||||||
React.ElementRef<typeof CommandPrimitive.Input>,
|
return (
|
||||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
<div data-slot="command-input-wrapper" className="flex h-9 items-center gap-2 border-b px-3">
|
||||||
>(({ className, ...props }, ref) => (
|
<SearchIcon className="size-4 shrink-0 opacity-50" />
|
||||||
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
|
<CommandPrimitive.Input
|
||||||
<Search className="me-2 h-4 w-4 shrink-0 opacity-50" />
|
data-slot="command-input"
|
||||||
<CommandPrimitive.Input
|
className={cn(
|
||||||
ref={ref}
|
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive.List
|
||||||
|
data-slot="command-list"
|
||||||
|
className={cn("max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandEmpty({ ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) {
|
||||||
|
return <CommandPrimitive.Empty data-slot="command-empty" className="py-6 text-center text-sm" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandGroup({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Group>) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive.Group
|
||||||
|
data-slot="command-group"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</div>
|
)
|
||||||
))
|
}
|
||||||
|
|
||||||
CommandInput.displayName = CommandPrimitive.Input.displayName
|
function CommandSeparator({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Separator>) {
|
||||||
|
return (
|
||||||
const CommandList = React.forwardRef<
|
<CommandPrimitive.Separator
|
||||||
React.ElementRef<typeof CommandPrimitive.List>,
|
data-slot="command-separator"
|
||||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
className={cn("bg-border -mx-1 h-px", className)}
|
||||||
>(({ className, ...props }, ref) => (
|
{...props}
|
||||||
<CommandPrimitive.List
|
/>
|
||||||
ref={ref}
|
)
|
||||||
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
}
|
||||||
{...props}
|
|
||||||
/>
|
function CommandItem({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>) {
|
||||||
))
|
return (
|
||||||
|
<CommandPrimitive.Item
|
||||||
CommandList.displayName = CommandPrimitive.List.displayName
|
data-slot="command-item"
|
||||||
|
className={cn(
|
||||||
const CommandEmpty = React.forwardRef<
|
"data-[selected=true]:bg-accent/70 data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
React.ElementRef<typeof CommandPrimitive.Empty>,
|
className
|
||||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
)}
|
||||||
>((props, ref) => <CommandPrimitive.Empty ref={ref} className="py-6 text-center text-sm" {...props} />)
|
{...props}
|
||||||
|
/>
|
||||||
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
|
)
|
||||||
|
}
|
||||||
const CommandGroup = React.forwardRef<
|
|
||||||
React.ElementRef<typeof CommandPrimitive.Group>,
|
function CommandShortcut({ className, ...props }: React.ComponentProps<"span">) {
|
||||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
return (
|
||||||
>(({ className, ...props }, ref) => (
|
<span
|
||||||
<CommandPrimitive.Group
|
data-slot="command-shortcut"
|
||||||
ref={ref}
|
className={cn("text-muted-foreground ml-auto text-xs tracking-wide", className)}
|
||||||
className={cn(
|
{...props}
|
||||||
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
|
/>
|
||||||
className
|
)
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
|
|
||||||
CommandGroup.displayName = CommandPrimitive.Group.displayName
|
|
||||||
|
|
||||||
const CommandSeparator = React.forwardRef<
|
|
||||||
React.ElementRef<typeof CommandPrimitive.Separator>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<CommandPrimitive.Separator ref={ref} className={cn("-mx-1 h-px bg-border", className)} {...props} />
|
|
||||||
))
|
|
||||||
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
|
|
||||||
|
|
||||||
const CommandItem = React.forwardRef<
|
|
||||||
React.ElementRef<typeof CommandPrimitive.Item>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<CommandPrimitive.Item
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
"relative flex cursor-default opacity-70 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-hidden aria-selected:bg-accent/60 aria-selected:opacity-90 data-[disabled='true']:pointer-events-none data-[disabled='true']:opacity-50",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
|
|
||||||
CommandItem.displayName = CommandPrimitive.Item.displayName
|
|
||||||
|
|
||||||
const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
||||||
return <span className={cn("ms-auto text-xs tracking-wide text-muted-foreground", className)} {...props} />
|
|
||||||
}
|
}
|
||||||
CommandShortcut.displayName = "CommandShortcut"
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Command,
|
Command,
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ const DropdownMenuItem = React.forwardRef<
|
|||||||
<DropdownMenuPrimitive.Item
|
<DropdownMenuPrimitive.Item
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex select-none items-center rounded-sm px-2.5 py-1.5 text-sm outline-hidden focus:bg-accent/70 focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
|
"cursor-pointer relative flex select-none items-center rounded-sm px-2.5 py-1.5 text-sm outline-hidden focus:bg-accent/70 focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
|
||||||
inset && "ps-8",
|
inset && "ps-8",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
@@ -95,7 +95,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
|
|||||||
<DropdownMenuPrimitive.CheckboxItem
|
<DropdownMenuPrimitive.CheckboxItem
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex select-none items-center rounded-sm py-1.5 ps-8 pe-2 text-sm outline-hidden focus:bg-accent/70 focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
|
"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 ps-8 pe-2.5 text-sm outline-hidden focus:bg-accent/70 focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
@@ -118,7 +118,7 @@ const DropdownMenuRadioItem = React.forwardRef<
|
|||||||
<DropdownMenuPrimitive.RadioItem
|
<DropdownMenuPrimitive.RadioItem
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 ps-8 pe-2 text-sm outline-hidden focus:bg-accent/70 focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
|
"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 ps-8 pe-2.5 text-sm outline-hidden focus:bg-accent/70 focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export function InputCopy({ value, id, name }: { value: string; id: string; name
|
|||||||
}
|
}
|
||||||
></div>
|
></div>
|
||||||
<TooltipProvider delayDuration={100} disableHoverableContent>
|
<TooltipProvider delayDuration={100} disableHoverableContent>
|
||||||
<Tooltip>
|
<Tooltip disableHoverableContent={true}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -2,21 +2,19 @@ import * as React from "react"
|
|||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
|
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||||
|
|
||||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(({ className, type, ...props }, ref) => {
|
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
|
data-slot="input"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-10 w-full rounded-md border bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
"flex h-10 w-full rounded-md border bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
Input.displayName = "Input"
|
|
||||||
|
|
||||||
export { Input }
|
export { Input }
|
||||||
|
|||||||
@@ -13,7 +13,11 @@ Table.displayName = "Table"
|
|||||||
|
|
||||||
const TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
|
const TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
|
||||||
({ className, ...props }, ref) => (
|
({ className, ...props }, ref) => (
|
||||||
<thead ref={ref} className={cn("bg-muted/30 [&_tr]:border-b", className)} {...props} />
|
<thead
|
||||||
|
ref={ref}
|
||||||
|
className={cn("bg-table-header border-b border-border/50 [&_tr]:border-b", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
TableHeader.displayName = "TableHeader"
|
TableHeader.displayName = "TableHeader"
|
||||||
|
|||||||
@@ -3,26 +3,47 @@ import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
|||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const TooltipProvider = TooltipPrimitive.Provider
|
function TooltipProvider({ delayDuration = 0, ...props }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
||||||
|
return <TooltipPrimitive.Provider data-slot="tooltip-provider" delayDuration={delayDuration} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
const Tooltip = TooltipPrimitive.Root
|
function Tooltip({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<TooltipProvider>
|
||||||
|
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
||||||
|
</TooltipProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const TooltipTrigger = TooltipPrimitive.Trigger
|
function TooltipTrigger({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
||||||
|
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
const TooltipContent = React.forwardRef<
|
function TooltipContent({
|
||||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
className,
|
||||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
sideOffset = 0,
|
||||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
children,
|
||||||
<TooltipPrimitive.Content
|
...props
|
||||||
ref={ref}
|
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
||||||
sideOffset={sideOffset}
|
return (
|
||||||
className={cn(
|
<TooltipPrimitive.Portal>
|
||||||
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
<TooltipPrimitive.Content
|
||||||
className
|
data-slot="tooltip-content"
|
||||||
)}
|
sideOffset={sideOffset}
|
||||||
{...props}
|
className={cn(
|
||||||
/>
|
"bg-popover text-popover-foreground border animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-sm text-balance",
|
||||||
))
|
className
|
||||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<TooltipPrimitive.Arrow
|
||||||
|
className="bg-popover border z-50 fill-popover size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] will-change-transform"
|
||||||
|
style={{ clipPath: "inset(25% 0 0 25%)" }}
|
||||||
|
/>
|
||||||
|
</TooltipPrimitive.Content>
|
||||||
|
</TooltipPrimitive.Portal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@import "tw-animate-css";
|
@import "tw-animate-css";
|
||||||
|
|
||||||
|
@custom-variant light (&:is(.light *));
|
||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
@custom-variant safari (@supports (hanging-punctuation: first) and (-webkit-appearance: none));
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background: hsl(30 8% 98%);
|
--background: hsl(30 8% 98%);
|
||||||
--foreground: hsl(30 0% 0%);
|
--foreground: hsl(30 0% 10%);
|
||||||
--card: hsl(30 0% 100%);
|
--card: hsl(30 0% 100%);
|
||||||
--card-foreground: hsl(240 6.67% 2.94%);
|
--card-foreground: hsl(240 6% 12%);
|
||||||
--popover: hsl(30 0% 100%);
|
--popover: hsl(30 0% 100%);
|
||||||
--popover-foreground: hsl(240 10% 6.2%);
|
--popover-foreground: hsl(240 10% 6.2%);
|
||||||
--primary: hsl(240 5.88% 10%);
|
--primary: hsl(240 5.88% 10%);
|
||||||
@@ -19,7 +21,7 @@
|
|||||||
--accent: hsl(20 23.08% 94%);
|
--accent: hsl(20 23.08% 94%);
|
||||||
--accent-foreground: hsl(240 5.88% 10%);
|
--accent-foreground: hsl(240 5.88% 10%);
|
||||||
--destructive: hsl(0 66% 53%);
|
--destructive: hsl(0 66% 53%);
|
||||||
--destructive-foreground: hsl(0 0% 98.04%);
|
--destructive-foreground: hsl(0 0% 97%);
|
||||||
--border: hsl(30 8.11% 85.49%);
|
--border: hsl(30 8.11% 85.49%);
|
||||||
--input: hsl(30 4.29% 72.55%);
|
--input: hsl(30 4.29% 72.55%);
|
||||||
--ring: hsl(30 3.97% 49.41%);
|
--ring: hsl(30 3.97% 49.41%);
|
||||||
@@ -29,6 +31,7 @@
|
|||||||
--chart-3: hsl(30 80% 55%);
|
--chart-3: hsl(30 80% 55%);
|
||||||
--chart-4: hsl(280 65% 60%);
|
--chart-4: hsl(280 65% 60%);
|
||||||
--chart-5: hsl(340 75% 55%);
|
--chart-5: hsl(340 75% 55%);
|
||||||
|
--table-header: hsl(225, 6%, 97%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
@@ -48,10 +51,10 @@
|
|||||||
--accent: hsl(220 5% 15.5%);
|
--accent: hsl(220 5% 15.5%);
|
||||||
--accent-foreground: hsl(220 2% 98%);
|
--accent-foreground: hsl(220 2% 98%);
|
||||||
--destructive: hsl(0 62% 46%);
|
--destructive: hsl(0 62% 46%);
|
||||||
--destructive-foreground: hsl(0 0% 97%);
|
|
||||||
--border: hsl(220 3% 16%);
|
--border: hsl(220 3% 16%);
|
||||||
--input: hsl(220 4% 22%);
|
--input: hsl(220 4% 22%);
|
||||||
--ring: hsl(220 4% 80%);
|
--ring: hsl(220 4% 80%);
|
||||||
|
--table-header: hsl(220, 6%, 13%);
|
||||||
--radius: 0.8rem;
|
--radius: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +97,7 @@
|
|||||||
--color-accent: var(--accent);
|
--color-accent: var(--accent);
|
||||||
--color-accent-foreground: var(--accent-foreground);
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
--color-destructive: var(--destructive);
|
--color-destructive: var(--destructive);
|
||||||
|
--color-destructive-foreground: var(--destructive-foreground);
|
||||||
--color-border: var(--border);
|
--color-border: var(--border);
|
||||||
--color-input: var(--input);
|
--color-input: var(--input);
|
||||||
--color-ring: var(--ring);
|
--color-ring: var(--ring);
|
||||||
@@ -102,6 +106,7 @@
|
|||||||
--color-chart-3: var(--chart-3);
|
--color-chart-3: var(--chart-3);
|
||||||
--color-chart-4: var(--chart-4);
|
--color-chart-4: var(--chart-4);
|
||||||
--color-chart-5: var(--chart-5);
|
--color-chart-5: var(--chart-5);
|
||||||
|
--color-table-header: var(--table-header);
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import type { AlertInfo, AlertRecord } from "@/types"
|
import type { AlertInfo, AlertRecord } from "@/types"
|
||||||
import type { RecordSubscription } from "pocketbase"
|
import type { RecordSubscription } from "pocketbase"
|
||||||
import { pb, $alerts } from "@/lib/stores"
|
import { $alerts } from "@/lib/stores"
|
||||||
import { EthernetIcon } from "@/components/ui/icons"
|
import { EthernetIcon } from "@/components/ui/icons"
|
||||||
import { ServerIcon, CpuIcon, MemoryStickIcon, HardDriveIcon, ThermometerIcon, HourglassIcon } from "lucide-react"
|
import { ServerIcon, CpuIcon, MemoryStickIcon, HardDriveIcon, ThermometerIcon, HourglassIcon } from "lucide-react"
|
||||||
import { t } from "@lingui/core/macro"
|
import { t } from "@lingui/core/macro"
|
||||||
|
import { pb } from "./api"
|
||||||
|
|
||||||
/** Alert info for each alert type */
|
/** Alert info for each alert type */
|
||||||
export const alertInfo: Record<string, AlertInfo> = {
|
export const alertInfo: Record<string, AlertInfo> = {
|
||||||
|
|||||||
66
beszel/site/src/lib/api.ts
Normal file
66
beszel/site/src/lib/api.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { ChartTimes, UserSettings } from "@/types"
|
||||||
|
import { $alerts, $allSystemsByName, $userSettings } from "./stores"
|
||||||
|
import { toast } from "@/components/ui/use-toast"
|
||||||
|
import { t } from "@lingui/core/macro"
|
||||||
|
import { chartTimeData } from "./utils"
|
||||||
|
import PocketBase from "pocketbase"
|
||||||
|
import { basePath } from "@/components/router"
|
||||||
|
|
||||||
|
/** PocketBase JS Client */
|
||||||
|
export const pb = new PocketBase(basePath)
|
||||||
|
|
||||||
|
export const isAdmin = () => pb.authStore.record?.role === "admin"
|
||||||
|
export const isReadOnlyUser = () => pb.authStore.record?.role === "readonly"
|
||||||
|
|
||||||
|
export const verifyAuth = () => {
|
||||||
|
pb.collection("users")
|
||||||
|
.authRefresh()
|
||||||
|
.catch(() => {
|
||||||
|
logOut()
|
||||||
|
toast({
|
||||||
|
title: t`Failed to authenticate`,
|
||||||
|
description: t`Please log in again`,
|
||||||
|
variant: "destructive",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Logs the user out by clearing the auth store and unsubscribing from realtime updates. */
|
||||||
|
export async function logOut() {
|
||||||
|
$allSystemsByName.set({})
|
||||||
|
$alerts.set({})
|
||||||
|
$userSettings.set({} as UserSettings)
|
||||||
|
sessionStorage.setItem("lo", "t") // prevent auto login on logout
|
||||||
|
pb.authStore.clear()
|
||||||
|
pb.realtime.unsubscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fetch or create user settings in database */
|
||||||
|
export async function updateUserSettings() {
|
||||||
|
try {
|
||||||
|
const req = await pb.collection("user_settings").getFirstListItem("", { fields: "settings" })
|
||||||
|
$userSettings.set(req.settings)
|
||||||
|
return
|
||||||
|
} catch (e) {
|
||||||
|
console.error("get settings", e)
|
||||||
|
}
|
||||||
|
// create user settings if error fetching existing
|
||||||
|
try {
|
||||||
|
const createdSettings = await pb.collection("user_settings").create({ user: pb.authStore.record!.id })
|
||||||
|
$userSettings.set(createdSettings.settings)
|
||||||
|
} catch (e) {
|
||||||
|
console.error("create settings", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPbTimestamp(timeString: ChartTimes, d?: Date) {
|
||||||
|
d ||= chartTimeData[timeString].getOffset(new Date())
|
||||||
|
const year = d.getUTCFullYear()
|
||||||
|
const month = String(d.getUTCMonth() + 1).padStart(2, "0")
|
||||||
|
const day = String(d.getUTCDate()).padStart(2, "0")
|
||||||
|
const hours = String(d.getUTCHours()).padStart(2, "0")
|
||||||
|
const minutes = String(d.getUTCMinutes()).padStart(2, "0")
|
||||||
|
const seconds = String(d.getUTCSeconds()).padStart(2, "0")
|
||||||
|
|
||||||
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||||
|
}
|
||||||
@@ -1,17 +1,23 @@
|
|||||||
import PocketBase from "pocketbase"
|
import { atom, computed, map, ReadableAtom } from "nanostores"
|
||||||
import { atom, map } from "nanostores"
|
|
||||||
import { AlertMap, ChartTimes, SystemRecord, UserSettings } from "@/types"
|
import { AlertMap, ChartTimes, SystemRecord, UserSettings } from "@/types"
|
||||||
import { basePath } from "@/components/router"
|
|
||||||
import { Unit } from "./enums"
|
import { Unit } from "./enums"
|
||||||
|
import { pb } from "./api"
|
||||||
/** PocketBase JS Client */
|
|
||||||
export const pb = new PocketBase(basePath)
|
|
||||||
|
|
||||||
/** Store if user is authenticated */
|
/** Store if user is authenticated */
|
||||||
export const $authenticated = atom(pb.authStore.isValid)
|
export const $authenticated = atom(pb.authStore.isValid)
|
||||||
|
|
||||||
/** List of system records */
|
/** Map of system records by name */
|
||||||
export const $systems = atom<SystemRecord[]>([])
|
export const $allSystemsByName = map<Record<string, SystemRecord>>({})
|
||||||
|
/** Map of system records by id */
|
||||||
|
export const $allSystemsById = map<Record<string, SystemRecord>>({})
|
||||||
|
/** Map of up systems by id */
|
||||||
|
export const $upSystems = map<Record<string, SystemRecord>>({})
|
||||||
|
/** Map of down systems by id */
|
||||||
|
export const $downSystems = map<Record<string, SystemRecord>>({})
|
||||||
|
/** Map of paused systems by id */
|
||||||
|
export const $pausedSystems = map<Record<string, SystemRecord>>({})
|
||||||
|
/** List of all system records */
|
||||||
|
export const $systems: ReadableAtom<SystemRecord[]> = computed($allSystemsById, Object.values)
|
||||||
|
|
||||||
/** Map of alert records by system id and alert name */
|
/** Map of alert records by system id and alert name */
|
||||||
export const $alerts = map<AlertMap>({})
|
export const $alerts = map<AlertMap>({})
|
||||||
@@ -57,3 +63,8 @@ export const $copyContent = atom("")
|
|||||||
|
|
||||||
/** Direction for localization */
|
/** Direction for localization */
|
||||||
export const $direction = atom<"ltr" | "rtl">("ltr")
|
export const $direction = atom<"ltr" | "rtl">("ltr")
|
||||||
|
|
||||||
|
/** Longest system name length. Used to set table column width. I know this
|
||||||
|
* is stupid but the table is virtualized and I know this will work.
|
||||||
|
*/
|
||||||
|
export const $longestSystemNameLen = atom(8)
|
||||||
|
|||||||
174
beszel/site/src/lib/systemsManager.ts
Normal file
174
beszel/site/src/lib/systemsManager.ts
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
import { SystemRecord } from "@/types"
|
||||||
|
import { PreinitializedMapStore } from "nanostores"
|
||||||
|
import { pb, verifyAuth } from "@/lib/api"
|
||||||
|
import {
|
||||||
|
$allSystemsByName,
|
||||||
|
$upSystems,
|
||||||
|
$downSystems,
|
||||||
|
$pausedSystems,
|
||||||
|
$allSystemsById,
|
||||||
|
$longestSystemNameLen,
|
||||||
|
} from "@/lib/stores"
|
||||||
|
import { updateFavicon, FAVICON_DEFAULT, FAVICON_GREEN, FAVICON_RED } from "@/lib/utils"
|
||||||
|
import { SystemStatus } from "./enums"
|
||||||
|
|
||||||
|
const COLLECTION = pb.collection<SystemRecord>("systems")
|
||||||
|
const FIELDS_DEFAULT = "id,name,host,port,info,status"
|
||||||
|
|
||||||
|
/** Maximum system name length for display purposes */
|
||||||
|
const MAX_SYSTEM_NAME_LENGTH = 20
|
||||||
|
|
||||||
|
let initialized = false
|
||||||
|
let unsub: (() => void) | undefined | void
|
||||||
|
|
||||||
|
/** Initialize the systems manager and set up listeners */
|
||||||
|
export function init() {
|
||||||
|
if (initialized) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
initialized = true
|
||||||
|
|
||||||
|
// sync system stores on change
|
||||||
|
$allSystemsById.listen((newSystems, oldSystems, changedKey) => {
|
||||||
|
const oldSystem = oldSystems[changedKey]
|
||||||
|
const newSystem = newSystems[changedKey]
|
||||||
|
|
||||||
|
// if system is undefined (deleted), remove it from the stores
|
||||||
|
if (oldSystem && !newSystem?.id) {
|
||||||
|
removeFromStore(oldSystem, $upSystems)
|
||||||
|
removeFromStore(oldSystem, $downSystems)
|
||||||
|
removeFromStore(oldSystem, $pausedSystems)
|
||||||
|
removeFromStore(oldSystem, $allSystemsById)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newSystem) {
|
||||||
|
onSystemsChanged(newSystems, undefined)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const newStatus = newSystem.status
|
||||||
|
if (newStatus === SystemStatus.Up) {
|
||||||
|
$upSystems.setKey(newSystem.id, newSystem)
|
||||||
|
removeFromStore(newSystem, $downSystems)
|
||||||
|
removeFromStore(newSystem, $pausedSystems)
|
||||||
|
} else if (newStatus === SystemStatus.Down) {
|
||||||
|
$downSystems.setKey(newSystem.id, newSystem)
|
||||||
|
removeFromStore(newSystem, $upSystems)
|
||||||
|
removeFromStore(newSystem, $pausedSystems)
|
||||||
|
} else if (newStatus === SystemStatus.Paused) {
|
||||||
|
$pausedSystems.setKey(newSystem.id, newSystem)
|
||||||
|
removeFromStore(newSystem, $upSystems)
|
||||||
|
removeFromStore(newSystem, $downSystems)
|
||||||
|
} else if (newStatus === SystemStatus.Pending) {
|
||||||
|
removeFromStore(newSystem, $upSystems)
|
||||||
|
removeFromStore(newSystem, $downSystems)
|
||||||
|
removeFromStore(newSystem, $pausedSystems)
|
||||||
|
}
|
||||||
|
|
||||||
|
// run things that need to be done when systems change
|
||||||
|
onSystemsChanged(newSystems, newSystem)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update the longest system name length and favicon based on system status */
|
||||||
|
function onSystemsChanged(_: Record<string, SystemRecord>, changedSystem: SystemRecord | undefined) {
|
||||||
|
const upSystemsStore = $upSystems.get()
|
||||||
|
const downSystemsStore = $downSystems.get()
|
||||||
|
const upSystems = Object.values(upSystemsStore)
|
||||||
|
const downSystems = Object.values(downSystemsStore)
|
||||||
|
|
||||||
|
// Update longest system name length
|
||||||
|
const longestName = $longestSystemNameLen.get()
|
||||||
|
const nameLen = Math.min(MAX_SYSTEM_NAME_LENGTH, changedSystem?.name.length || 0)
|
||||||
|
if (nameLen > longestName) {
|
||||||
|
$longestSystemNameLen.set(nameLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update favicon based on system status
|
||||||
|
if (downSystems.length > 0) {
|
||||||
|
updateFavicon(FAVICON_RED)
|
||||||
|
} else if (upSystems.length > 0) {
|
||||||
|
updateFavicon(FAVICON_GREEN)
|
||||||
|
} else {
|
||||||
|
updateFavicon(FAVICON_DEFAULT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fetch systems from collection */
|
||||||
|
async function fetchSystems(): Promise<SystemRecord[]> {
|
||||||
|
try {
|
||||||
|
return await COLLECTION.getFullList({ sort: "+name", fields: FIELDS_DEFAULT })
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch systems:", error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add system to both name and ID stores */
|
||||||
|
export function add(system: SystemRecord) {
|
||||||
|
$allSystemsByName.setKey(system.name, system)
|
||||||
|
$allSystemsById.setKey(system.id, system)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update system in stores */
|
||||||
|
export function update(system: SystemRecord) {
|
||||||
|
// if name changed, make sure old name is removed from the name store
|
||||||
|
const oldName = $allSystemsById.get()[system.id]?.name
|
||||||
|
if (oldName !== system.name) {
|
||||||
|
$allSystemsByName.setKey(oldName, undefined as any)
|
||||||
|
}
|
||||||
|
add(system)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Remove system from stores */
|
||||||
|
export function remove(system: SystemRecord) {
|
||||||
|
removeFromStore(system, $allSystemsByName)
|
||||||
|
removeFromStore(system, $allSystemsById)
|
||||||
|
removeFromStore(system, $upSystems)
|
||||||
|
removeFromStore(system, $downSystems)
|
||||||
|
removeFromStore(system, $pausedSystems)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Remove system from specific store */
|
||||||
|
function removeFromStore(system: SystemRecord, store: PreinitializedMapStore<Record<string, SystemRecord>>) {
|
||||||
|
const key = store === $allSystemsByName ? system.name : system.id
|
||||||
|
store.setKey(key, undefined as any)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Action functions for subscription */
|
||||||
|
const actionFns: Record<string, (system: SystemRecord) => void> = {
|
||||||
|
create: add,
|
||||||
|
update: update,
|
||||||
|
delete: remove,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Subscribe to real-time system updates from the collection */
|
||||||
|
export async function subscribe() {
|
||||||
|
try {
|
||||||
|
unsub = await COLLECTION.subscribe("*", ({ action, record }) => actionFns[action]?.(record), {
|
||||||
|
fields: FIELDS_DEFAULT,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to subscribe to systems collection:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Refresh all systems with latest data from the hub */
|
||||||
|
export async function refresh() {
|
||||||
|
try {
|
||||||
|
const records = await fetchSystems()
|
||||||
|
if (!records.length) {
|
||||||
|
// No systems found, verify authentication
|
||||||
|
verifyAuth()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (const record of records) {
|
||||||
|
add(record)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to refresh systems:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Unsubscribe from real-time system updates */
|
||||||
|
export const unsubscribe = () => (unsub = unsub?.())
|
||||||
@@ -2,14 +2,16 @@ import { t } from "@lingui/core/macro"
|
|||||||
import { toast } from "@/components/ui/use-toast"
|
import { toast } from "@/components/ui/use-toast"
|
||||||
import { type ClassValue, clsx } from "clsx"
|
import { type ClassValue, clsx } from "clsx"
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from "tailwind-merge"
|
||||||
import { $alerts, $copyContent, $systems, $userSettings, pb } from "./stores"
|
import { $copyContent, $userSettings } from "./stores"
|
||||||
import type { ChartTimeData, ChartTimes, FingerprintRecord, SemVer, SystemRecord, UserSettings } from "@/types"
|
import type { ChartTimeData, FingerprintRecord, SemVer, SystemRecord } from "@/types"
|
||||||
import { RecordModel, RecordSubscription } from "pocketbase"
|
|
||||||
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 { prependBasePath } from "@/components/router"
|
|
||||||
import { MeterState, Unit } from "./enums"
|
import { MeterState, Unit } from "./enums"
|
||||||
|
import { prependBasePath } from "@/components/router"
|
||||||
|
|
||||||
|
export const FAVICON_DEFAULT = "favicon.svg"
|
||||||
|
export const FAVICON_GREEN = "favicon-green.svg"
|
||||||
|
export const FAVICON_RED = "favicon-red.svg"
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs))
|
||||||
@@ -34,52 +36,6 @@ export async function copyToClipboard(content: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const verifyAuth = () => {
|
|
||||||
pb.collection("users")
|
|
||||||
.authRefresh()
|
|
||||||
.catch(() => {
|
|
||||||
logOut()
|
|
||||||
toast({
|
|
||||||
title: t`Failed to authenticate`,
|
|
||||||
description: t`Please log in again`,
|
|
||||||
variant: "destructive",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const updateSystemList = (() => {
|
|
||||||
let isFetchingSystems = false
|
|
||||||
return async () => {
|
|
||||||
if (isFetchingSystems) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
isFetchingSystems = true
|
|
||||||
try {
|
|
||||||
const records = await pb
|
|
||||||
.collection<SystemRecord>("systems")
|
|
||||||
.getFullList({ sort: "+name", fields: "id,name,host,port,info,status" })
|
|
||||||
|
|
||||||
if (records.length) {
|
|
||||||
$systems.set(records)
|
|
||||||
} else {
|
|
||||||
verifyAuth()
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
isFetchingSystems = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
|
|
||||||
/** Logs the user out by clearing the auth store and unsubscribing from realtime updates. */
|
|
||||||
export async function logOut() {
|
|
||||||
$systems.set([])
|
|
||||||
$alerts.set({})
|
|
||||||
$userSettings.set({} as UserSettings)
|
|
||||||
sessionStorage.setItem("lo", "t") // prevent auto login on logout
|
|
||||||
pb.authStore.clear()
|
|
||||||
pb.realtime.unsubscribe()
|
|
||||||
}
|
|
||||||
|
|
||||||
const hourWithMinutesFormatter = new Intl.DateTimeFormat(undefined, {
|
const hourWithMinutesFormatter = new Intl.DateTimeFormat(undefined, {
|
||||||
hour: "numeric",
|
hour: "numeric",
|
||||||
minute: "numeric",
|
minute: "numeric",
|
||||||
@@ -110,47 +66,6 @@ export const updateFavicon = (newIcon: string) => {
|
|||||||
;(document.querySelector("link[rel='icon']") as HTMLLinkElement).href = prependBasePath(`/static/${newIcon}`)
|
;(document.querySelector("link[rel='icon']") as HTMLLinkElement).href = prependBasePath(`/static/${newIcon}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isAdmin = () => pb.authStore.record?.role === "admin"
|
|
||||||
export const isReadOnlyUser = () => pb.authStore.record?.role === "readonly"
|
|
||||||
|
|
||||||
/** Update systems / alerts list when records change */
|
|
||||||
export function updateRecordList<T extends RecordModel>(e: RecordSubscription<T>, $store: WritableAtom<T[]>) {
|
|
||||||
const curRecords = $store.get()
|
|
||||||
const newRecords = []
|
|
||||||
if (e.action === "delete") {
|
|
||||||
for (const server of curRecords) {
|
|
||||||
if (server.id !== e.record.id) {
|
|
||||||
newRecords.push(server)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let found = 0
|
|
||||||
for (const server of curRecords) {
|
|
||||||
if (server.id === e.record.id) {
|
|
||||||
found = newRecords.push(e.record)
|
|
||||||
} else {
|
|
||||||
newRecords.push(server)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found) {
|
|
||||||
newRecords.push(e.record)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$store.set(newRecords)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPbTimestamp(timeString: ChartTimes, d?: Date) {
|
|
||||||
d ||= chartTimeData[timeString].getOffset(new Date())
|
|
||||||
const year = d.getUTCFullYear()
|
|
||||||
const month = String(d.getUTCMonth() + 1).padStart(2, "0")
|
|
||||||
const day = String(d.getUTCDate()).padStart(2, "0")
|
|
||||||
const hours = String(d.getUTCHours()).padStart(2, "0")
|
|
||||||
const minutes = String(d.getUTCMinutes()).padStart(2, "0")
|
|
||||||
const seconds = String(d.getUTCSeconds()).padStart(2, "0")
|
|
||||||
|
|
||||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export const chartTimeData: ChartTimeData = {
|
export const chartTimeData: ChartTimeData = {
|
||||||
"1h": {
|
"1h": {
|
||||||
type: "1m",
|
type: "1m",
|
||||||
@@ -193,32 +108,6 @@ export const chartTimeData: ChartTimeData = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the correct width of the y axis in recharts based on the longest label */
|
|
||||||
export function useYAxisWidth() {
|
|
||||||
const [yAxisWidth, setYAxisWidth] = useState(0)
|
|
||||||
let maxChars = 0
|
|
||||||
let timeout: Timer
|
|
||||||
function updateYAxisWidth(str: string) {
|
|
||||||
if (str.length > maxChars) {
|
|
||||||
maxChars = str.length
|
|
||||||
const div = document.createElement("div")
|
|
||||||
div.className = "text-xs tabular-nums tracking-tighter table sr-only"
|
|
||||||
div.innerHTML = str
|
|
||||||
clearTimeout(timeout)
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
document.body.appendChild(div)
|
|
||||||
const width = div.offsetWidth + 24
|
|
||||||
if (width > yAxisWidth) {
|
|
||||||
setYAxisWidth(div.offsetWidth + 24)
|
|
||||||
}
|
|
||||||
document.body.removeChild(div)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
return { yAxisWidth, updateYAxisWidth }
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Format number to x decimal places, without trailing zeros */
|
/** Format number to x decimal places, without trailing zeros */
|
||||||
export function toFixedFloat(num: number, digits: number) {
|
export function toFixedFloat(num: number, digits: number) {
|
||||||
return parseFloat((digits === 0 ? Math.ceil(num) : num).toFixed(digits))
|
return parseFloat((digits === 0 ? Math.ceil(num) : num).toFixed(digits))
|
||||||
@@ -241,20 +130,20 @@ export function decimalString(num: number, digits = 2) {
|
|||||||
return formatter.format(num)
|
return formatter.format(num)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get value from local storage */
|
/** Get value from local or session storage */
|
||||||
function getStorageValue(key: string, defaultValue: any) {
|
function getStorageValue(key: string, defaultValue: any, storageInterface: Storage = localStorage) {
|
||||||
const saved = localStorage?.getItem(key)
|
const saved = storageInterface?.getItem(key)
|
||||||
return saved ? JSON.parse(saved) : defaultValue
|
return saved ? JSON.parse(saved) : defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Hook to sync value in local storage */
|
/** Hook to sync value in local or session storage */
|
||||||
export function useLocalStorage<T>(key: string, defaultValue: T) {
|
export function useBrowserStorage<T>(key: string, defaultValue: T, storageInterface: Storage = localStorage) {
|
||||||
key = `besz-${key}`
|
key = `besz-${key}`
|
||||||
const [value, setValue] = useState(() => {
|
const [value, setValue] = useState(() => {
|
||||||
return getStorageValue(key, defaultValue)
|
return getStorageValue(key, defaultValue, storageInterface)
|
||||||
})
|
})
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage?.setItem(key, JSON.stringify(value))
|
storageInterface?.setItem(key, JSON.stringify(value))
|
||||||
}, [key, value])
|
}, [key, value])
|
||||||
|
|
||||||
return [value, setValue]
|
return [value, setValue]
|
||||||
@@ -329,24 +218,6 @@ export function formatBytes(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Fetch or create user settings in database */
|
|
||||||
export async function updateUserSettings() {
|
|
||||||
try {
|
|
||||||
const req = await pb.collection("user_settings").getFirstListItem("", { fields: "settings" })
|
|
||||||
$userSettings.set(req.settings)
|
|
||||||
return
|
|
||||||
} catch (e) {
|
|
||||||
console.error("get settings", e)
|
|
||||||
}
|
|
||||||
// create user settings if error fetching existing
|
|
||||||
try {
|
|
||||||
const createdSettings = await pb.collection("user_settings").create({ user: pb.authStore.record!.id })
|
|
||||||
$userSettings.set(createdSettings.settings)
|
|
||||||
} catch (e) {
|
|
||||||
console.error("create settings", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const chartMargin = { top: 12 }
|
export const chartMargin = { top: 12 }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -357,6 +228,21 @@ export const chartMargin = { top: 12 }
|
|||||||
*/
|
*/
|
||||||
export const getHostDisplayValue = (system: SystemRecord): string => system.host.slice(system.host.lastIndexOf("/") + 1)
|
export const getHostDisplayValue = (system: SystemRecord): string => system.host.slice(system.host.lastIndexOf("/") + 1)
|
||||||
|
|
||||||
|
// export function formatUptimeString(uptimeSeconds: number): string {
|
||||||
|
// if (!uptimeSeconds || isNaN(uptimeSeconds)) return ""
|
||||||
|
// if (uptimeSeconds < 3600) {
|
||||||
|
// const minutes = Math.trunc(uptimeSeconds / 60)
|
||||||
|
// return plural({ minutes }, { one: "# minute", other: "# minutes" })
|
||||||
|
// } else if (uptimeSeconds < 172800) {
|
||||||
|
// const hours = Math.trunc(uptimeSeconds / 3600)
|
||||||
|
// console.log(hours)
|
||||||
|
// return plural({ hours }, { one: "# hour", other: "# hours" })
|
||||||
|
// } else {
|
||||||
|
// const days = Math.trunc(uptimeSeconds / 86400)
|
||||||
|
// return plural({ days }, { one: "# day", other: "# days" })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
/** Generate a random token for the agent */
|
/** Generate a random token for the agent */
|
||||||
export const generateToken = () => {
|
export const generateToken = () => {
|
||||||
try {
|
try {
|
||||||
@@ -436,15 +322,20 @@ export function debounce<T extends (...args: any[]) => any>(func: T, wait: numbe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* returns the name of a system from its id */
|
// Cache for runOnce
|
||||||
export const getSystemNameFromId = (() => {
|
const runOnceCache = new WeakMap<Function, { done: boolean; result: unknown }>()
|
||||||
const cache = new Map<string, string>()
|
/** Run a function only once */
|
||||||
return (systemId: string): string => {
|
export function runOnce<T extends (...args: any[]) => any>(fn: T): T {
|
||||||
if (cache.has(systemId)) {
|
return ((...args: Parameters<T>) => {
|
||||||
return cache.get(systemId)!
|
let state = runOnceCache.get(fn)
|
||||||
|
if (!state) {
|
||||||
|
state = { done: false, result: undefined }
|
||||||
|
runOnceCache.set(fn, state)
|
||||||
}
|
}
|
||||||
const sysName = $systems.get().find((s) => s.id === systemId)?.name ?? ""
|
if (!state.done) {
|
||||||
cache.set(systemId, sysName)
|
state.result = fn(...args)
|
||||||
return sysName
|
state.done = true
|
||||||
}
|
}
|
||||||
})()
|
return state.result
|
||||||
|
}) as T
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: ar\n"
|
"Language: ar\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2025-08-25 01:15\n"
|
"PO-Revision-Date: 2025-08-28 23:21\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"
|
||||||
@@ -23,16 +23,22 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# يوم} other {# أيام}}"
|
msgstr "{0, plural, one {# يوم} other {# أيام}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {# ساعة} other {# ساعات}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr "{0, plural, one {# دقيقة} few {# دقائق} many {# دقيقة} other {# دقيقة}}"
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr "تم تحديد {0} من {1} صف"
|
msgstr "تم تحديد {0} من {1} صف"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {# ساعة} other {# ساعات}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "1 ساعة"
|
msgstr "1 ساعة"
|
||||||
@@ -125,6 +131,7 @@ msgstr "التنبيهات"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "جميع الأنظمة"
|
msgstr "جميع الأنظمة"
|
||||||
|
|
||||||
@@ -252,6 +259,10 @@ msgstr "تحقق من السجلات لمزيد من التفاصيل."
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "تحقق من خدمة الإشعارات الخاصة بك"
|
msgstr "تحقق من خدمة الإشعارات الخاصة بك"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr "انقر على نظام لعرض مزيد من المعلومات."
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "انقر للنسخ"
|
msgstr "انقر للنسخ"
|
||||||
@@ -426,6 +437,10 @@ msgstr "التوثيق"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr "معطل"
|
msgstr "معطل"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr "معطل ({downSystemsLength})"
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr "المدة"
|
msgstr "المدة"
|
||||||
@@ -492,7 +507,7 @@ msgstr "تصدير تكوين الأنظمة الحالية الخاصة بك."
|
|||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "فهرنهايت (°ف)"
|
msgstr "فهرنهايت (°ف)"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "فشل في المصادقة"
|
msgstr "فشل في المصادقة"
|
||||||
|
|
||||||
@@ -753,6 +768,10 @@ msgstr "إيقاف مؤقت"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "متوقف مؤقتا"
|
msgstr "متوقف مؤقتا"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr "متوقف مؤقتا ({pausedSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr "يرجى <0>تكوين خادم SMTP</0> لضمان تسليم التنبيهات."
|
msgstr "يرجى <0>تكوين خادم SMTP</0> لضمان تسليم التنبيهات."
|
||||||
@@ -774,7 +793,7 @@ msgstr "يرجى إنشاء حساب مسؤول"
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr "يرجى تمكين النوافذ المنبثقة لهذا الموقع"
|
msgstr "يرجى تمكين النوافذ المنبثقة لهذا الموقع"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "يرجى تسجيل الدخول مرة أخرى"
|
msgstr "يرجى تسجيل الدخول مرة أخرى"
|
||||||
|
|
||||||
@@ -901,6 +920,7 @@ msgstr "الترتيب حسب"
|
|||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr "الحالة"
|
msgstr "الحالة"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "الحالة"
|
msgstr "الحالة"
|
||||||
@@ -1055,6 +1075,7 @@ msgstr "يتم التفعيل عندما يتجاوز استخدام أي قرص
|
|||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr "تفضيلات الوحدة"
|
msgstr "تفضيلات الوحدة"
|
||||||
|
|
||||||
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Universal token"
|
msgid "Universal token"
|
||||||
msgstr "رمز مميز عالمي"
|
msgstr "رمز مميز عالمي"
|
||||||
@@ -1071,8 +1092,8 @@ msgid "Up"
|
|||||||
msgstr "قيد التشغيل"
|
msgstr "قيد التشغيل"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "محدث في الوقت الحقيقي. انقر على نظام لعرض المعلومات."
|
msgstr "قيد التشغيل ({upSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
@@ -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: 2025-08-25 01:16\n"
|
"PO-Revision-Date: 2025-08-28 23:21\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"
|
||||||
@@ -23,16 +23,22 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# ден} other {# дни}}"
|
msgstr "{0, plural, one {# ден} other {# дни}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {# час} other {# часа}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr "{0, plural, one {# минута} few {# минути} many {# минути} other {# минути}}"
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr "{0} от {1} селектирани."
|
msgstr "{0} от {1} селектирани."
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {# час} other {# часа}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "1 час"
|
msgstr "1 час"
|
||||||
@@ -125,6 +131,7 @@ msgstr "Тревоги"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "Всички системи"
|
msgstr "Всички системи"
|
||||||
|
|
||||||
@@ -252,6 +259,10 @@ msgstr "Провери log-овете за повече информация."
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "Провери услугата си за удостоверяване"
|
msgstr "Провери услугата си за удостоверяване"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr "Кликнете върху система, за да видите повече информация."
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "Настисни за да копираш"
|
msgstr "Настисни за да копираш"
|
||||||
@@ -426,6 +437,10 @@ msgstr "Документация"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr "Офлайн"
|
msgstr "Офлайн"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr "Офлайн ({downSystemsLength})"
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr "Продължителност"
|
msgstr "Продължителност"
|
||||||
@@ -492,7 +507,7 @@ msgstr "Експортирай конфигурацията на системи
|
|||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "Фаренхайт (°F)"
|
msgstr "Фаренхайт (°F)"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "Неуспешно удостоверяване"
|
msgstr "Неуспешно удостоверяване"
|
||||||
|
|
||||||
@@ -753,6 +768,10 @@ msgstr "Пауза"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "На пауза"
|
msgstr "На пауза"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr "На пауза ({pausedSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr "Моля <0>конфигурурай SMTP сървър</0> за да се подсигуриш, че тревогите са доставени."
|
msgstr "Моля <0>конфигурурай SMTP сървър</0> за да се подсигуриш, че тревогите са доставени."
|
||||||
@@ -774,7 +793,7 @@ msgstr "Моля създай администраторски акаунт"
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr "Моля активирай изскачащите прозорци за този сайт"
|
msgstr "Моля активирай изскачащите прозорци за този сайт"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "Моля влез отново"
|
msgstr "Моля влез отново"
|
||||||
|
|
||||||
@@ -901,6 +920,7 @@ msgstr "Сортиране по"
|
|||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr "Състояние"
|
msgstr "Състояние"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Статус"
|
msgstr "Статус"
|
||||||
@@ -1055,6 +1075,7 @@ msgstr "Задейства се, когато употребата на няко
|
|||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr "Предпочитания на единицата"
|
msgstr "Предпочитания на единицата"
|
||||||
|
|
||||||
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Universal token"
|
msgid "Universal token"
|
||||||
msgstr "Универсален тоукън"
|
msgstr "Универсален тоукън"
|
||||||
@@ -1071,8 +1092,8 @@ msgid "Up"
|
|||||||
msgstr "Нагоре"
|
msgstr "Нагоре"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "Актуализира се в реално време. Натисни на система за да видиш информация."
|
msgstr "Нагоре ({upSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: cs\n"
|
"Language: cs\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2025-08-25 01:16\n"
|
"PO-Revision-Date: 2025-08-28 23:21\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"
|
||||||
@@ -23,16 +23,22 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# den} few {# dny} other {# dní}}"
|
msgstr "{0, plural, one {# den} few {# dny} other {# dní}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {# Hodina} few {# Hodiny} many {# Hodin} other {# Hodin}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr "{0, plural, one {# minuta} few {# minuty} many {# minut} other {# minut}}"
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr "{0} z {1} vybraných řádků."
|
msgstr "{0} z {1} vybraných řádků."
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {# Hodina} few {# Hodiny} many {# Hodin} other {# Hodin}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "1 hodina"
|
msgstr "1 hodina"
|
||||||
@@ -125,6 +131,7 @@ msgstr "Výstrahy"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "Všechny systémy"
|
msgstr "Všechny systémy"
|
||||||
|
|
||||||
@@ -252,6 +259,10 @@ msgstr "Pro více informací zkontrolujte logy."
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "Zkontrolujte službu upozornění"
|
msgstr "Zkontrolujte službu upozornění"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr "Klikněte na systém pro zobrazení více informací."
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "Klikněte pro zkopírování"
|
msgstr "Klikněte pro zkopírování"
|
||||||
@@ -426,6 +437,10 @@ msgstr "Dokumentace"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr "Nefunkční"
|
msgstr "Nefunkční"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr "Nefunkční ({downSystemsLength})"
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr "Doba trvání"
|
msgstr "Doba trvání"
|
||||||
@@ -492,7 +507,7 @@ msgstr "Exportovat aktuální konfiguraci systémů."
|
|||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "Fahrenheita (°F)"
|
msgstr "Fahrenheita (°F)"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "Ověření se nezdařilo"
|
msgstr "Ověření se nezdařilo"
|
||||||
|
|
||||||
@@ -753,6 +768,10 @@ msgstr "Pozastavit"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "Pozastaveno"
|
msgstr "Pozastaveno"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr "Pozastaveno ({pausedSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr "<0>nakonfigurujte SMTP server</0> pro zajištění toho, aby byla upozornění doručena."
|
msgstr "<0>nakonfigurujte SMTP server</0> pro zajištění toho, aby byla upozornění doručena."
|
||||||
@@ -774,7 +793,7 @@ msgstr "Vytvořte si prosím účet administrátora"
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr "Prosím povolte vyskakovací okna pro tento web"
|
msgstr "Prosím povolte vyskakovací okna pro tento web"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "Přihlaste se prosím znovu"
|
msgstr "Přihlaste se prosím znovu"
|
||||||
|
|
||||||
@@ -901,6 +920,7 @@ msgstr "Seřadit podle"
|
|||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr "Stav"
|
msgstr "Stav"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Stav"
|
msgstr "Stav"
|
||||||
@@ -1055,6 +1075,7 @@ msgstr "Spustí se, když využití disku překročí prahovou hodnotu"
|
|||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr "Předvolby jednotek"
|
msgstr "Předvolby jednotek"
|
||||||
|
|
||||||
|
#: 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 "Univerzální token"
|
msgstr "Univerzální token"
|
||||||
@@ -1071,8 +1092,8 @@ msgid "Up"
|
|||||||
msgstr "Funkční"
|
msgstr "Funkční"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "Aktualizováno v reálném čase. Klepnutím na systém zobrazíte informace."
|
msgstr "Funkční ({upSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: da\n"
|
"Language: da\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2025-08-25 01:16\n"
|
"PO-Revision-Date: 2025-08-28 23:21\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"
|
||||||
@@ -23,16 +23,22 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# day} other {# days}}"
|
msgstr "{0, plural, one {# day} other {# days}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "1 time"
|
msgstr "1 time"
|
||||||
@@ -125,6 +131,7 @@ msgstr "Alarmer"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "Alle systemer"
|
msgstr "Alle systemer"
|
||||||
|
|
||||||
@@ -252,6 +259,10 @@ msgstr "Tjek logfiler for flere detaljer."
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "Tjek din notifikationstjeneste"
|
msgstr "Tjek din notifikationstjeneste"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "Klik for at kopiere"
|
msgstr "Klik for at kopiere"
|
||||||
@@ -426,6 +437,10 @@ msgstr "Dokumentation"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr "Nede"
|
msgstr "Nede"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -492,7 +507,7 @@ msgstr "Eksporter din nuværende systemkonfiguration."
|
|||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "Kunne ikke godkende"
|
msgstr "Kunne ikke godkende"
|
||||||
|
|
||||||
@@ -753,6 +768,10 @@ msgstr "Pause"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "Sat på pause"
|
msgstr "Sat på pause"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr "Konfigurer <0>en SMTP server</0> for at sikre at alarmer bliver leveret."
|
msgstr "Konfigurer <0>en SMTP server</0> for at sikre at alarmer bliver leveret."
|
||||||
@@ -774,7 +793,7 @@ msgstr "Opret venligst en administratorkonto"
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr "Aktiver pop-ups for dette websted"
|
msgstr "Aktiver pop-ups for dette websted"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "Log venligst ind igen"
|
msgstr "Log venligst ind igen"
|
||||||
|
|
||||||
@@ -901,6 +920,7 @@ msgstr "Sorter efter"
|
|||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Status"
|
msgstr "Status"
|
||||||
@@ -1055,6 +1075,7 @@ msgstr "Udløser når brugen af en disk overstiger en tærskel"
|
|||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Universal token"
|
msgid "Universal token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1071,8 +1092,8 @@ msgid "Up"
|
|||||||
msgstr "Oppe"
|
msgstr "Oppe"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "Opdateret i realtid. Klik på et system for at se information."
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: de\n"
|
"Language: de\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2025-08-25 01:15\n"
|
"PO-Revision-Date: 2025-08-28 23:21\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"
|
||||||
@@ -23,16 +23,22 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# Tag} other {# Tage}}"
|
msgstr "{0, plural, one {# Tag} other {# Tage}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {# Stunde} other {# Stunden}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr "{0, plural, one {# Minute} other {# Minuten}}"
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr "{0} von {1} Zeile(n) ausgewählt."
|
msgstr "{0} von {1} Zeile(n) ausgewählt."
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {# Stunde} other {# Stunden}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "1 Stunde"
|
msgstr "1 Stunde"
|
||||||
@@ -125,6 +131,7 @@ msgstr "Warnungen"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "Alle Systeme"
|
msgstr "Alle Systeme"
|
||||||
|
|
||||||
@@ -252,6 +259,10 @@ msgstr "Überprüfe die Protokolle für weitere Details."
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "Überprüfe deinen Benachrichtigungsdienst"
|
msgstr "Überprüfe deinen Benachrichtigungsdienst"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr "Klicke auf ein System, um weitere Informationen zu sehen."
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "Zum Kopieren klicken"
|
msgstr "Zum Kopieren klicken"
|
||||||
@@ -426,6 +437,10 @@ msgstr "Dokumentation"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr "Offline"
|
msgstr "Offline"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr "Offline ({downSystemsLength})"
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr "Dauer"
|
msgstr "Dauer"
|
||||||
@@ -492,7 +507,7 @@ msgstr "Exportiere die aktuelle Systemkonfiguration."
|
|||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "Fahrenheit (°F)"
|
msgstr "Fahrenheit (°F)"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "Authentifizierung fehlgeschlagen"
|
msgstr "Authentifizierung fehlgeschlagen"
|
||||||
|
|
||||||
@@ -601,7 +616,7 @@ msgstr "Durchschnittliche Systemlast 5 Min"
|
|||||||
#. Short label for load average
|
#. Short label for load average
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Load Avg"
|
msgid "Load Avg"
|
||||||
msgstr "Durchschnittliche Last"
|
msgstr "Systemlast"
|
||||||
|
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Log Out"
|
msgid "Log Out"
|
||||||
@@ -753,6 +768,10 @@ msgstr "Pause"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "Pausiert"
|
msgstr "Pausiert"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr "Pausiert ({pausedSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr "Bitte <0>konfiguriere einen SMTP-Server</0>, um sicherzustellen, dass Warnungen zugestellt werden."
|
msgstr "Bitte <0>konfiguriere einen SMTP-Server</0>, um sicherzustellen, dass Warnungen zugestellt werden."
|
||||||
@@ -774,7 +793,7 @@ msgstr "Bitte erstelle ein Administratorkonto"
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr "Bitte aktiviere Pop-ups für diese Seite"
|
msgstr "Bitte aktiviere Pop-ups für diese Seite"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "Bitte melde dich erneut an"
|
msgstr "Bitte melde dich erneut an"
|
||||||
|
|
||||||
@@ -901,6 +920,7 @@ msgstr "Sortieren nach"
|
|||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr "Status"
|
msgstr "Status"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Status"
|
msgstr "Status"
|
||||||
@@ -1055,6 +1075,7 @@ msgstr "Löst aus, wenn die Nutzung einer Festplatte einen Schwellenwert übersc
|
|||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr "Einheiten"
|
msgstr "Einheiten"
|
||||||
|
|
||||||
|
#: 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 "Universeller Token"
|
msgstr "Universeller Token"
|
||||||
@@ -1071,8 +1092,8 @@ msgid "Up"
|
|||||||
msgstr "aktiv"
|
msgstr "aktiv"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "In Echtzeit aktualisiert. Klicke auf ein System, um Informationen anzuzeigen."
|
msgstr "aktiv ({upSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
@@ -18,16 +18,22 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# day} other {# days}}"
|
msgstr "{0, plural, one {# day} other {# days}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr "{0} of {1} row(s) selected."
|
msgstr "{0} of {1} row(s) selected."
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "1 hour"
|
msgstr "1 hour"
|
||||||
@@ -120,6 +126,7 @@ msgstr "Alerts"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "All Systems"
|
msgstr "All Systems"
|
||||||
|
|
||||||
@@ -247,6 +254,10 @@ msgstr "Check logs for more details."
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "Check your notification service"
|
msgstr "Check your notification service"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr "Click on a system to view more information."
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "Click to copy"
|
msgstr "Click to copy"
|
||||||
@@ -421,6 +432,10 @@ msgstr "Documentation"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr "Down"
|
msgstr "Down"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr "Down ({downSystemsLength})"
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr "Duration"
|
msgstr "Duration"
|
||||||
@@ -487,7 +502,7 @@ msgstr "Export your current systems configuration."
|
|||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "Fahrenheit (°F)"
|
msgstr "Fahrenheit (°F)"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "Failed to authenticate"
|
msgstr "Failed to authenticate"
|
||||||
|
|
||||||
@@ -748,6 +763,10 @@ msgstr "Pause"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "Paused"
|
msgstr "Paused"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr "Paused ({pausedSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgstr "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
@@ -769,7 +788,7 @@ msgstr "Please create an admin account"
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr "Please enable pop-ups for this site"
|
msgstr "Please enable pop-ups for this site"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "Please log in again"
|
msgstr "Please log in again"
|
||||||
|
|
||||||
@@ -896,6 +915,7 @@ msgstr "Sort By"
|
|||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr "State"
|
msgstr "State"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Status"
|
msgstr "Status"
|
||||||
@@ -1050,6 +1070,7 @@ msgstr "Triggers when usage of any disk exceeds a threshold"
|
|||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr "Unit preferences"
|
msgstr "Unit preferences"
|
||||||
|
|
||||||
|
#: 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 "Universal token"
|
||||||
@@ -1066,8 +1087,8 @@ msgid "Up"
|
|||||||
msgstr "Up"
|
msgstr "Up"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "Updated in real time. Click on a system to view information."
|
msgstr "Up ({upSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: es\n"
|
"Language: es\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2025-07-25 22:44\n"
|
"PO-Revision-Date: 2025-08-28 23:21\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"
|
||||||
@@ -23,16 +23,22 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# día} other {# días}}"
|
msgstr "{0, plural, one {# día} other {# días}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {# hora} other {# horas}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr "{0, plural, one {# minuto} other {# minutos}}"
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr "{0} de {1} fila(s) seleccionada(s)."
|
msgstr "{0} de {1} fila(s) seleccionada(s)."
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {# hora} other {# horas}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "1 hora"
|
msgstr "1 hora"
|
||||||
@@ -40,7 +46,7 @@ msgstr "1 hora"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/charts/load-average-chart.tsx
|
#: src/components/charts/load-average-chart.tsx
|
||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr ""
|
msgstr "1 min"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
@@ -53,7 +59,7 @@ msgstr "12 horas"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/charts/load-average-chart.tsx
|
#: src/components/charts/load-average-chart.tsx
|
||||||
msgid "15 min"
|
msgid "15 min"
|
||||||
msgstr ""
|
msgstr "15 min"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "24 hours"
|
msgid "24 hours"
|
||||||
@@ -66,7 +72,7 @@ msgstr "30 días"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/charts/load-average-chart.tsx
|
#: src/components/charts/load-average-chart.tsx
|
||||||
msgid "5 min"
|
msgid "5 min"
|
||||||
msgstr ""
|
msgstr "5 min"
|
||||||
|
|
||||||
#. Table column
|
#. Table column
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
@@ -125,6 +131,7 @@ msgstr "Alertas"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "Todos los Sistemas"
|
msgstr "Todos los Sistemas"
|
||||||
|
|
||||||
@@ -195,12 +202,12 @@ msgstr "Binario"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bits (Kbps, Mbps, Gbps)"
|
msgid "Bits (Kbps, Mbps, Gbps)"
|
||||||
msgstr ""
|
msgstr "Bits (Kbps, Mbps, Gbps)"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bytes (KB/s, MB/s, GB/s)"
|
msgid "Bytes (KB/s, MB/s, GB/s)"
|
||||||
msgstr ""
|
msgstr "Bytes (KB/s, MB/s, GB/s)"
|
||||||
|
|
||||||
#: src/components/charts/mem-chart.tsx
|
#: src/components/charts/mem-chart.tsx
|
||||||
msgid "Cache / Buffers"
|
msgid "Cache / Buffers"
|
||||||
@@ -217,7 +224,7 @@ msgstr "Precaución - posible pérdida de datos"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr ""
|
msgstr "Celsius (°C)"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
@@ -252,6 +259,10 @@ msgstr "Revise los registros para más detalles."
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "Verifique su servicio de notificaciones"
|
msgstr "Verifique su servicio de notificaciones"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr "Haga clic en un sistema para ver más información."
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "Haga clic para copiar"
|
msgstr "Haga clic para copiar"
|
||||||
@@ -426,6 +437,10 @@ msgstr "Documentación"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr "Abajo"
|
msgstr "Abajo"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr "Abajo ({downSystemsLength})"
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr "Duración"
|
msgstr "Duración"
|
||||||
@@ -490,9 +505,9 @@ msgstr "Exporte la configuración actual de sus sistemas."
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr ""
|
msgstr "Fahrenheit (°F)"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "Error al autenticar"
|
msgstr "Error al autenticar"
|
||||||
|
|
||||||
@@ -753,6 +768,10 @@ msgstr "Pausar"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "Pausado"
|
msgstr "Pausado"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr "Pausado ({pausedSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr "Por favor, <0>configure un servidor SMTP</0> para asegurar que las alertas sean entregadas."
|
msgstr "Por favor, <0>configure un servidor SMTP</0> para asegurar que las alertas sean entregadas."
|
||||||
@@ -774,7 +793,7 @@ msgstr "Por favor, cree una cuenta de administrador"
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr "Por favor, habilite las ventanas emergentes para este sitio"
|
msgstr "Por favor, habilite las ventanas emergentes para este sitio"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "Por favor, inicie sesión de nuevo"
|
msgstr "Por favor, inicie sesión de nuevo"
|
||||||
|
|
||||||
@@ -901,6 +920,7 @@ msgstr "Ordenar por"
|
|||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr "Estado"
|
msgstr "Estado"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Estado"
|
msgstr "Estado"
|
||||||
@@ -998,7 +1018,7 @@ msgstr "Alternar tema"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr ""
|
msgstr "Token"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -1055,6 +1075,7 @@ msgstr "Se activa cuando el uso de cualquier disco supera un umbral"
|
|||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr "Preferencias de unidad"
|
msgstr "Preferencias de unidad"
|
||||||
|
|
||||||
|
#: 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 "Token universal"
|
msgstr "Token universal"
|
||||||
@@ -1071,8 +1092,8 @@ msgid "Up"
|
|||||||
msgstr "Activo"
|
msgstr "Activo"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "Actualizado en tiempo real. Haga clic en un sistema para ver la información."
|
msgstr "Activo ({upSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: fa\n"
|
"Language: fa\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2025-07-25 22:44\n"
|
"PO-Revision-Date: 2025-08-28 23:21\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"
|
||||||
@@ -23,16 +23,22 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# روز} other {# روز}}"
|
msgstr "{0, plural, one {# روز} other {# روز}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {# ساعت} other {# ساعت}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr "{0, plural, one {# دقیقه} few {# دقیقه} many {# دقیقه} other {# دقیقه}}"
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr "{0} از {1} ردیف انتخاب شده است."
|
msgstr "{0} از {1} ردیف انتخاب شده است."
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {# ساعت} other {# ساعت}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "۱ ساعت"
|
msgstr "۱ ساعت"
|
||||||
@@ -125,6 +131,7 @@ msgstr "هشدارها"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "همه سیستمها"
|
msgstr "همه سیستمها"
|
||||||
|
|
||||||
@@ -252,6 +259,10 @@ msgstr "برای جزئیات بیشتر، لاگها را بررسی کنی
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "سرویس اطلاعرسانی خود را بررسی کنید"
|
msgstr "سرویس اطلاعرسانی خود را بررسی کنید"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr "برای مشاهده اطلاعات بیشتر روی یک سیستم کلیک کنید."
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "برای کپی کردن کلیک کنید"
|
msgstr "برای کپی کردن کلیک کنید"
|
||||||
@@ -426,6 +437,10 @@ msgstr "مستندات"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr "قطع"
|
msgstr "قطع"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr "قطع ({downSystemsLength})"
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr "مدت زمان"
|
msgstr "مدت زمان"
|
||||||
@@ -492,7 +507,7 @@ msgstr "پیکربندی سیستمهای فعلی خود را خارج کن
|
|||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "فارنهایت (°F)"
|
msgstr "فارنهایت (°F)"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "احراز هویت ناموفق بود"
|
msgstr "احراز هویت ناموفق بود"
|
||||||
|
|
||||||
@@ -753,6 +768,10 @@ msgstr "توقف"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "مکث شده"
|
msgstr "مکث شده"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr "مکث شده ({pausedSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr "لطفاً برای اطمینان از تحویل هشدارها، یک <0>سرور SMTP پیکربندی کنید</0>."
|
msgstr "لطفاً برای اطمینان از تحویل هشدارها، یک <0>سرور SMTP پیکربندی کنید</0>."
|
||||||
@@ -774,7 +793,7 @@ msgstr "لطفاً یک حساب مدیر ایجاد کنید"
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr "لطفاً پنجرههای بازشو را برای این سایت فعال کنید"
|
msgstr "لطفاً پنجرههای بازشو را برای این سایت فعال کنید"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "لطفاً دوباره وارد شوید"
|
msgstr "لطفاً دوباره وارد شوید"
|
||||||
|
|
||||||
@@ -901,6 +920,7 @@ msgstr "مرتبسازی بر اساس"
|
|||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr "وضعیت"
|
msgstr "وضعیت"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "وضعیت"
|
msgstr "وضعیت"
|
||||||
@@ -1055,6 +1075,7 @@ msgstr "هنگامی که استفاده از هر دیسکی از یک آستا
|
|||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr "تنظیمات واحدها"
|
msgstr "تنظیمات واحدها"
|
||||||
|
|
||||||
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Universal token"
|
msgid "Universal token"
|
||||||
msgstr "توکن جهانی"
|
msgstr "توکن جهانی"
|
||||||
@@ -1071,8 +1092,8 @@ msgid "Up"
|
|||||||
msgstr "فعال"
|
msgstr "فعال"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "به صورت لحظهای بهروزرسانی میشود. برای مشاهده اطلاعات، روی یک سیستم کلیک کنید."
|
msgstr "فعال ({upSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: fr\n"
|
"Language: fr\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2025-07-25 22:44\n"
|
"PO-Revision-Date: 2025-08-28 23:21\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"
|
||||||
@@ -23,16 +23,22 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# jour} other {# jours}}"
|
msgstr "{0, plural, one {# jour} other {# jours}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {# heure} other {# heures}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {# heure} other {# heures}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "1 heure"
|
msgstr "1 heure"
|
||||||
@@ -125,6 +131,7 @@ msgstr "Alertes"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "Tous les systèmes"
|
msgstr "Tous les systèmes"
|
||||||
|
|
||||||
@@ -252,6 +259,10 @@ msgstr "Vérifiez les journaux pour plus de détails."
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "Vérifiez votre service de notification"
|
msgstr "Vérifiez votre service de notification"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "Cliquez pour copier"
|
msgstr "Cliquez pour copier"
|
||||||
@@ -426,6 +437,10 @@ msgstr "Documentation"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr "Injoignable"
|
msgstr "Injoignable"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -492,7 +507,7 @@ msgstr "Exportez la configuration actuelle de vos systèmes."
|
|||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "Échec de l'authentification"
|
msgstr "Échec de l'authentification"
|
||||||
|
|
||||||
@@ -753,6 +768,10 @@ msgstr "Pause"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "En pause"
|
msgstr "En pause"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr "Veuillez <0>configurer un serveur SMTP</0> pour garantir la livraison des alertes."
|
msgstr "Veuillez <0>configurer un serveur SMTP</0> pour garantir la livraison des alertes."
|
||||||
@@ -774,7 +793,7 @@ msgstr "Veuillez créer un compte administrateur"
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr "Veuillez activer les pop-ups pour ce site"
|
msgstr "Veuillez activer les pop-ups pour ce site"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "Veuillez vous reconnecter"
|
msgstr "Veuillez vous reconnecter"
|
||||||
|
|
||||||
@@ -901,6 +920,7 @@ msgstr "Trier par"
|
|||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Statut"
|
msgstr "Statut"
|
||||||
@@ -1055,6 +1075,7 @@ msgstr "Déclenchement lorsque l'utilisation de tout disque dépasse un seuil"
|
|||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: 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 "Token universel"
|
msgstr "Token universel"
|
||||||
@@ -1071,8 +1092,8 @@ msgid "Up"
|
|||||||
msgstr "Joignable"
|
msgstr "Joignable"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "Mis à jour en temps réel. Cliquez sur un système pour voir les informations."
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: hr\n"
|
"Language: hr\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2025-08-25 01:16\n"
|
"PO-Revision-Date: 2025-08-28 23:21\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"
|
||||||
@@ -23,16 +23,22 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# dan} other {# dani}}"
|
msgstr "{0, plural, one {# dan} other {# dani}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {# sat} other {# sati}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {# sat} other {# sati}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "1 sat"
|
msgstr "1 sat"
|
||||||
@@ -125,6 +131,7 @@ msgstr "Upozorenja"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "Svi Sistemi"
|
msgstr "Svi Sistemi"
|
||||||
|
|
||||||
@@ -252,6 +259,10 @@ msgstr "Provjerite logove za više detalja."
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "Provjerite Vaš servis notifikacija"
|
msgstr "Provjerite Vaš servis notifikacija"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "Pritisnite za kopiranje"
|
msgstr "Pritisnite za kopiranje"
|
||||||
@@ -426,6 +437,10 @@ msgstr "Dokumentacija"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -492,7 +507,7 @@ msgstr "Izvoz trenutne sistemske konfiguracije."
|
|||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "Provjera autentičnosti nije uspjela"
|
msgstr "Provjera autentičnosti nije uspjela"
|
||||||
|
|
||||||
@@ -753,6 +768,10 @@ msgstr "Pauza"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "Pauzirano"
|
msgstr "Pauzirano"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr "Molimo <0>konfigurirajte SMTP server</0> kako biste osigurali isporuku upozorenja."
|
msgstr "Molimo <0>konfigurirajte SMTP server</0> kako biste osigurali isporuku upozorenja."
|
||||||
@@ -774,7 +793,7 @@ msgstr "Molimo kreirajte administratorski račun"
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr "Omogućite skočne prozore za ovu stranicu"
|
msgstr "Omogućite skočne prozore za ovu stranicu"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "Molimo prijavite se ponovno"
|
msgstr "Molimo prijavite se ponovno"
|
||||||
|
|
||||||
@@ -901,6 +920,7 @@ msgstr "Sortiraj po"
|
|||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Status"
|
msgstr "Status"
|
||||||
@@ -1055,6 +1075,7 @@ msgstr "Pokreće se kada iskorištenost bilo kojeg diska premaši prag"
|
|||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Universal token"
|
msgid "Universal token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1071,8 +1092,8 @@ msgid "Up"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "Ažurirano odmah. Kliknite na sistem za više informacija."
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: hu\n"
|
"Language: hu\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2025-07-25 22:44\n"
|
"PO-Revision-Date: 2025-08-28 23:21\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"
|
||||||
@@ -23,16 +23,22 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# nap} other {# nap}}"
|
msgstr "{0, plural, one {# nap} other {# nap}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {# óra} other {# óra}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {# óra} other {# óra}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "1 óra"
|
msgstr "1 óra"
|
||||||
@@ -125,6 +131,7 @@ msgstr "Riasztások"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "Minden rendszer"
|
msgstr "Minden rendszer"
|
||||||
|
|
||||||
@@ -252,6 +259,10 @@ msgstr "Ellenőrizd a naplót a további részletekért."
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "Ellenőrizd az értesítési szolgáltatásodat"
|
msgstr "Ellenőrizd az értesítési szolgáltatásodat"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "Kattints a másoláshoz"
|
msgstr "Kattints a másoláshoz"
|
||||||
@@ -426,6 +437,10 @@ msgstr "Dokumentáció"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -492,7 +507,7 @@ msgstr "Exportálja a jelenlegi rendszerkonfigurációt."
|
|||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "Hitelesítés sikertelen"
|
msgstr "Hitelesítés sikertelen"
|
||||||
|
|
||||||
@@ -753,6 +768,10 @@ msgstr "Szüneteltetés"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "Szüneteltetve"
|
msgstr "Szüneteltetve"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr "Kérjük, <0>konfigurálj egy SMTP szervert</0> az értesítések kézbesítésének biztosítása érdekében."
|
msgstr "Kérjük, <0>konfigurálj egy SMTP szervert</0> az értesítések kézbesítésének biztosítása érdekében."
|
||||||
@@ -774,7 +793,7 @@ msgstr "Kérjük, hozzon létre egy admin fiókot"
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr "Kérjük, engedélyezze a felugró ablakokat ezen az oldalon"
|
msgstr "Kérjük, engedélyezze a felugró ablakokat ezen az oldalon"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "Kérjük jelentkezz be újra"
|
msgstr "Kérjük jelentkezz be újra"
|
||||||
|
|
||||||
@@ -901,6 +920,7 @@ msgstr "Rendezés"
|
|||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Állapot"
|
msgstr "Állapot"
|
||||||
@@ -1055,6 +1075,7 @@ msgstr "Bekapcsol, ha a lemez érzékelő túllép egy küszöbértéket"
|
|||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Universal token"
|
msgid "Universal token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1071,8 +1092,8 @@ msgid "Up"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "Valós időben frissítve. Kattintson egy rendszerre az információk megtekintéséhez."
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: is\n"
|
"Language: is\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2025-07-25 22:44\n"
|
"PO-Revision-Date: 2025-08-28 23:21\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Icelandic\n"
|
"Language-Team: Icelandic\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@@ -23,16 +23,22 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# dagur} other {# dagar}}"
|
msgstr "{0, plural, one {# dagur} other {# dagar}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {# klukkustund} other {# klukkustundir}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {# klukkustund} other {# klukkustundir}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "1 klukkustund"
|
msgstr "1 klukkustund"
|
||||||
@@ -125,6 +131,7 @@ msgstr "Tilkynningar"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "Öll kerfi"
|
msgstr "Öll kerfi"
|
||||||
|
|
||||||
@@ -252,6 +259,10 @@ msgstr "Skoðaðu logga til að sjá meiri upplýsingar."
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "Athugaðu tilkynningaþjónustuna þína"
|
msgstr "Athugaðu tilkynningaþjónustuna þína"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "Smelltu til að afrita"
|
msgstr "Smelltu til að afrita"
|
||||||
@@ -426,6 +437,10 @@ msgstr "Skjal"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -492,7 +507,7 @@ msgstr ""
|
|||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "Villa í auðkenningu"
|
msgstr "Villa í auðkenningu"
|
||||||
|
|
||||||
@@ -753,6 +768,10 @@ msgstr "Pása"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "Í bið"
|
msgstr "Í bið"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -774,7 +793,7 @@ msgstr "Vinsamlegast búðu til admin aðgang"
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "Vinsamlegast skráðu þið inn aftur"
|
msgstr "Vinsamlegast skráðu þið inn aftur"
|
||||||
|
|
||||||
@@ -901,6 +920,7 @@ msgstr "Raða eftir"
|
|||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Staða"
|
msgstr "Staða"
|
||||||
@@ -1055,6 +1075,7 @@ msgstr "Virkjast þegar diska notkun fer yfir þröskuld"
|
|||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Universal token"
|
msgid "Universal token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1071,8 +1092,8 @@ msgid "Up"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "Uppfærist í rauntíma. Veldu kerfi til að skoða upplýsingar."
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: it\n"
|
"Language: it\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2025-08-25 01:16\n"
|
"PO-Revision-Date: 2025-08-28 23:21\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"
|
||||||
@@ -23,16 +23,22 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# giorno} other {# giorni}}"
|
msgstr "{0, plural, one {# giorno} other {# giorni}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {# ora} other {# ore}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr "{0, plural, one {# minuto} other {# minuti}}"
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr "{0} di {1} righe selezionate."
|
msgstr "{0} di {1} righe selezionate."
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {# ora} other {# ore}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "1 ora"
|
msgstr "1 ora"
|
||||||
@@ -125,6 +131,7 @@ msgstr "Avvisi"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "Tutti i Sistemi"
|
msgstr "Tutti i Sistemi"
|
||||||
|
|
||||||
@@ -195,12 +202,12 @@ msgstr "Binario"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bits (Kbps, Mbps, Gbps)"
|
msgid "Bits (Kbps, Mbps, Gbps)"
|
||||||
msgstr ""
|
msgstr "Bit (Kbps, Mbps, Gbps)"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bytes (KB/s, MB/s, GB/s)"
|
msgid "Bytes (KB/s, MB/s, GB/s)"
|
||||||
msgstr ""
|
msgstr "Byte (KB/s, MB/s, GB/s)"
|
||||||
|
|
||||||
#: src/components/charts/mem-chart.tsx
|
#: src/components/charts/mem-chart.tsx
|
||||||
msgid "Cache / Buffers"
|
msgid "Cache / Buffers"
|
||||||
@@ -217,11 +224,11 @@ msgstr "Attenzione - possibile perdita di dati"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr ""
|
msgstr "Celsius (°C)"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
msgstr ""
|
msgstr "Modifica le unità di visualizzazione per le metriche."
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change general application options."
|
msgid "Change general application options."
|
||||||
@@ -252,6 +259,10 @@ msgstr "Controlla i log per maggiori dettagli."
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "Controlla il tuo servizio di notifica"
|
msgstr "Controlla il tuo servizio di notifica"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr "Clicca su un sistema per visualizzare più informazioni."
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "Clicca per copiare"
|
msgstr "Clicca per copiare"
|
||||||
@@ -272,7 +283,7 @@ msgstr "Conferma password"
|
|||||||
|
|
||||||
#: src/components/routes/home.tsx
|
#: src/components/routes/home.tsx
|
||||||
msgid "Connection is down"
|
msgid "Connection is down"
|
||||||
msgstr ""
|
msgstr "La connessione è interrotta"
|
||||||
|
|
||||||
#: 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
|
||||||
@@ -346,7 +357,7 @@ msgstr "Crea account"
|
|||||||
#. Context: date created
|
#. Context: date created
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Created"
|
msgid "Created"
|
||||||
msgstr ""
|
msgstr "Creato"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Critical (%)"
|
msgid "Critical (%)"
|
||||||
@@ -390,7 +401,7 @@ msgstr "I/O Disco"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Disk unit"
|
msgid "Disk unit"
|
||||||
msgstr ""
|
msgstr "Unità disco"
|
||||||
|
|
||||||
#: src/components/charts/disk-chart.tsx
|
#: src/components/charts/disk-chart.tsx
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
@@ -426,9 +437,13 @@ msgstr "Documentazione"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr "Offline"
|
msgstr "Offline"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr "Offline ({downSystemsLength})"
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr ""
|
msgstr "Durata"
|
||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
@@ -490,9 +505,9 @@ msgstr "Esporta la configurazione attuale dei tuoi sistemi."
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr ""
|
msgstr "Fahrenheit (°F)"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "Autenticazione fallita"
|
msgstr "Autenticazione fallita"
|
||||||
|
|
||||||
@@ -517,7 +532,7 @@ msgstr "Filtra..."
|
|||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Fingerprint"
|
msgid "Fingerprint"
|
||||||
msgstr ""
|
msgstr "Impronta digitale"
|
||||||
|
|
||||||
#: 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}}"
|
||||||
@@ -671,7 +686,7 @@ msgstr "Traffico di rete delle interfacce pubbliche"
|
|||||||
#. Context: Bytes or bits
|
#. Context: Bytes or bits
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Network unit"
|
msgid "Network unit"
|
||||||
msgstr ""
|
msgstr "Unità rete"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
msgid "No results found."
|
msgid "No results found."
|
||||||
@@ -753,6 +768,10 @@ msgstr "Pausa"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "In pausa"
|
msgstr "In pausa"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr "In pausa ({pausedSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr "Si prega di <0>configurare un server SMTP</0> per garantire la consegna degli avvisi."
|
msgstr "Si prega di <0>configurare un server SMTP</0> per garantire la consegna degli avvisi."
|
||||||
@@ -774,7 +793,7 @@ msgstr "Si prega di creare un account amministratore"
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr "Si prega di abilitare i pop-up per questo sito"
|
msgstr "Si prega di abilitare i pop-up per questo sito"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "Si prega di accedere nuovamente"
|
msgstr "Si prega di accedere nuovamente"
|
||||||
|
|
||||||
@@ -822,7 +841,7 @@ msgstr "Reimposta Password"
|
|||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "Resolved"
|
msgid "Resolved"
|
||||||
msgstr ""
|
msgstr "Risolto"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Resume"
|
msgid "Resume"
|
||||||
@@ -834,7 +853,7 @@ msgstr "Ruota token"
|
|||||||
|
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "Rows per page"
|
msgid "Rows per page"
|
||||||
msgstr ""
|
msgstr "Righe per pagina"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Save address using enter key or comma. Leave blank to disable email notifications."
|
msgid "Save address using enter key or comma. Leave blank to disable email notifications."
|
||||||
@@ -899,8 +918,9 @@ msgstr "Ordina per"
|
|||||||
#. Context: alert state (active or resolved)
|
#. Context: alert state (active or resolved)
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr ""
|
msgstr "Stato"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Stato"
|
msgstr "Stato"
|
||||||
@@ -922,7 +942,7 @@ msgstr "Sistema"
|
|||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "System load averages over time"
|
msgid "System load averages over time"
|
||||||
msgstr ""
|
msgstr "Medie di carico del sistema nel tempo"
|
||||||
|
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Systems"
|
msgid "Systems"
|
||||||
@@ -948,7 +968,7 @@ msgstr "Temperatura"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Temperature unit"
|
msgid "Temperature unit"
|
||||||
msgstr ""
|
msgstr "Unità temperatura"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Temperatures of system sensors"
|
msgid "Temperatures of system sensors"
|
||||||
@@ -972,7 +992,7 @@ msgstr "Questa azione non può essere annullata. Questo eliminerà permanentemen
|
|||||||
|
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "This will permanently delete all selected records from the database."
|
msgid "This will permanently delete all selected records from the database."
|
||||||
msgstr ""
|
msgstr "Questo eliminerà permanentemente tutti i record selezionati dal database."
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Throughput of {extraFsName}"
|
msgid "Throughput of {extraFsName}"
|
||||||
@@ -1016,15 +1036,15 @@ msgstr "I token e le impronte digitali vengono utilizzati per autenticare le con
|
|||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Triggers when 1 minute load average exceeds a threshold"
|
msgid "Triggers when 1 minute load average exceeds a threshold"
|
||||||
msgstr ""
|
msgstr "Si attiva quando la media di carico di 1 minuto supera una soglia"
|
||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Triggers when 15 minute load average exceeds a threshold"
|
msgid "Triggers when 15 minute load average exceeds a threshold"
|
||||||
msgstr ""
|
msgstr "Si attiva quando la media di carico di 15 minuti supera una soglia"
|
||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Triggers when 5 minute load average exceeds a threshold"
|
msgid "Triggers when 5 minute load average exceeds a threshold"
|
||||||
msgstr ""
|
msgstr "Si attiva quando la media di carico di 5 minuti supera una soglia"
|
||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Triggers when any sensor exceeds a threshold"
|
msgid "Triggers when any sensor exceeds a threshold"
|
||||||
@@ -1053,8 +1073,9 @@ msgstr "Attiva quando l'utilizzo di un disco supera una soglia"
|
|||||||
#. Temperature / network units
|
#. Temperature / network units
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr ""
|
msgstr "Preferenze unità"
|
||||||
|
|
||||||
|
#: 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 "Token universale"
|
msgstr "Token universale"
|
||||||
@@ -1071,8 +1092,8 @@ msgid "Up"
|
|||||||
msgstr "Attivo"
|
msgstr "Attivo"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "Aggiornato in tempo reale. Clicca su un sistema per visualizzare le informazioni."
|
msgstr "Attivo ({upSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
@@ -1101,7 +1122,7 @@ msgstr "Utenti"
|
|||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Value"
|
msgid "Value"
|
||||||
msgstr ""
|
msgstr "Valore"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "View"
|
msgid "View"
|
||||||
@@ -1109,7 +1130,7 @@ msgstr "Vista"
|
|||||||
|
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "View your 200 most recent alerts."
|
msgid "View your 200 most recent alerts."
|
||||||
msgstr ""
|
msgstr "Visualizza i tuoi 200 avvisi più recenti."
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Visible Fields"
|
msgid "Visible Fields"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: ja\n"
|
"Language: ja\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2025-08-25 01:15\n"
|
"PO-Revision-Date: 2025-08-28 23:21\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"
|
||||||
@@ -23,16 +23,22 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# 日} other {# 日}}"
|
msgstr "{0, plural, one {# 日} other {# 日}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {# 時間} other {# 時間}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr "{0, plural, one {# 分} few {# 分} many {# 分} other {# 分}}"
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr "{1}行のうち{0}行が選択されました。"
|
msgstr "{1}行のうち{0}行が選択されました。"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {# 時間} other {# 時間}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "1時間"
|
msgstr "1時間"
|
||||||
@@ -125,6 +131,7 @@ msgstr "アラート"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "すべてのシステム"
|
msgstr "すべてのシステム"
|
||||||
|
|
||||||
@@ -252,6 +259,10 @@ msgstr "詳細についてはログを確認してください。"
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "通知サービスを確認してください"
|
msgstr "通知サービスを確認してください"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr "システムをクリックして詳細を表示します。"
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "クリックしてコピー"
|
msgstr "クリックしてコピー"
|
||||||
@@ -426,6 +437,10 @@ msgstr "ドキュメント"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr "停止"
|
msgstr "停止"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr "停止 ({downSystemsLength})"
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr "期間"
|
msgstr "期間"
|
||||||
@@ -492,7 +507,7 @@ msgstr "現在のシステム設定をエクスポートします。"
|
|||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "華氏 (°F)"
|
msgstr "華氏 (°F)"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "認証に失敗しました"
|
msgstr "認証に失敗しました"
|
||||||
|
|
||||||
@@ -753,6 +768,10 @@ msgstr "一時停止"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "一時停止中"
|
msgstr "一時停止中"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr "一時停止 ({pausedSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr "アラートが配信されるように<0>SMTPサーバーを設定</0>してください。"
|
msgstr "アラートが配信されるように<0>SMTPサーバーを設定</0>してください。"
|
||||||
@@ -774,7 +793,7 @@ msgstr "管理者アカウントを作成してください"
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr "このサイトのポップアップを有効にしてください"
|
msgstr "このサイトのポップアップを有効にしてください"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "再度ログインしてください"
|
msgstr "再度ログインしてください"
|
||||||
|
|
||||||
@@ -901,6 +920,7 @@ msgstr "並び替え基準"
|
|||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr "状態"
|
msgstr "状態"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "ステータス"
|
msgstr "ステータス"
|
||||||
@@ -1055,6 +1075,7 @@ msgstr "ディスクの使用量がしきい値を超えたときにトリガー
|
|||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr "単位の設定"
|
msgstr "単位の設定"
|
||||||
|
|
||||||
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Universal token"
|
msgid "Universal token"
|
||||||
msgstr "ユニバーサルトークン"
|
msgstr "ユニバーサルトークン"
|
||||||
@@ -1071,8 +1092,8 @@ msgid "Up"
|
|||||||
msgstr "正常"
|
msgstr "正常"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "リアルタイムで更新されます。システムをクリックして情報を表示します。"
|
msgstr "正常 ({upSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
@@ -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: 2025-08-25 01:16\n"
|
"PO-Revision-Date: 2025-08-31 15:44\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"
|
||||||
@@ -23,16 +23,22 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# 일} other {# 일}}"
|
msgstr "{0, plural, one {# 일} other {# 일}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {# 시간} other {# 시간}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr "{0, plural, one {# 분} few {# 분} many {# 분} other {# 분}}"
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr "{1}개의 행 중 {0}개가 선택되었습니다."
|
msgstr "{1}개의 행 중 {0}개가 선택되었습니다."
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {# 시간} other {# 시간}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "1시간"
|
msgstr "1시간"
|
||||||
@@ -125,6 +131,7 @@ msgstr "알림"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "모든 시스템"
|
msgstr "모든 시스템"
|
||||||
|
|
||||||
@@ -252,6 +259,10 @@ msgstr "자세한 내용은 로그를 확인하세요."
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "알림 서비스를 확인하세요."
|
msgstr "알림 서비스를 확인하세요."
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr "더 많은 정보를 보려면 시스템을 클릭하세요."
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "클릭하여 복사"
|
msgstr "클릭하여 복사"
|
||||||
@@ -426,6 +437,10 @@ msgstr "문서"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr "오프라인"
|
msgstr "오프라인"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr "오프라인 ({downSystemsLength})"
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr "기간"
|
msgstr "기간"
|
||||||
@@ -492,7 +507,7 @@ msgstr "현재 시스템 구성 내보내기"
|
|||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "화씨 (°F)"
|
msgstr "화씨 (°F)"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "인증 실패"
|
msgstr "인증 실패"
|
||||||
|
|
||||||
@@ -753,6 +768,10 @@ msgstr "일시 중지"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "일시 정지됨"
|
msgstr "일시 정지됨"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr "일시 정지됨 ({pausedSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr "알림이 전달되도록 <0>SMTP 서버를 구성</0>하세요."
|
msgstr "알림이 전달되도록 <0>SMTP 서버를 구성</0>하세요."
|
||||||
@@ -774,7 +793,7 @@ msgstr "관리자 계정을 생성하세요."
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr "이 사이트에 대해 팝업을 활성화하세요."
|
msgstr "이 사이트에 대해 팝업을 활성화하세요."
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "다시 로그인하세요."
|
msgstr "다시 로그인하세요."
|
||||||
|
|
||||||
@@ -901,6 +920,7 @@ msgstr "정렬 기준"
|
|||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr "상태"
|
msgstr "상태"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "상태"
|
msgstr "상태"
|
||||||
@@ -1055,6 +1075,7 @@ msgstr "디스크 사용량이 임계값을 초과할 때 트리거됩니다."
|
|||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr "단위 기본 설정"
|
msgstr "단위 기본 설정"
|
||||||
|
|
||||||
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Universal token"
|
msgid "Universal token"
|
||||||
msgstr "범용 토큰"
|
msgstr "범용 토큰"
|
||||||
@@ -1071,8 +1092,8 @@ msgid "Up"
|
|||||||
msgstr "온라인"
|
msgstr "온라인"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "실시간으로 업데이트됩니다. 시스템을 클릭하여 정보를 확인하세요."
|
msgstr "온라인 ({upSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
@@ -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: 2025-08-25 01:16\n"
|
"PO-Revision-Date: 2025-08-28 23:21\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"
|
||||||
@@ -23,16 +23,22 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# dag} other {# dagen}}"
|
msgstr "{0, plural, one {# dag} other {# dagen}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {# uur} other {# uren}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr "{0, plural, one {# minuut} other {# minuten}}"
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr "{0} van de {1} rij(en) geselecteerd."
|
msgstr "{0} van de {1} rij(en) geselecteerd."
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {# uur} other {# uren}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "1 uur"
|
msgstr "1 uur"
|
||||||
@@ -125,6 +131,7 @@ msgstr "Waarschuwingen"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "Alle systemen"
|
msgstr "Alle systemen"
|
||||||
|
|
||||||
@@ -252,6 +259,10 @@ msgstr "Controleer de logs voor meer details."
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "Controleer je meldingsservice"
|
msgstr "Controleer je meldingsservice"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr "Klik op een systeem om meer informatie te bekijken."
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "Klik om te kopiëren"
|
msgstr "Klik om te kopiëren"
|
||||||
@@ -426,6 +437,10 @@ msgstr "Documentatie"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr "Offline"
|
msgstr "Offline"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr "Offline ({downSystemsLength})"
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr "Duur"
|
msgstr "Duur"
|
||||||
@@ -492,7 +507,7 @@ msgstr "Exporteer je huidige systeemconfiguratie."
|
|||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "Fahrenheit (°F)"
|
msgstr "Fahrenheit (°F)"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "Authenticatie mislukt"
|
msgstr "Authenticatie mislukt"
|
||||||
|
|
||||||
@@ -753,6 +768,10 @@ msgstr "Pauze"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "Gepauzeerd"
|
msgstr "Gepauzeerd"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr "Gepauzeerd ({pausedSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr "<0>Configureer een SMTP-server </0> om ervoor te zorgen dat waarschuwingen worden afgeleverd."
|
msgstr "<0>Configureer een SMTP-server </0> om ervoor te zorgen dat waarschuwingen worden afgeleverd."
|
||||||
@@ -774,7 +793,7 @@ msgstr "Maak een beheerdersaccount aan"
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr "Activeer pop-ups voor deze website"
|
msgstr "Activeer pop-ups voor deze website"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "Meld je opnieuw aan"
|
msgstr "Meld je opnieuw aan"
|
||||||
|
|
||||||
@@ -901,6 +920,7 @@ msgstr "Sorteren op"
|
|||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr "Status"
|
msgstr "Status"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Status"
|
msgstr "Status"
|
||||||
@@ -1055,6 +1075,7 @@ msgstr "Triggert wanneer het gebruik van een schijf een drempelwaarde overschrij
|
|||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr "Eenheid voorkeuren"
|
msgstr "Eenheid voorkeuren"
|
||||||
|
|
||||||
|
#: 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 "Universele token"
|
msgstr "Universele token"
|
||||||
@@ -1071,8 +1092,8 @@ msgid "Up"
|
|||||||
msgstr "Online"
|
msgstr "Online"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "In realtime bijgewerkt. Klik op een systeem om informatie te bekijken."
|
msgstr "Online ({upSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
@@ -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: 2025-07-25 22:44\n"
|
"PO-Revision-Date: 2025-08-28 23:21\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"
|
||||||
@@ -23,16 +23,22 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# dag} other {# dager}}"
|
msgstr "{0, plural, one {# dag} other {# dager}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {# time} other {# timer}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {# time} other {# timer}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "1 time"
|
msgstr "1 time"
|
||||||
@@ -125,6 +131,7 @@ msgstr "Alarmer"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "Alle Systemer"
|
msgstr "Alle Systemer"
|
||||||
|
|
||||||
@@ -252,6 +259,10 @@ msgstr "Sjekk loggene for flere detaljer."
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "Sjekk din meldingstjeneste"
|
msgstr "Sjekk din meldingstjeneste"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "Klikk for å kopiere"
|
msgstr "Klikk for å kopiere"
|
||||||
@@ -426,6 +437,10 @@ msgstr "Dokumentasjon"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr "Nede"
|
msgstr "Nede"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -492,7 +507,7 @@ msgstr "Eksporter din nåværende systemkonfigurasjon"
|
|||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "Autentisering mislyktes"
|
msgstr "Autentisering mislyktes"
|
||||||
|
|
||||||
@@ -753,6 +768,10 @@ msgstr "Pause"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "Satt på Pause"
|
msgstr "Satt på Pause"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr "Vennligst <0>konfigurer en SMTP-server</0> for å forsikre deg om at varsler blir levert."
|
msgstr "Vennligst <0>konfigurer en SMTP-server</0> for å forsikre deg om at varsler blir levert."
|
||||||
@@ -774,7 +793,7 @@ msgstr "Vennligst opprett en admin-konto"
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr "Vennligst aktiver pop-ups for nettsiden"
|
msgstr "Vennligst aktiver pop-ups for nettsiden"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "Vennligst logg inn på nytt"
|
msgstr "Vennligst logg inn på nytt"
|
||||||
|
|
||||||
@@ -901,6 +920,7 @@ msgstr "Sorter Etter"
|
|||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Status"
|
msgstr "Status"
|
||||||
@@ -1055,6 +1075,7 @@ msgstr "Slår inn når forbruk av hvilken som helst disk overstiger en grensever
|
|||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Universal token"
|
msgid "Universal token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1071,8 +1092,8 @@ msgid "Up"
|
|||||||
msgstr "Oppe"
|
msgstr "Oppe"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "Oppdatert i sanntid. Klikk på et system for å se mer informasjon."
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
@@ -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: 2025-08-13 15:36\n"
|
"PO-Revision-Date: 2025-09-03 18:54\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"
|
||||||
@@ -23,16 +23,22 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# dzień} few {# dni} many {# dni} other {# dni}}"
|
msgstr "{0, plural, one {# dzień} few {# dni} many {# dni} other {# dni}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {godzinę} few {# godziny} many {# godzin} other {# godziny}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr "{0, plural, one {# minuta} few {# minuty} many {# minut} other {# minut}}"
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr "{0} z {1} wybranych wierszy."
|
msgstr "{0} z {1} wybranych wierszy."
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {godzinę} few {# godziny} many {# godzin} other {# godziny}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "1 godzina"
|
msgstr "1 godzina"
|
||||||
@@ -125,6 +131,7 @@ msgstr "Alerty"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "Wszystkie systemy"
|
msgstr "Wszystkie systemy"
|
||||||
|
|
||||||
@@ -186,7 +193,7 @@ msgstr "Beszel obsługuje OpenID Connect i wielu dostawców uwierzytelniania OAu
|
|||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Beszel uses <0>Shoutrrr</0> to integrate with popular notification services."
|
msgid "Beszel uses <0>Shoutrrr</0> to integrate with popular notification services."
|
||||||
msgstr "Beszel używa <0>Shoutrrr</0> do integracji z popularnych serwisami powiadomień."
|
msgstr "Beszel używa <0>Shoutrrr</0> do integracji z popularnych serwisów powiadomień."
|
||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Binary"
|
msgid "Binary"
|
||||||
@@ -252,6 +259,10 @@ msgstr "Sprawdź logi, aby uzyskać więcej informacji."
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "Sprawdź swój serwis powiadomień"
|
msgstr "Sprawdź swój serwis powiadomień"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr "Kliknij na system, aby zobaczyć więcej informacji."
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "Kliknij, aby skopiować"
|
msgstr "Kliknij, aby skopiować"
|
||||||
@@ -426,6 +437,10 @@ msgstr "Dokumentacja"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr "Nie działa"
|
msgstr "Nie działa"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr "Nie działa ({downSystemsLength})"
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr "Czas trwania"
|
msgstr "Czas trwania"
|
||||||
@@ -492,7 +507,7 @@ msgstr "Eksportuj aktualną konfigurację systemów."
|
|||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "Fahrenheit (°F)"
|
msgstr "Fahrenheit (°F)"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "Błąd autoryzacji"
|
msgstr "Błąd autoryzacji"
|
||||||
|
|
||||||
@@ -753,6 +768,10 @@ msgstr "Pauza"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "Wstrzymane"
|
msgstr "Wstrzymane"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr "Wstrzymane ({pausedSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr "Proszę <0>skonfigurować serwer SMTP</0>, aby zapewnić dostarczanie powiadomień."
|
msgstr "Proszę <0>skonfigurować serwer SMTP</0>, aby zapewnić dostarczanie powiadomień."
|
||||||
@@ -774,7 +793,7 @@ msgstr "Utwórz konto administratora"
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr "Włącz wyskakujące okna dla tej strony"
|
msgstr "Włącz wyskakujące okna dla tej strony"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "Zaloguj się ponownie"
|
msgstr "Zaloguj się ponownie"
|
||||||
|
|
||||||
@@ -901,6 +920,7 @@ msgstr "Sortuj według"
|
|||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr "Stan"
|
msgstr "Stan"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Status"
|
msgstr "Status"
|
||||||
@@ -1055,6 +1075,7 @@ msgstr "Wyzwalane, gdy wykorzystanie któregokolwiek dysku przekroczy ustalony p
|
|||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr "Ustawienia jednostek"
|
msgstr "Ustawienia jednostek"
|
||||||
|
|
||||||
|
#: 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 "Uniwersalny token"
|
msgstr "Uniwersalny token"
|
||||||
@@ -1071,8 +1092,8 @@ msgid "Up"
|
|||||||
msgstr "Działa"
|
msgstr "Działa"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "Aktualizowane w czasie rzeczywistym. Kliknij system, aby zobaczyć informacje."
|
msgstr "Działa ({upSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
@@ -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: 2025-08-25 01:16\n"
|
"PO-Revision-Date: 2025-08-28 23:21\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"
|
||||||
@@ -23,15 +23,21 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# dia} other {# dias}}"
|
msgstr "{0, plural, one {# dia} other {# dias}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {# hora} other {# horas}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr "{0, plural, one {# minuto} other {# minutos}}"
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr ""
|
msgstr "{0} de {1} linha(s) selecionada(s)."
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {# hora} other {# horas}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
@@ -125,6 +131,7 @@ msgstr "Alertas"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "Todos os Sistemas"
|
msgstr "Todos os Sistemas"
|
||||||
|
|
||||||
@@ -195,12 +202,12 @@ msgstr "Binário"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bits (Kbps, Mbps, Gbps)"
|
msgid "Bits (Kbps, Mbps, Gbps)"
|
||||||
msgstr ""
|
msgstr "Bits (Kbps, Mbps, Gbps)"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bytes (KB/s, MB/s, GB/s)"
|
msgid "Bytes (KB/s, MB/s, GB/s)"
|
||||||
msgstr ""
|
msgstr "Bytes (KB/s, MB/s, GB/s)"
|
||||||
|
|
||||||
#: src/components/charts/mem-chart.tsx
|
#: src/components/charts/mem-chart.tsx
|
||||||
msgid "Cache / Buffers"
|
msgid "Cache / Buffers"
|
||||||
@@ -217,11 +224,11 @@ msgstr "Cuidado - possível perda de dados"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Celsius (°C)"
|
msgid "Celsius (°C)"
|
||||||
msgstr ""
|
msgstr "Celsius (°C)"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change display units for metrics."
|
msgid "Change display units for metrics."
|
||||||
msgstr ""
|
msgstr "Alterar unidades de exibição para métricas."
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Change general application options."
|
msgid "Change general application options."
|
||||||
@@ -252,6 +259,10 @@ msgstr "Verifique os logs para mais detalhes."
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "Verifique seu serviço de notificação"
|
msgstr "Verifique seu serviço de notificação"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr "Clique em um sistema para ver mais informações."
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "Clique para copiar"
|
msgstr "Clique para copiar"
|
||||||
@@ -272,7 +283,7 @@ msgstr "Confirmar senha"
|
|||||||
|
|
||||||
#: src/components/routes/home.tsx
|
#: src/components/routes/home.tsx
|
||||||
msgid "Connection is down"
|
msgid "Connection is down"
|
||||||
msgstr ""
|
msgstr "A conexão está inativa"
|
||||||
|
|
||||||
#: 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
|
||||||
@@ -390,7 +401,7 @@ msgstr "E/S de Disco"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Disk unit"
|
msgid "Disk unit"
|
||||||
msgstr ""
|
msgstr "Unidade de disco"
|
||||||
|
|
||||||
#: src/components/charts/disk-chart.tsx
|
#: src/components/charts/disk-chart.tsx
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
@@ -426,6 +437,10 @@ msgstr "Documentação"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr "“Desligado”"
|
msgstr "“Desligado”"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr "Inativo ({downSystemsLength})"
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr "Duração"
|
msgstr "Duração"
|
||||||
@@ -490,9 +505,9 @@ msgstr "Exporte a configuração atual dos seus sistemas."
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr ""
|
msgstr "Fahrenheit (°F)"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "Falha na autenticação"
|
msgstr "Falha na autenticação"
|
||||||
|
|
||||||
@@ -517,7 +532,7 @@ msgstr "Filtrar..."
|
|||||||
|
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Fingerprint"
|
msgid "Fingerprint"
|
||||||
msgstr ""
|
msgstr "Impressão digital"
|
||||||
|
|
||||||
#: 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}}"
|
||||||
@@ -584,24 +599,24 @@ msgstr "Aspeto"
|
|||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Load Average"
|
msgid "Load Average"
|
||||||
msgstr ""
|
msgstr "Carga Média"
|
||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Load Average 15m"
|
msgid "Load Average 15m"
|
||||||
msgstr ""
|
msgstr "Carga média 15m"
|
||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Load Average 1m"
|
msgid "Load Average 1m"
|
||||||
msgstr ""
|
msgstr "Carga média 1m"
|
||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Load Average 5m"
|
msgid "Load Average 5m"
|
||||||
msgstr ""
|
msgstr "Carga média 5m"
|
||||||
|
|
||||||
#. Short label for load average
|
#. Short label for load average
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Load Avg"
|
msgid "Load Avg"
|
||||||
msgstr ""
|
msgstr "Carga Média"
|
||||||
|
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Log Out"
|
msgid "Log Out"
|
||||||
@@ -671,7 +686,7 @@ msgstr "Tráfego de rede das interfaces públicas"
|
|||||||
#. Context: Bytes or bits
|
#. Context: Bytes or bits
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Network unit"
|
msgid "Network unit"
|
||||||
msgstr ""
|
msgstr "Unidade de rede"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
msgid "No results found."
|
msgid "No results found."
|
||||||
@@ -722,7 +737,7 @@ msgstr "Página"
|
|||||||
#. placeholder {1}: table.getPageCount()
|
#. placeholder {1}: table.getPageCount()
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "Page {0} of {1}"
|
msgid "Page {0} of {1}"
|
||||||
msgstr ""
|
msgstr "Página {0} de {1}"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
msgid "Pages / Settings"
|
msgid "Pages / Settings"
|
||||||
@@ -753,6 +768,10 @@ msgstr "Pausar"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "Pausado"
|
msgstr "Pausado"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr "Pausado ({pausedSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr "Por favor, <0>configure um servidor SMTP</0> para garantir que os alertas sejam entregues."
|
msgstr "Por favor, <0>configure um servidor SMTP</0> para garantir que os alertas sejam entregues."
|
||||||
@@ -774,7 +793,7 @@ msgstr "Por favor, crie uma conta de administrador"
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr "Por favor, habilite pop-ups para este site"
|
msgstr "Por favor, habilite pop-ups para este site"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "Por favor, faça login novamente"
|
msgstr "Por favor, faça login novamente"
|
||||||
|
|
||||||
@@ -822,7 +841,7 @@ msgstr "Redefinir Senha"
|
|||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "Resolved"
|
msgid "Resolved"
|
||||||
msgstr ""
|
msgstr "Resolvido"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Resume"
|
msgid "Resume"
|
||||||
@@ -899,8 +918,9 @@ msgstr "Ordenar Por"
|
|||||||
#. Context: alert state (active or resolved)
|
#. Context: alert state (active or resolved)
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr ""
|
msgstr "Estado"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Status"
|
msgstr "Status"
|
||||||
@@ -922,7 +942,7 @@ msgstr "Sistema"
|
|||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "System load averages over time"
|
msgid "System load averages over time"
|
||||||
msgstr ""
|
msgstr "Médias de carga do sistema ao longo do tempo"
|
||||||
|
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Systems"
|
msgid "Systems"
|
||||||
@@ -948,7 +968,7 @@ msgstr "Temperatura"
|
|||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Temperature unit"
|
msgid "Temperature unit"
|
||||||
msgstr ""
|
msgstr "Unidade de temperatura"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Temperatures of system sensors"
|
msgid "Temperatures of system sensors"
|
||||||
@@ -972,7 +992,7 @@ msgstr "Esta ação não pode ser desfeita. Isso excluirá permanentemente todos
|
|||||||
|
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "This will permanently delete all selected records from the database."
|
msgid "This will permanently delete all selected records from the database."
|
||||||
msgstr ""
|
msgstr "Isso excluirá permanentemente todos os registros selecionados do banco de dados."
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Throughput of {extraFsName}"
|
msgid "Throughput of {extraFsName}"
|
||||||
@@ -998,7 +1018,7 @@ msgstr "Alternar tema"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr ""
|
msgstr "Token"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -1016,15 +1036,15 @@ msgstr "Tokens e impressões digitais são usados para autenticar conexões WebS
|
|||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Triggers when 1 minute load average exceeds a threshold"
|
msgid "Triggers when 1 minute load average exceeds a threshold"
|
||||||
msgstr ""
|
msgstr "Dispara quando a média de carga de 1 minuto excede um limite"
|
||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Triggers when 15 minute load average exceeds a threshold"
|
msgid "Triggers when 15 minute load average exceeds a threshold"
|
||||||
msgstr ""
|
msgstr "Dispara quando a média de carga de 15 minutos excede um limite"
|
||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Triggers when 5 minute load average exceeds a threshold"
|
msgid "Triggers when 5 minute load average exceeds a threshold"
|
||||||
msgstr ""
|
msgstr "Dispara quando a média de carga de 5 minutos excede um limite"
|
||||||
|
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Triggers when any sensor exceeds a threshold"
|
msgid "Triggers when any sensor exceeds a threshold"
|
||||||
@@ -1053,8 +1073,9 @@ msgstr "Dispara quando o uso de qualquer disco excede um limite"
|
|||||||
#. Temperature / network units
|
#. Temperature / network units
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr ""
|
msgstr "Preferências de unidade"
|
||||||
|
|
||||||
|
#: 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 "Token universal"
|
msgstr "Token universal"
|
||||||
@@ -1071,8 +1092,8 @@ msgid "Up"
|
|||||||
msgstr "“Ligado”"
|
msgstr "“Ligado”"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "Atualizado em tempo real. Clique em um sistema para ver informações."
|
msgstr "Ativo ({upSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
@@ -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: 2025-08-25 02:49\n"
|
"PO-Revision-Date: 2025-08-28 23:21\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"
|
||||||
@@ -23,16 +23,22 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# день} other {# дней}}"
|
msgstr "{0, plural, one {# день} other {# дней}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {# час} other {# часов}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr "Выбрано {0} из {1} строк."
|
msgstr "Выбрано {0} из {1} строк."
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {# час} other {# часов}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "1 час"
|
msgstr "1 час"
|
||||||
@@ -125,6 +131,7 @@ msgstr "Оповещения"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "Все системы"
|
msgstr "Все системы"
|
||||||
|
|
||||||
@@ -252,6 +259,10 @@ msgstr "Проверьте журналы для получения более
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "Проверьте ваш сервис уведомлений"
|
msgstr "Проверьте ваш сервис уведомлений"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "Нажмите, чтобы скопировать"
|
msgstr "Нажмите, чтобы скопировать"
|
||||||
@@ -426,6 +437,10 @@ msgstr "Документация"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr "Не в сети"
|
msgstr "Не в сети"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr "Длительность"
|
msgstr "Длительность"
|
||||||
@@ -492,7 +507,7 @@ msgstr "Экспортируйте текущую конфигурацию си
|
|||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "Фаренгейт (°F)"
|
msgstr "Фаренгейт (°F)"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "Не удалось аутентифицировать"
|
msgstr "Не удалось аутентифицировать"
|
||||||
|
|
||||||
@@ -753,6 +768,10 @@ msgstr "Пауза"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "Пауза"
|
msgstr "Пауза"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr "Пожалуйста, <0>настройте SMTP-сервер</0>, чтобы гарантировать доставку оповещений."
|
msgstr "Пожалуйста, <0>настройте SMTP-сервер</0>, чтобы гарантировать доставку оповещений."
|
||||||
@@ -774,7 +793,7 @@ msgstr "Пожалуйста, создайте учетную запись ад
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr "Пожалуйста, включите всплывающие окна для этого сайта"
|
msgstr "Пожалуйста, включите всплывающие окна для этого сайта"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "Пожалуйста, войдите снова"
|
msgstr "Пожалуйста, войдите снова"
|
||||||
|
|
||||||
@@ -901,6 +920,7 @@ msgstr "Сортировать по"
|
|||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr "Состояние"
|
msgstr "Состояние"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Статус"
|
msgstr "Статус"
|
||||||
@@ -1055,6 +1075,7 @@ msgstr "Срабатывает, когда использование любог
|
|||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr "Параметры единиц измерения"
|
msgstr "Параметры единиц измерения"
|
||||||
|
|
||||||
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Universal token"
|
msgid "Universal token"
|
||||||
msgstr "Универсальный токен"
|
msgstr "Универсальный токен"
|
||||||
@@ -1071,8 +1092,8 @@ msgid "Up"
|
|||||||
msgstr "В сети"
|
msgstr "В сети"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "Обновляется в реальном времени. Нажмите на систему, чтобы просмотреть информацию."
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
@@ -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: 2025-07-25 22:44\n"
|
"PO-Revision-Date: 2025-08-28 23:21\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"
|
||||||
@@ -23,16 +23,22 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# dan} two {# dneva} few {# dni} other {# dni}}"
|
msgstr "{0, plural, one {# dan} two {# dneva} few {# dni} other {# dni}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {# ura} two {# uri} few {# ur} other {# ur}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {# ura} two {# uri} few {# ur} other {# ur}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "1 ura"
|
msgstr "1 ura"
|
||||||
@@ -125,6 +131,7 @@ msgstr "Opozorila"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "Vsi sistemi"
|
msgstr "Vsi sistemi"
|
||||||
|
|
||||||
@@ -252,6 +259,10 @@ msgstr "Za več podrobnosti preverite dnevnike."
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "Preverite storitev obveščanja"
|
msgstr "Preverite storitev obveščanja"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "Klikni za kopiranje"
|
msgstr "Klikni za kopiranje"
|
||||||
@@ -426,6 +437,10 @@ msgstr "Dokumentacija"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -492,7 +507,7 @@ msgstr "Izvozi trenutne nastavitve sistema."
|
|||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "Preverjanje pristnosti ni uspelo"
|
msgstr "Preverjanje pristnosti ni uspelo"
|
||||||
|
|
||||||
@@ -753,6 +768,10 @@ msgstr "Premor"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "Zaustavljeno"
|
msgstr "Zaustavljeno"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr "<0>Nastavite strežnik SMTP</0>, da zagotovite dostavo opozoril."
|
msgstr "<0>Nastavite strežnik SMTP</0>, da zagotovite dostavo opozoril."
|
||||||
@@ -774,7 +793,7 @@ msgstr "Ustvarite skrbniški račun"
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr "Omogočite pojavna okna za to spletno mesto"
|
msgstr "Omogočite pojavna okna za to spletno mesto"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "Prosimo, prijavite se znova"
|
msgstr "Prosimo, prijavite se znova"
|
||||||
|
|
||||||
@@ -901,6 +920,7 @@ msgstr "Razvrsti po"
|
|||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Status"
|
msgstr "Status"
|
||||||
@@ -1055,6 +1075,7 @@ msgstr "Sproži se, ko uporaba katerega koli diska preseže prag"
|
|||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Universal token"
|
msgid "Universal token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1071,8 +1092,8 @@ msgid "Up"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "Posodobljeno v realnem času. Za ogled informacij kliknite na sistem."
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
@@ -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: 2025-08-01 23:21\n"
|
"PO-Revision-Date: 2025-08-28 23:21\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"
|
||||||
@@ -23,16 +23,22 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# dag} other {# dagar}}"
|
msgstr "{0, plural, one {# dag} other {# dagar}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {# timme} other {# timmar}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr "{0} av {1} rad(er) valda."
|
msgstr "{0} av {1} rad(er) valda."
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {# timme} other {# timmar}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "1 timme"
|
msgstr "1 timme"
|
||||||
@@ -125,6 +131,7 @@ msgstr "Larm"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "Alla system"
|
msgstr "Alla system"
|
||||||
|
|
||||||
@@ -252,6 +259,10 @@ msgstr "Kontrollera loggarna för mer information."
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "Kontrollera din aviseringstjänst"
|
msgstr "Kontrollera din aviseringstjänst"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "Klicka för att kopiera"
|
msgstr "Klicka för att kopiera"
|
||||||
@@ -426,6 +437,10 @@ msgstr "Dokumentation"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -492,7 +507,7 @@ msgstr "Exportera din nuvarande systemkonfiguration."
|
|||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "Autentisering misslyckades"
|
msgstr "Autentisering misslyckades"
|
||||||
|
|
||||||
@@ -753,6 +768,10 @@ msgstr "Paus"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "Pausad"
|
msgstr "Pausad"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr "Vänligen <0>konfigurera en SMTP-server</0> för att säkerställa att larm levereras."
|
msgstr "Vänligen <0>konfigurera en SMTP-server</0> för att säkerställa att larm levereras."
|
||||||
@@ -774,7 +793,7 @@ msgstr "Vänligen skapa ett administratörskonto"
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr "Vänligen aktivera popup-fönster för den här webbplatsen"
|
msgstr "Vänligen aktivera popup-fönster för den här webbplatsen"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "Vänligen logga in igen"
|
msgstr "Vänligen logga in igen"
|
||||||
|
|
||||||
@@ -901,6 +920,7 @@ msgstr "Sortera efter"
|
|||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Status"
|
msgstr "Status"
|
||||||
@@ -1055,6 +1075,7 @@ msgstr "Utlöses när användningen av någon disk överskrider ett tröskelvär
|
|||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Universal token"
|
msgid "Universal token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1071,8 +1092,8 @@ msgid "Up"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "Uppdateras i realtid. Klicka på ett system för att visa information."
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
@@ -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: 2025-07-25 22:44\n"
|
"PO-Revision-Date: 2025-08-28 23:21\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"
|
||||||
@@ -23,16 +23,22 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# gün} other {# gün}}"
|
msgstr "{0, plural, one {# gün} other {# gün}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {# saat} other {# saat}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr "{0, plural, one {# dakika} few {# dakika} many {# dakika} other {# dakika}}"
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr "{1} satırdan {0} tanesi seçildi."
|
msgstr "{1} satırdan {0} tanesi seçildi."
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {# saat} other {# saat}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "1 saat"
|
msgstr "1 saat"
|
||||||
@@ -125,6 +131,7 @@ msgstr "Uyarılar"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "Tüm Sistemler"
|
msgstr "Tüm Sistemler"
|
||||||
|
|
||||||
@@ -178,7 +185,7 @@ msgstr "Bant Genişliği"
|
|||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Battery"
|
msgid "Battery"
|
||||||
msgstr ""
|
msgstr "Pil"
|
||||||
|
|
||||||
#: src/components/login/auth-form.tsx
|
#: src/components/login/auth-form.tsx
|
||||||
msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers."
|
msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers."
|
||||||
@@ -229,12 +236,12 @@ msgstr "Genel uygulama seçeneklerini değiştirin."
|
|||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Charge"
|
msgid "Charge"
|
||||||
msgstr ""
|
msgstr "Şarj"
|
||||||
|
|
||||||
#. Context: Battery state
|
#. Context: Battery state
|
||||||
#: src/lib/i18n.ts
|
#: src/lib/i18n.ts
|
||||||
msgid "Charging"
|
msgid "Charging"
|
||||||
msgstr ""
|
msgstr "Şarj oluyor"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Chart options"
|
msgid "Chart options"
|
||||||
@@ -252,6 +259,10 @@ msgstr "Daha fazla ayrıntı için günlükleri kontrol edin."
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "Bildirim hizmetinizi kontrol edin"
|
msgstr "Bildirim hizmetinizi kontrol edin"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr "Daha fazla bilgi görmek için bir sisteme tıklayın."
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "Kopyalamak için tıklayın"
|
msgstr "Kopyalamak için tıklayın"
|
||||||
@@ -355,7 +366,7 @@ msgstr "Kritik (%)"
|
|||||||
#. Context: Battery state
|
#. Context: Battery state
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Current state"
|
msgid "Current state"
|
||||||
msgstr ""
|
msgstr "Mevcut durum"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/home.tsx
|
#: src/components/routes/home.tsx
|
||||||
@@ -378,7 +389,7 @@ msgstr "Parmak izini sil"
|
|||||||
#. Context: Battery state
|
#. Context: Battery state
|
||||||
#: src/lib/i18n.ts
|
#: src/lib/i18n.ts
|
||||||
msgid "Discharging"
|
msgid "Discharging"
|
||||||
msgstr ""
|
msgstr "Boşalıyor"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "Disk"
|
msgid "Disk"
|
||||||
@@ -426,6 +437,10 @@ msgstr "Dokümantasyon"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr "Kapalı"
|
msgstr "Kapalı"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr "Kapalı ({downSystemsLength})"
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr "Süre"
|
msgstr "Süre"
|
||||||
@@ -447,7 +462,7 @@ msgstr "E-posta bildirimleri"
|
|||||||
#. Context: Battery state
|
#. Context: Battery state
|
||||||
#: src/lib/i18n.ts
|
#: src/lib/i18n.ts
|
||||||
msgid "Empty"
|
msgid "Empty"
|
||||||
msgstr ""
|
msgstr "Boş"
|
||||||
|
|
||||||
#: src/components/login/login.tsx
|
#: src/components/login/login.tsx
|
||||||
msgid "Enter email address to reset password"
|
msgid "Enter email address to reset password"
|
||||||
@@ -492,7 +507,7 @@ msgstr "Mevcut sistem yapılandırmanızı dışa aktarın."
|
|||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "Fahrenhayt (°F)"
|
msgstr "Fahrenhayt (°F)"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "Kimlik doğrulama başarısız"
|
msgstr "Kimlik doğrulama başarısız"
|
||||||
|
|
||||||
@@ -530,7 +545,7 @@ msgstr "Şifrenizi mi unuttunuz?"
|
|||||||
#. Context: Battery state
|
#. Context: Battery state
|
||||||
#: src/lib/i18n.ts
|
#: src/lib/i18n.ts
|
||||||
msgid "Full"
|
msgid "Full"
|
||||||
msgstr ""
|
msgstr "Dolu"
|
||||||
|
|
||||||
#. Context: General settings
|
#. Context: General settings
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
@@ -559,7 +574,7 @@ msgstr "Host / IP"
|
|||||||
#. Context: Battery state
|
#. Context: Battery state
|
||||||
#: src/lib/i18n.ts
|
#: src/lib/i18n.ts
|
||||||
msgid "Idle"
|
msgid "Idle"
|
||||||
msgstr ""
|
msgstr "Boşta"
|
||||||
|
|
||||||
#: src/components/login/forgot-pass-form.tsx
|
#: src/components/login/forgot-pass-form.tsx
|
||||||
msgid "If you've lost the password to your admin account, you may reset it using the following command."
|
msgid "If you've lost the password to your admin account, you may reset it using the following command."
|
||||||
@@ -753,6 +768,10 @@ msgstr "Duraklat"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "Duraklatıldı"
|
msgstr "Duraklatıldı"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr "Duraklatıldı ({pausedSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr "Uyarıların teslim edilmesini sağlamak için lütfen bir SMTP sunucusu <0>yapılandırın</0>."
|
msgstr "Uyarıların teslim edilmesini sağlamak için lütfen bir SMTP sunucusu <0>yapılandırın</0>."
|
||||||
@@ -774,7 +793,7 @@ msgstr "Lütfen bir yönetici hesabı oluşturun"
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr "Lütfen bu site için açılır pencereleri etkinleştirin"
|
msgstr "Lütfen bu site için açılır pencereleri etkinleştirin"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "Lütfen tekrar giriş yapın"
|
msgstr "Lütfen tekrar giriş yapın"
|
||||||
|
|
||||||
@@ -901,6 +920,7 @@ msgstr "Sıralama Ölçütü"
|
|||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr "Durum"
|
msgstr "Durum"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Durum"
|
msgstr "Durum"
|
||||||
@@ -998,7 +1018,7 @@ msgstr "Temayı değiştir"
|
|||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr ""
|
msgstr "Token"
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/layout.tsx
|
#: src/components/routes/settings/layout.tsx
|
||||||
@@ -1055,6 +1075,7 @@ msgstr "Herhangi bir diskin kullanımı bir eşiği aştığında tetiklenir"
|
|||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr "Birim tercihleri"
|
msgstr "Birim tercihleri"
|
||||||
|
|
||||||
|
#: 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 "Evrensel token"
|
msgstr "Evrensel token"
|
||||||
@@ -1062,7 +1083,7 @@ msgstr "Evrensel token"
|
|||||||
#. Context: Battery state
|
#. Context: Battery state
|
||||||
#: src/lib/i18n.ts
|
#: src/lib/i18n.ts
|
||||||
msgid "Unknown"
|
msgid "Unknown"
|
||||||
msgstr ""
|
msgstr "Bilinmiyor"
|
||||||
|
|
||||||
#. Context: System is up
|
#. Context: System is up
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
@@ -1071,8 +1092,8 @@ msgid "Up"
|
|||||||
msgstr "Açık"
|
msgstr "Açık"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "Gerçek zamanlı olarak güncellenir. Bilgileri görüntülemek için bir sisteme tıklayın."
|
msgstr "Açık ({upSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
@@ -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: 2025-07-25 22:44\n"
|
"PO-Revision-Date: 2025-08-30 16:20\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"
|
||||||
@@ -23,16 +23,22 @@ msgstr ""
|
|||||||
msgid "{0, plural, one {# day} other {# days}}"
|
msgid "{0, plural, one {# day} other {# days}}"
|
||||||
msgstr "{0, plural, one {# день} few {# дні} many {# днів} other {# дня}}"
|
msgstr "{0, plural, one {# день} few {# дні} many {# днів} other {# дня}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# hour} other {# hours}}"
|
||||||
|
msgstr "{0, plural, one {# година} few {# години} many {# годин} other {# години}}"
|
||||||
|
|
||||||
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
|
#: src/components/routes/system.tsx
|
||||||
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
|
msgstr "{0, plural, one {# хвилина} few {# хвилини} many {# хвилин} other {# хвилини}}"
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr "Вибрано {0} з {1} рядків."
|
msgstr "Вибрано {0} з {1} рядків."
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{hours, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{hours, plural, one {# година} few {# години} many {# годин} other {# години}}"
|
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "1 година"
|
msgstr "1 година"
|
||||||
@@ -125,6 +131,7 @@ msgstr "Сповіщення"
|
|||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "All Systems"
|
msgid "All Systems"
|
||||||
msgstr "Всі системи"
|
msgstr "Всі системи"
|
||||||
|
|
||||||
@@ -252,6 +259,10 @@ msgstr "Перевірте журнали для отримання додатк
|
|||||||
msgid "Check your notification service"
|
msgid "Check your notification service"
|
||||||
msgstr "Перевірте свій сервіс сповіщень"
|
msgstr "Перевірте свій сервіс сповіщень"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Click on a system to view more information."
|
||||||
|
msgstr "Натисніть на систему, щоб переглянути більше інформації."
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
msgstr "Натисніть, щоб скопіювати"
|
msgstr "Натисніть, щоб скопіювати"
|
||||||
@@ -426,6 +437,10 @@ msgstr "Документація"
|
|||||||
msgid "Down"
|
msgid "Down"
|
||||||
msgstr "Не працює"
|
msgstr "Не працює"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Down ({downSystemsLength})"
|
||||||
|
msgstr "Не працює ({downSystemsLength})"
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr "Тривалість"
|
msgstr "Тривалість"
|
||||||
@@ -492,7 +507,7 @@ msgstr "Експортуйте поточну конфігурацію сист
|
|||||||
msgid "Fahrenheit (°F)"
|
msgid "Fahrenheit (°F)"
|
||||||
msgstr "Фаренгейт (°F)"
|
msgstr "Фаренгейт (°F)"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Failed to authenticate"
|
msgid "Failed to authenticate"
|
||||||
msgstr "Не вдалося автентифікувати"
|
msgstr "Не вдалося автентифікувати"
|
||||||
|
|
||||||
@@ -753,6 +768,10 @@ msgstr "Призупинити"
|
|||||||
msgid "Paused"
|
msgid "Paused"
|
||||||
msgstr "Призупинено"
|
msgstr "Призупинено"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
|
msgstr "Призупинено ({pausedSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
msgstr "Будь ласка, <0>налаштуйте SMTP сервер</0>, щоб забезпечити доставку сповіщень."
|
msgstr "Будь ласка, <0>налаштуйте SMTP сервер</0>, щоб забезпечити доставку сповіщень."
|
||||||
@@ -774,7 +793,7 @@ msgstr "Будь ласка, створіть адміністративний
|
|||||||
msgid "Please enable pop-ups for this site"
|
msgid "Please enable pop-ups for this site"
|
||||||
msgstr "Будь ласка, увімкніть спливаючі вікна для цього сайту"
|
msgstr "Будь ласка, увімкніть спливаючі вікна для цього сайту"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/api.ts
|
||||||
msgid "Please log in again"
|
msgid "Please log in again"
|
||||||
msgstr "Будь ласка, увійдіть знову"
|
msgstr "Будь ласка, увійдіть знову"
|
||||||
|
|
||||||
@@ -901,6 +920,7 @@ msgstr "Сортувати за"
|
|||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr "Стан"
|
msgstr "Стан"
|
||||||
|
|
||||||
|
#: src/components/systems-table/systems-table.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Статус"
|
msgstr "Статус"
|
||||||
@@ -1055,6 +1075,7 @@ msgstr "Спрацьовує, коли використання будь-яко
|
|||||||
msgid "Unit preferences"
|
msgid "Unit preferences"
|
||||||
msgstr "Налаштування одиниць вимірювання"
|
msgstr "Налаштування одиниць вимірювання"
|
||||||
|
|
||||||
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||||
msgid "Universal token"
|
msgid "Universal token"
|
||||||
msgstr "Універсальний токен"
|
msgstr "Універсальний токен"
|
||||||
@@ -1071,8 +1092,8 @@ msgid "Up"
|
|||||||
msgstr "Працює"
|
msgstr "Працює"
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Updated in real time. Click on a system to view information."
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr "Оновлюється в реальному часі. Натисніть на систему, щоб переглянути інформацію."
|
msgstr "Працює ({upSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user