Compare commits

...

56 Commits

Author SHA1 Message Date
henrygd
5f4d3dc730 0.12.5 release :) 2025-08-26 21:04:46 -04:00
henrygd
8fa9aece63 change long german translation 2025-08-26 20:50:35 -04:00
henrygd
2f1a022e2a refactor: use width for meters instead of scale 2025-08-26 20:49:31 -04:00
henrygd
4815cd29bc ghupdate: rename plugin struct 2025-08-26 18:41:42 -04:00
henrygd
e49bfaf5d7 downgrade gopsutil to fix freebsd bug (#1083) 2025-08-26 18:40:32 -04:00
henrygd
b13915b76f freebsd: fix battery-related bug (#1081) 2025-08-26 18:39:42 -04:00
henrygd
e2a57dc43b update tooltip component for tailwind 4 2025-08-26 16:16:38 -04:00
henrygd
7222224b40 add battery to supported metrics 2025-08-25 23:15:19 -04:00
henrygd
02ff475b84 improve language toggle selected style 2025-08-25 22:14:48 -04:00
henrygd
09cd8d0db9 add check for battery array length (#1076) 2025-08-25 21:34:34 -04:00
henrygd
36f1a0c53b update synctest.run to synctest.test 2025-08-25 21:25:19 -04:00
henrygd
0b0e94e045 remove pocketbase imports from ghupdate 2025-08-25 21:16:43 -04:00
henrygd
20ca6edf81 0.12.4 release :) 2025-08-25 20:23:38 -04:00
henrygd
1990f8c6df use mirror for asset download in update command (#1035) 2025-08-25 20:18:31 -04:00
henrygd
6e9dbf863f remove rhysd/go-github-selfupdate dependency 2025-08-25 20:05:02 -04:00
henrygd
fa921d77f1 update updater (#1009) 2025-08-25 20:04:23 -04:00
Sven van Ginkel
ff854d481d [Chore] Improve auto update mechanism (#1009) 2025-08-25 17:30:42 -04:00
henrygd
4ce491fe48 i18n: add default machine translations for new strings 2025-08-25 17:02:34 -04:00
henrygd
493bae7eb6 i18n: new battery strings 2025-08-25 16:46:15 -04:00
henrygd
ae5532aa36 new translations 2025-08-25 16:37:20 -04:00
NaNomicon
a1eae6413a New Vietnamese translations 2025-08-25 16:36:41 -04:00
henrygd
ee52bf1fbf New Polish translations by dymek37 2025-08-25 16:32:15 -04:00
Alex van Steenhoven
2ff0bd6b44 New Dutch translations 2025-08-25 16:30:57 -04:00
harupong
a385233b7d New Japanese translations 2025-08-25 16:28:02 -04:00
henrygd
f5648a415d New Italian translations by Tommaso Cavazza 2025-08-25 16:27:35 -04:00
Radoslav Mandev
556fb18953 New Bulgarian translations 2025-08-25 16:26:03 -04:00
henrygd
a482f78739 add changelog 2025-08-25 16:21:07 -04:00
henrygd
4a580ce972 tailwind 4 upgrade + update js deps 2025-08-25 16:12:15 -04:00
henrygd
e07558237f remove dropdown from theme mode toggle 2025-08-25 16:09:49 -04:00
henrygd
fb3c70a1bc update battery charge to include all batteries 2025-08-24 22:34:38 -04:00
henrygd
cba4d60895 improve chart legend alignment 2025-08-24 22:34:26 -04:00
henrygd
8b655ef2b9 add battery charge monitoring 2025-08-24 20:45:38 -04:00
henrygd
0188418055 update go deps 2025-08-24 19:57:40 -04:00
henrygd
72334c42d0 refactor: add @/lib/alerts 2025-08-24 19:57:28 -04:00
henrygd
0638ff3c21 refactor: add subscribe / unsubscribe to alertManager 2025-08-24 19:48:21 -04:00
henrygd
b64318d9e8 fix: frontend token generation in insecure contexts 2025-08-24 17:24:34 -04:00
henrygd
0f5b1b5157 refactor: replace status strings with systemstatus enum
- also small style changes after tailwind update
2025-08-23 20:35:18 -04:00
henrygd
3c4ae46f50 remove usememo return in add system dialog
- forgot to remove this before last commit. interferes with token display.
2025-08-22 20:27:12 -04:00
henrygd
c158b1aeeb move alerts ui to sheet component 2025-08-22 19:28:00 -04:00
henrygd
684d92c497 upgrade to tailwind 4 2025-08-22 18:06:19 -04:00
henrygd
bbd9595ec0 upgrade js dependencies (react 19) 2025-08-22 14:39:48 -04:00
henrygd
bbebb3e301 update Link component to support opening links in new tabs with ctrl/cmd key 2025-08-21 19:00:34 -04:00
henrygd
9d25181d1d use anchor tag for system table links 2025-08-21 18:44:38 -04:00
henrygd
7ba1f366ba remove batch api in favor of custom endpoint for alerts management (#1039, #1023) 2025-08-19 20:56:12 -04:00
henrygd
37c6b920f9 refactor: move useStore above possible return nulls 2025-08-19 20:20:47 -04:00
henrygd
49db81dac8 refactor: api router groups and auth handling
- require auth for `/api/beszel/getkey`
- Change `GET /api/beszel/send-test-notification` endpoint to `POST /api/beszel/test-notification`.
- add tests for API endpoints
2025-08-19 20:14:01 -04:00
henrygd
a9e90ec19c update resolveAlertHistoryRecord params to use alert ID directly 2025-08-19 19:56:51 -04:00
henrygd
2ad60507b7 small style updates 2025-08-19 19:48:25 -04:00
henrygd
12059ee3db refactor: js performance improvements 2025-08-06 22:21:48 -04:00
henrygd
de56544ca3 0.12.3 release :) 2025-08-03 22:10:51 -04:00
henrygd
065c7facb6 update language files 2025-08-03 22:10:25 -04:00
NickAss512
630c92c139 New Czech translations 2025-08-03 21:53:51 -04:00
henrygd
e11d452d91 separate agent dockerfiles 2025-08-03 21:14:43 -04:00
dalton-baker
99c7f7bd8a Add GPU-enabled build target in dockerfile_Agent (nvidia-smi support) (#898) 2025-08-03 13:31:26 -04:00
henrygd
8af3a0eb5b refactor: add getMeterState function 2025-08-02 23:44:07 -04:00
henrygd
5f7950b474 tweaks to custom meter percentages 2025-08-02 20:53:49 -04:00
129 changed files with 8708 additions and 5463 deletions

View File

@@ -14,28 +14,48 @@ jobs:
include: include:
- image: henrygd/beszel - image: henrygd/beszel
context: ./beszel context: ./beszel
dockerfile: ./beszel/dockerfile_Hub dockerfile: ./beszel/dockerfile_hub
registry: docker.io registry: docker.io
username_secret: DOCKERHUB_USERNAME username_secret: DOCKERHUB_USERNAME
password_secret: DOCKERHUB_TOKEN password_secret: DOCKERHUB_TOKEN
- image: henrygd/beszel-agent - image: henrygd/beszel-agent
context: ./beszel context: ./beszel
dockerfile: ./beszel/dockerfile_Agent dockerfile: ./beszel/dockerfile_agent
registry: docker.io registry: docker.io
username_secret: DOCKERHUB_USERNAME username_secret: DOCKERHUB_USERNAME
password_secret: DOCKERHUB_TOKEN password_secret: DOCKERHUB_TOKEN
- image: henrygd/beszel-agent-nvidia
context: ./beszel
dockerfile: ./beszel/dockerfile_agent_nvidia
platforms: linux/amd64
registry: docker.io
username_secret: DOCKERHUB_USERNAME
password_secret: DOCKERHUB_TOKEN
- image: ghcr.io/${{ github.repository }}/beszel - image: ghcr.io/${{ github.repository }}/beszel
context: ./beszel context: ./beszel
dockerfile: ./beszel/dockerfile_Hub dockerfile: ./beszel/dockerfile_hub
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password_secret: GITHUB_TOKEN password_secret: GITHUB_TOKEN
- image: ghcr.io/${{ github.repository }}/beszel-agent - image: ghcr.io/${{ github.repository }}/beszel-agent
context: ./beszel context: ./beszel
dockerfile: ./beszel/dockerfile_Agent dockerfile: ./beszel/dockerfile_agent
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password_secret: GITHUB_TOKEN password_secret: GITHUB_TOKEN
- image: ghcr.io/${{ github.repository }}/beszel-agent-nvidia
context: ./beszel
dockerfile: ./beszel/dockerfile_agent_nvidia
platforms: linux/amd64
registry: ghcr.io
username: ${{ github.actor }}
password_secret: GITHUB_TOKEN
permissions: permissions:
contents: read contents: read
packages: write packages: write
@@ -87,7 +107,7 @@ jobs:
with: with:
context: "${{ matrix.context }}" context: "${{ matrix.context }}"
file: ${{ matrix.dockerfile }} file: ${{ matrix.dockerfile }}
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' }}
tags: ${{ steps.metadata.outputs.tags }} tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }} labels: ${{ steps.metadata.outputs.labels }}

1
.gitignore vendored
View File

@@ -19,3 +19,4 @@ beszel/site/src/locales/**/*.ts
__debug_* __debug_*
beszel/internal/agent/lhm/obj beszel/internal/agent/lhm/obj
beszel/internal/agent/lhm/bin beszel/internal/agent/lhm/bin
dockerfile_agent_dev

26
beszel/dockerfile_agent Normal file
View File

@@ -0,0 +1,26 @@
FROM --platform=$BUILDPLATFORM golang:alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
# RUN go mod download
COPY *.go ./
COPY cmd ./cmd
COPY internal ./internal
# Build
ARG TARGETOS TARGETARCH
RUN CGO_ENABLED=0 GOGC=75 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags "-w -s" -o /agent ./cmd/agent
RUN rm -rf /tmp/*
# --------------------------
# Final image: default scratch-based agent
# --------------------------
FROM scratch
COPY --from=builder /agent /agent
# this is so we don't need to create the /tmp directory in the scratch container
COPY --from=builder /tmp /tmp
ENTRYPOINT ["/agent"]

View File

@@ -12,15 +12,10 @@ COPY internal ./internal
ARG TARGETOS TARGETARCH ARG TARGETOS TARGETARCH
RUN CGO_ENABLED=0 GOGC=75 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags "-w -s" -o /agent ./cmd/agent RUN CGO_ENABLED=0 GOGC=75 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags "-w -s" -o /agent ./cmd/agent
RUN rm -rf /tmp/* # --------------------------
# Final image: GPU-enabled agent with nvidia-smi
# ? ------------------------- # --------------------------
FROM scratch FROM nvidia/cuda:12.9.1-base-ubuntu22.04
COPY --from=builder /agent /agent COPY --from=builder /agent /agent
# this is so we don't need to create the
# /tmp directory in the scratch container
COPY --from=builder /tmp /tmp
ENTRYPOINT ["/agent"] ENTRYPOINT ["/agent"]

View File

@@ -7,20 +7,20 @@ replace github.com/nicholas-fedor/shoutrrr => github.com/nicholas-fedor/shoutrrr
require ( require (
github.com/blang/semver v3.5.1+incompatible github.com/blang/semver v3.5.1+incompatible
github.com/distatus/battery v0.11.0
github.com/fxamacker/cbor/v2 v2.9.0 github.com/fxamacker/cbor/v2 v2.9.0
github.com/gliderlabs/ssh v0.3.8 github.com/gliderlabs/ssh v0.3.8
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/lxzan/gws v1.8.9 github.com/lxzan/gws v1.8.9
github.com/nicholas-fedor/shoutrrr v0.8.15 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.0 github.com/pocketbase/pocketbase v0.29.3
github.com/rhysd/go-github-selfupdate v1.2.3
github.com/shirou/gopsutil/v4 v4.25.6 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/stretchr/testify v1.10.0 github.com/stretchr/testify v1.11.0
golang.org/x/crypto v0.40.0 golang.org/x/crypto v0.41.0
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
@@ -39,13 +39,10 @@ require (
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
github.com/go-sql-driver/mysql v1.9.1 // indirect github.com/go-sql-driver/mysql v1.9.1 // indirect
github.com/golang-jwt/jwt/v5 v5.2.3 // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/google/go-github/v30 v30.1.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect github.com/lufia/plan9stats v0.0.0-20250821153705-5981dea3221d // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect
@@ -53,20 +50,20 @@ require (
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/spf13/pflag v1.0.7 // indirect
github.com/tcnksm/go-gitconfig v0.1.2 // 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/ulikunitz/xz v0.5.12 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/image v0.29.0 // indirect golang.org/x/image v0.30.0 // indirect
golang.org/x/net v0.42.0 // indirect golang.org/x/net v0.43.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.16.0 // indirect golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.34.0 // indirect golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.27.0 // indirect golang.org/x/text v0.28.0 // indirect
modernc.org/libc v1.65.10 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
howett.net/plist v1.0.1 // indirect
modernc.org/libc v1.66.3 // indirect
modernc.org/mathutil v1.7.1 // indirect modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.38.0 // indirect modernc.org/sqlite v1.38.2 // indirect
) )

View File

@@ -13,6 +13,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/distatus/battery v0.11.0 h1:KJk89gz90Iq/wJtbjjM9yUzBXV+ASV/EG2WOOL7N8lc=
github.com/distatus/battery v0.11.0/go.mod h1:KmVkE8A8hpIX4T78QRdMktYpEp35QfOL8A8dwZBxq2k=
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ= github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4= github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8= github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8=
@@ -25,7 +27,6 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
@@ -46,41 +47,28 @@ github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtS
github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo=
github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7VjIE6z8dIvMsI4/s+1qr5EL+zoIGev1BQj1eoJ8=
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jarcoal/httpmock v1.4.0 h1:BvhqnH0JAYbNudL2GMJKgOHe2CtKlzJ/5rWKyp+hc2k= github.com/jarcoal/httpmock v1.4.0 h1:BvhqnH0JAYbNudL2GMJKgOHe2CtKlzJ/5rWKyp+hc2k=
github.com/jarcoal/httpmock v1.4.0/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0= github.com/jarcoal/httpmock v1.4.0/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc= github.com/lufia/plan9stats v0.0.0-20250821153705-5981dea3221d h1:vFzYZc8yji+9DmNRhpEbs8VBK4CgV/DPfGzeVJSSp/8=
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lufia/plan9stats v0.0.0-20250821153705-5981dea3221d/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/lxzan/gws v1.8.9 h1:VU3SGUeWlQrEwfUSfokcZep8mdg/BrUF+y73YYshdBM= github.com/lxzan/gws v1.8.9 h1:VU3SGUeWlQrEwfUSfokcZep8mdg/BrUF+y73YYshdBM=
github.com/lxzan/gws v1.8.9/go.mod h1:d9yHaR1eDTBHagQC6KY7ycUOaz5KWeqQtP3xu7aMK8Y= github.com/lxzan/gws v1.8.9/go.mod h1:d9yHaR1eDTBHagQC6KY7ycUOaz5KWeqQtP3xu7aMK8Y=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
@@ -91,11 +79,8 @@ github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdh
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nicholas-fedor/shoutrrr v0.8.8 h1:F/oyoatWK5cbHPPgkjRZrA0262TP7KWuUQz9KskRtR8= github.com/nicholas-fedor/shoutrrr v0.8.8 h1:F/oyoatWK5cbHPPgkjRZrA0262TP7KWuUQz9KskRtR8=
github.com/nicholas-fedor/shoutrrr v0.8.8/go.mod h1:T30Y+eoZFEjDk4HtOItcHQioZSOe3Z6a6aNfSz6jc5c= github.com/nicholas-fedor/shoutrrr v0.8.8/go.mod h1:T30Y+eoZFEjDk4HtOItcHQioZSOe3Z6a6aNfSz6jc5c=
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -103,14 +88,12 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pocketbase/dbx v1.11.0 h1:LpZezioMfT3K4tLrqA55wWFw1EtH1pM4tzSVa7kgszU= github.com/pocketbase/dbx v1.11.0 h1:LpZezioMfT3K4tLrqA55wWFw1EtH1pM4tzSVa7kgszU=
github.com/pocketbase/dbx v1.11.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs= github.com/pocketbase/dbx v1.11.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
github.com/pocketbase/pocketbase v0.29.0 h1:oL6qvkU2QSybClVtQdaq9Z1F3Wk59iKYCfIaf1R8KUs= github.com/pocketbase/pocketbase v0.29.3 h1:Mj8o5awsbVJIdIoTuQNhfC2oL/c4aImQ3RyfFZlzFVg=
github.com/pocketbase/pocketbase v0.29.0/go.mod h1:SqyH7o/3e+/uLySATlJqxH4S8gyU6R0adG56ZSV1vuU= github.com/pocketbase/pocketbase v0.29.3/go.mod h1:oGpT67LObxCFK4V2fSL7J9YnPbBnnshOpJ5v3zcneww=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rhysd/go-github-selfupdate v1.2.3 h1:iaa+J202f+Nc+A8zi75uccC8Wg3omaM7HDeimXA22Ag=
github.com/rhysd/go-github-selfupdate v1.2.3/go.mod h1:mp/N8zj6jFfBQy/XMYoWsmfzxazpPAODuqarmPDe2Rg=
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=
@@ -125,17 +108,12 @@ github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tcnksm/go-gitconfig v0.1.2 h1:iiDhRitByXAEyjgBqsKi9QU4o2TNtv9kPP3RgPgXBPw=
github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4UsLGSItWYCpE=
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
@@ -143,65 +121,50 @@ github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0=
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4= golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.29.0 h1:HcdsyR4Gsuys/Axh0rDEmlBmB68rW1U9BUdB3UVHsas= golang.org/x/image v0.30.0 h1:jD5RhkmVAnjqaCUXfbGBrn3lpxbknfN9w2UhHHU+5B4=
golang.org/x/image v0.29.0/go.mod h1:RVJROnf3SLK8d26OW91j4FrIHGbsJ8QnbEocVTOWQDA= golang.org/x/image v0.30.0/go.mod h1:SAEUTxCCMWSrJcCy/4HwavEsfZZJlYxeHLc6tTiAe/c=
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM= modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM=
modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
@@ -212,8 +175,6 @@ modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.65.10 h1:ZwEk8+jhW7qBjHIT+wd0d9VjitRyQef9BnzlzGwMODc=
modernc.org/libc v1.65.10/go.mod h1:StFvYpx7i/mXtBAfVOjaU0PWZOvIRoZSgXhrwXzr8Po=
modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ=
modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
@@ -224,8 +185,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI= modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek=
modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE= modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=

View File

@@ -14,7 +14,7 @@ import (
) )
func TestSessionCache_GetSet(t *testing.T) { func TestSessionCache_GetSet(t *testing.T) {
synctest.Run(func() { synctest.Test(t, func(t *testing.T) {
cache := NewSessionCache(69 * time.Second) cache := NewSessionCache(69 * time.Second)
testData := &system.CombinedData{ testData := &system.CombinedData{

View 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
}

View 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
}

View File

@@ -39,7 +39,7 @@ func TestHealth(t *testing.T) {
// This test uses synctest to simulate time passing. // This test uses synctest to simulate time passing.
// NOTE: This test requires GOEXPERIMENT=synctest to run. // NOTE: This test requires GOEXPERIMENT=synctest to run.
t.Run("check with simulated time", func(t *testing.T) { t.Run("check with simulated time", func(t *testing.T) {
synctest.Run(func() { synctest.Test(t, func(t *testing.T) {
// Update the file to set the initial timestamp. // Update the file to set the initial timestamp.
require.NoError(t, Update(), "Update() failed inside synctest") require.NoError(t, Update(), "Update() failed inside synctest")

View File

@@ -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"
@@ -59,10 +60,10 @@ func (a *Agent) initializeSystemInfo() {
} }
// zfs // zfs
if _, err := getARCSize(); err == nil { if _, err := getARCSize(); err != nil {
a.zfs = true
} else {
slog.Debug("Not monitoring ZFS ARC", "err", err) slog.Debug("Not monitoring ZFS ARC", "err", err)
} else {
a.zfs = true
} }
} }
@@ -70,6 +71,11 @@ func (a *Agent) initializeSystemInfo() {
func (a *Agent) getSystemStats() system.Stats { func (a *Agent) getSystemStats() system.Stats {
systemStats := system.Stats{} systemStats := system.Stats{}
// battery
if battery.HasReadableBattery() {
systemStats.Battery[0], systemStats.Battery[1], _ = battery.GetBatteryStats()
}
// cpu percent // cpu percent
cpuPct, err := cpu.Percent(0, false) cpuPct, err := cpu.Percent(0, false)
if err != nil { if err != nil {
@@ -80,7 +86,6 @@ func (a *Agent) getSystemStats() system.Stats {
// load average // load average
if avgstat, err := load.Avg(); err == nil { if avgstat, err := load.Avg(); err == nil {
// TODO: remove these in future release in favor of load avg array
systemStats.LoadAvg[0] = avgstat.Load1 systemStats.LoadAvg[0] = avgstat.Load1
systemStats.LoadAvg[1] = avgstat.Load5 systemStats.LoadAvg[1] = avgstat.Load5
systemStats.LoadAvg[2] = avgstat.Load15 systemStats.LoadAvg[2] = avgstat.Load15

View File

@@ -1,56 +1,147 @@
package agent package agent
import ( import (
"beszel" "beszel/internal/ghupdate"
"fmt" "fmt"
"log"
"os" "os"
"os/exec"
"strings" "strings"
"github.com/blang/semver"
"github.com/rhysd/go-github-selfupdate/selfupdate"
) )
// Update updates beszel-agent to the latest version // restarter knows how to restart the beszel-agent service.
func Update() { type restarter interface {
var latest *selfupdate.Release Restart() error
var found bool }
var err error
currentVersion := semver.MustParse(beszel.Version) type systemdRestarter struct{ cmd string }
fmt.Println("beszel-agent", currentVersion)
fmt.Println("Checking for updates...") func (s *systemdRestarter) Restart() error {
updater, _ := selfupdate.NewUpdater(selfupdate.Config{ // Only restart if the service is active
Filters: []string{"beszel-agent"}, if err := exec.Command(s.cmd, "is-active", "beszel-agent.service").Run(); err != nil {
}) return nil
latest, found, err = updater.DetectLatest("henrygd/beszel") }
ghupdate.ColorPrint(ghupdate.ColorYellow, "Restarting beszel-agent.service via systemd…")
if err != nil { return exec.Command(s.cmd, "restart", "beszel-agent.service").Run()
fmt.Println("Error checking for updates:", err) }
os.Exit(1)
} type openRCRestarter struct{ cmd string }
if !found { func (o *openRCRestarter) Restart() error {
fmt.Println("No updates found") if err := exec.Command(o.cmd, "status", "beszel-agent").Run(); err != nil {
os.Exit(0) return nil
} }
ghupdate.ColorPrint(ghupdate.ColorYellow, "Restarting beszel-agent via OpenRC…")
fmt.Println("Latest version:", latest.Version) return exec.Command(o.cmd, "restart", "beszel-agent").Run()
}
if latest.Version.LTE(currentVersion) {
fmt.Println("You are up to date") type openWRTRestarter struct{ cmd string }
return
} func (w *openWRTRestarter) Restart() error {
if err := exec.Command(w.cmd, "running", "beszel-agent").Run(); err != nil {
var binaryPath string return nil
fmt.Printf("Updating from %s to %s...\n", currentVersion, latest.Version) }
binaryPath, err = os.Executable() ghupdate.ColorPrint(ghupdate.ColorYellow, "Restarting beszel-agent via procd…")
if err != nil { return exec.Command(w.cmd, "restart", "beszel-agent").Run()
fmt.Println("Error getting binary path:", err) }
os.Exit(1)
} func detectRestarter() restarter {
err = selfupdate.UpdateTo(latest.AssetURL, binaryPath) if path, err := exec.LookPath("systemctl"); err == nil {
if err != nil { return &systemdRestarter{cmd: path}
fmt.Println("Please try rerunning with sudo. Error:", err) }
os.Exit(1) if path, err := exec.LookPath("rc-service"); err == nil {
} return &openRCRestarter{cmd: path}
fmt.Printf("Successfully updated to %s\n\n%s\n", latest.Version, strings.TrimSpace(latest.ReleaseNotes)) }
if path, err := exec.LookPath("service"); err == nil {
return &openWRTRestarter{cmd: path}
}
return nil
}
// Update checks GitHub for a newer release of beszel-agent, applies it,
// fixes SELinux context if needed, and restarts the service.
func Update() error {
exePath, _ := os.Executable()
dataDir, err := getDataDir()
if err != nil {
dataDir = os.TempDir()
}
updated, err := ghupdate.Update(ghupdate.Config{
ArchiveExecutable: "beszel-agent",
DataDir: dataDir,
})
if err != nil {
log.Fatal(err)
}
if !updated {
return nil
}
// make sure the file is executable
if err := os.Chmod(exePath, 0755); err != nil {
ghupdate.ColorPrintf(ghupdate.ColorYellow, "Warning: failed to set executable permissions: %v", err)
}
// set ownership to beszel:beszel if possible
if chownPath, err := exec.LookPath("chown"); err == nil {
if err := exec.Command(chownPath, "beszel:beszel", exePath).Run(); err != nil {
ghupdate.ColorPrintf(ghupdate.ColorYellow, "Warning: failed to set file ownership: %v", err)
}
}
// 6) Fix SELinux context if necessary
if err := handleSELinuxContext(exePath); err != nil {
ghupdate.ColorPrintf(ghupdate.ColorYellow, "Warning: SELinux context handling: %v", err)
}
// 7) Restart service if running under a recognised init system
if r := detectRestarter(); r != nil {
if err := r.Restart(); err != nil {
ghupdate.ColorPrintf(ghupdate.ColorYellow, "Warning: failed to restart service: %v", err)
ghupdate.ColorPrint(ghupdate.ColorYellow, "Please restart the service manually.")
}
} else {
ghupdate.ColorPrint(ghupdate.ColorYellow, "No supported init system detected; please restart manually if needed.")
}
return nil
}
// handleSELinuxContext restores or applies the correct SELinux label to the binary.
func handleSELinuxContext(path string) error {
out, err := exec.Command("getenforce").Output()
if err != nil {
// SELinux not enabled or getenforce not available
return nil
}
state := strings.TrimSpace(string(out))
if state == "Disabled" {
return nil
}
ghupdate.ColorPrint(ghupdate.ColorYellow, "SELinux is enabled; applying context…")
var errs []string
// Try persistent context via semanage+restorecon
if semanagePath, err := exec.LookPath("semanage"); err == nil {
if err := exec.Command(semanagePath, "fcontext", "-a", "-t", "bin_t", path).Run(); err != nil {
errs = append(errs, "semanage fcontext failed: "+err.Error())
} else if restoreconPath, err := exec.LookPath("restorecon"); err == nil {
if err := exec.Command(restoreconPath, "-v", path).Run(); err != nil {
errs = append(errs, "restorecon failed: "+err.Error())
}
}
}
// Fallback to temporary context via chcon
if chconPath, err := exec.LookPath("chcon"); err == nil {
if err := exec.Command(chconPath, "-t", "bin_t", path).Run(); err != nil {
errs = append(errs, "chcon failed: "+err.Error())
}
}
if len(errs) > 0 {
return fmt.Errorf("SELinux context errors: %s", strings.Join(errs, "; "))
}
return nil
} }

View File

@@ -10,7 +10,6 @@ import (
"github.com/nicholas-fedor/shoutrrr" "github.com/nicholas-fedor/shoutrrr"
"github.com/pocketbase/dbx" "github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/mailer" "github.com/pocketbase/pocketbase/tools/mailer"
) )
@@ -206,16 +205,14 @@ func (am *AlertManager) SendShoutrrrAlert(notificationUrl, title, message, link,
} }
func (am *AlertManager) SendTestNotification(e *core.RequestEvent) error { func (am *AlertManager) SendTestNotification(e *core.RequestEvent) error {
info, _ := e.RequestInfo() var data struct {
if info.Auth == nil { URL string `json:"url"`
return apis.NewForbiddenError("Forbidden", nil)
} }
url := e.Request.URL.Query().Get("url") err := e.BindBody(&data)
// log.Println("url", url) if err != nil || data.URL == "" {
if url == "" { return e.BadRequestError("URL is required", err)
return e.JSON(200, map[string]string{"err": "URL is required"})
} }
err := am.SendShoutrrrAlert(url, "Test Alert", "This is a notification from Beszel.", am.hub.Settings().Meta.AppURL, "View Beszel") err = am.SendShoutrrrAlert(data.URL, "Test Alert", "This is a notification from Beszel.", am.hub.Settings().Meta.AppURL, "View Beszel")
if err != nil { if err != nil {
return e.JSON(200, map[string]string{"err": err.Error()}) return e.JSON(200, map[string]string{"err": err.Error()})
} }

View File

@@ -0,0 +1,119 @@
package alerts
import (
"database/sql"
"errors"
"net/http"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
)
// UpsertUserAlerts handles API request to create or update alerts for a user
// across multiple systems (POST /api/beszel/user-alerts)
func UpsertUserAlerts(e *core.RequestEvent) error {
userID := e.Auth.Id
reqData := struct {
Min uint8 `json:"min"`
Value float64 `json:"value"`
Name string `json:"name"`
Systems []string `json:"systems"`
Overwrite bool `json:"overwrite"`
}{}
err := e.BindBody(&reqData)
if err != nil || userID == "" || reqData.Name == "" || len(reqData.Systems) == 0 {
return e.BadRequestError("Bad data", err)
}
alertsCollection, err := e.App.FindCachedCollectionByNameOrId("alerts")
if err != nil {
return err
}
err = e.App.RunInTransaction(func(txApp core.App) error {
for _, systemId := range reqData.Systems {
// find existing matching alert
alertRecord, err := txApp.FindFirstRecordByFilter(alertsCollection,
"system={:system} && name={:name} && user={:user}",
dbx.Params{"system": systemId, "name": reqData.Name, "user": userID})
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
// skip if alert already exists and overwrite is not set
if !reqData.Overwrite && alertRecord != nil {
continue
}
// create new alert if it doesn't exist
if alertRecord == nil {
alertRecord = core.NewRecord(alertsCollection)
alertRecord.Set("user", userID)
alertRecord.Set("system", systemId)
alertRecord.Set("name", reqData.Name)
}
alertRecord.Set("value", reqData.Value)
alertRecord.Set("min", reqData.Min)
if err := txApp.SaveNoValidate(alertRecord); err != nil {
return err
}
}
return nil
})
if err != nil {
return err
}
return e.JSON(http.StatusOK, map[string]any{"success": true})
}
// DeleteUserAlerts handles API request to delete alerts for a user across multiple systems
// (DELETE /api/beszel/user-alerts)
func DeleteUserAlerts(e *core.RequestEvent) error {
userID := e.Auth.Id
reqData := struct {
AlertName string `json:"name"`
Systems []string `json:"systems"`
}{}
err := e.BindBody(&reqData)
if err != nil || userID == "" || reqData.AlertName == "" || len(reqData.Systems) == 0 {
return e.BadRequestError("Bad data", err)
}
var numDeleted uint16
err = e.App.RunInTransaction(func(txApp core.App) error {
for _, systemId := range reqData.Systems {
// Find existing alert to delete
alertRecord, err := txApp.FindFirstRecordByFilter("alerts",
"system={:system} && name={:name} && user={:user}",
dbx.Params{"system": systemId, "name": reqData.AlertName, "user": userID})
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
// alert doesn't exist, continue to next system
continue
}
return err
}
if err := txApp.Delete(alertRecord); err != nil {
return err
}
numDeleted++
}
return nil
})
if err != nil {
return err
}
return e.JSON(http.StatusOK, map[string]any{"success": true, "count": numDeleted})
}

View File

@@ -12,7 +12,7 @@ func resolveHistoryOnAlertDelete(e *core.RecordEvent) error {
if !e.Record.GetBool("triggered") { if !e.Record.GetBool("triggered") {
return e.Next() return e.Next()
} }
_ = resolveAlertHistoryRecord(e.App, e.Record) _ = resolveAlertHistoryRecord(e.App, e.Record.Id)
return e.Next() return e.Next()
} }
@@ -36,19 +36,19 @@ func updateHistoryOnAlertUpdate(e *core.RecordEvent) error {
} }
// if new state is not triggered, check for matching alert history record and set it to resolved // if new state is not triggered, check for matching alert history record and set it to resolved
_ = resolveAlertHistoryRecord(e.App, new) _ = resolveAlertHistoryRecord(e.App, new.Id)
return e.Next() return e.Next()
} }
// resolveAlertHistoryRecord sets the resolved field to the current time // resolveAlertHistoryRecord sets the resolved field to the current time
func resolveAlertHistoryRecord(app core.App, alertRecord *core.Record) error { func resolveAlertHistoryRecord(app core.App, alertRecordID string) error {
alertHistoryRecords, err := app.FindRecordsByFilter( alertHistoryRecords, err := app.FindRecordsByFilter(
"alerts_history", "alerts_history",
"alert_id={:alert_id} && resolved=null", "alert_id={:alert_id} && resolved=null",
"-created", "-created",
1, 1,
0, 0,
dbx.Params{"alert_id": alertRecord.Id}, dbx.Params{"alert_id": alertRecordID},
) )
if err != nil { if err != nil {
return err return err

View File

@@ -0,0 +1,368 @@
//go:build testing
// +build testing
package alerts_test
import (
"bytes"
"encoding/json"
"io"
"net/http"
"strings"
"testing"
beszelTests "beszel/internal/tests"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
pbTests "github.com/pocketbase/pocketbase/tests"
"github.com/stretchr/testify/assert"
)
// marshal to json and return an io.Reader (for use in ApiScenario.Body)
func jsonReader(v any) io.Reader {
data, err := json.Marshal(v)
if err != nil {
panic(err)
}
return bytes.NewReader(data)
}
func TestUserAlertsApi(t *testing.T) {
hub, _ := beszelTests.NewTestHub(t.TempDir())
defer hub.Cleanup()
hub.StartHub()
user1, _ := beszelTests.CreateUser(hub, "alertstest@example.com", "password")
user1Token, _ := user1.NewAuthToken()
user2, _ := beszelTests.CreateUser(hub, "alertstest2@example.com", "password")
user2Token, _ := user2.NewAuthToken()
system1, _ := beszelTests.CreateRecord(hub, "systems", map[string]any{
"name": "system1",
"users": []string{user1.Id},
"host": "127.0.0.1",
})
system2, _ := beszelTests.CreateRecord(hub, "systems", map[string]any{
"name": "system2",
"users": []string{user1.Id, user2.Id},
"host": "127.0.0.2",
})
userRecords, _ := hub.CountRecords("users")
assert.EqualValues(t, 2, userRecords, "all users should be created")
systemRecords, _ := hub.CountRecords("systems")
assert.EqualValues(t, 2, systemRecords, "all systems should be created")
testAppFactory := func(t testing.TB) *pbTests.TestApp {
return hub.TestApp
}
scenarios := []beszelTests.ApiScenario{
{
Name: "GET not implemented - returns index",
Method: http.MethodGet,
URL: "/api/beszel/user-alerts",
ExpectedStatus: 200,
ExpectedContent: []string{"<html ", "globalThis.BESZEL"},
TestAppFactory: testAppFactory,
},
{
Name: "POST no auth",
Method: http.MethodPost,
URL: "/api/beszel/user-alerts",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "POST no body",
Method: http.MethodPost,
URL: "/api/beszel/user-alerts",
Headers: map[string]string{
"Authorization": user1Token,
},
ExpectedStatus: 400,
ExpectedContent: []string{"Bad data"},
TestAppFactory: testAppFactory,
},
{
Name: "POST bad data",
Method: http.MethodPost,
URL: "/api/beszel/user-alerts",
Headers: map[string]string{
"Authorization": user1Token,
},
ExpectedStatus: 400,
ExpectedContent: []string{"Bad data"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"invalidField": "this should cause validation error",
"threshold": "not a number",
}),
},
{
Name: "POST malformed JSON",
Method: http.MethodPost,
URL: "/api/beszel/user-alerts",
Headers: map[string]string{
"Authorization": user1Token,
},
ExpectedStatus: 400,
ExpectedContent: []string{"Bad data"},
TestAppFactory: testAppFactory,
Body: strings.NewReader(`{"alertType": "cpu", "threshold": 80, "enabled": true,}`),
},
{
Name: "POST valid alert data multiple systems",
Method: http.MethodPost,
URL: "/api/beszel/user-alerts",
Headers: map[string]string{
"Authorization": user1Token,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"success\":true"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "CPU",
"value": 69,
"min": 9,
"systems": []string{system1.Id, system2.Id},
"overwrite": false,
}),
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
// check total alerts
alerts, _ := app.CountRecords("alerts")
assert.EqualValues(t, 2, alerts, "should have 2 alerts")
// check alert has correct values
matchingAlerts, _ := app.CountRecords("alerts", dbx.HashExp{"name": "CPU", "user": user1.Id, "system": system1.Id, "value": 69, "min": 9})
assert.EqualValues(t, 1, matchingAlerts, "should have 1 alert")
},
},
{
Name: "POST valid alert data single system",
Method: http.MethodPost,
URL: "/api/beszel/user-alerts",
Headers: map[string]string{
"Authorization": user1Token,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"success\":true"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "Memory",
"systems": []string{system1.Id},
"value": 90,
"min": 10,
}),
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
user1Alerts, _ := app.CountRecords("alerts", dbx.HashExp{"user": user1.Id})
assert.EqualValues(t, 3, user1Alerts, "should have 3 alerts")
},
},
{
Name: "Overwrite: false, should not overwrite existing alert",
Method: http.MethodPost,
URL: "/api/beszel/user-alerts",
Headers: map[string]string{
"Authorization": user1Token,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"success\":true"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "CPU",
"value": 45,
"min": 5,
"systems": []string{system1.Id},
"overwrite": false,
}),
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
beszelTests.ClearCollection(t, app, "alerts")
beszelTests.CreateRecord(app, "alerts", map[string]any{
"name": "CPU",
"system": system1.Id,
"user": user1.Id,
"value": 80,
"min": 10,
})
},
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
alerts, _ := app.CountRecords("alerts")
assert.EqualValues(t, 1, alerts, "should have 1 alert")
alert, _ := app.FindFirstRecordByFilter("alerts", "name = 'CPU' && user = {:user}", dbx.Params{"user": user1.Id})
assert.EqualValues(t, 80, alert.Get("value"), "should have 80 as value")
},
},
{
Name: "Overwrite: true, should overwrite existing alert",
Method: http.MethodPost,
URL: "/api/beszel/user-alerts",
Headers: map[string]string{
"Authorization": user2Token,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"success\":true"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "CPU",
"value": 45,
"min": 5,
"systems": []string{system2.Id},
"overwrite": true,
}),
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
beszelTests.ClearCollection(t, app, "alerts")
beszelTests.CreateRecord(app, "alerts", map[string]any{
"name": "CPU",
"system": system2.Id,
"user": user2.Id,
"value": 80,
"min": 10,
})
},
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
alerts, _ := app.CountRecords("alerts")
assert.EqualValues(t, 1, alerts, "should have 1 alert")
alert, _ := app.FindFirstRecordByFilter("alerts", "name = 'CPU' && user = {:user}", dbx.Params{"user": user2.Id})
assert.EqualValues(t, 45, alert.Get("value"), "should have 45 as value")
},
},
{
Name: "DELETE no auth",
Method: http.MethodDelete,
URL: "/api/beszel/user-alerts",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "CPU",
"systems": []string{system1.Id},
}),
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
beszelTests.ClearCollection(t, app, "alerts")
beszelTests.CreateRecord(app, "alerts", map[string]any{
"name": "CPU",
"system": system1.Id,
"user": user1.Id,
"value": 80,
"min": 10,
})
},
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
alerts, _ := app.CountRecords("alerts")
assert.EqualValues(t, 1, alerts, "should have 1 alert")
},
},
{
Name: "DELETE alert",
Method: http.MethodDelete,
URL: "/api/beszel/user-alerts",
Headers: map[string]string{
"Authorization": user1Token,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"count\":1", "\"success\":true"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "CPU",
"systems": []string{system1.Id},
}),
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
beszelTests.ClearCollection(t, app, "alerts")
beszelTests.CreateRecord(app, "alerts", map[string]any{
"name": "CPU",
"system": system1.Id,
"user": user1.Id,
"value": 80,
"min": 10,
})
},
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
alerts, _ := app.CountRecords("alerts")
assert.Zero(t, alerts, "should have 0 alerts")
},
},
{
Name: "DELETE alert multiple systems",
Method: http.MethodDelete,
URL: "/api/beszel/user-alerts",
Headers: map[string]string{
"Authorization": user1Token,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"count\":2", "\"success\":true"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "Memory",
"systems": []string{system1.Id, system2.Id},
}),
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
beszelTests.ClearCollection(t, app, "alerts")
for _, systemId := range []string{system1.Id, system2.Id} {
_, err := beszelTests.CreateRecord(app, "alerts", map[string]any{
"name": "Memory",
"system": systemId,
"user": user1.Id,
"value": 90,
"min": 10,
})
assert.NoError(t, err, "should create alert")
}
alerts, _ := app.CountRecords("alerts")
assert.EqualValues(t, 2, alerts, "should have 2 alerts")
},
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
alerts, _ := app.CountRecords("alerts")
assert.Zero(t, alerts, "should have 0 alerts")
},
},
{
Name: "User 2 should not be able to delete alert of user 1",
Method: http.MethodDelete,
URL: "/api/beszel/user-alerts",
Headers: map[string]string{
"Authorization": user2Token,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"count\":1", "\"success\":true"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "CPU",
"systems": []string{system2.Id},
}),
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
beszelTests.ClearCollection(t, app, "alerts")
for _, user := range []string{user1.Id, user2.Id} {
beszelTests.CreateRecord(app, "alerts", map[string]any{
"name": "CPU",
"system": system2.Id,
"user": user,
"value": 80,
"min": 10,
})
}
alerts, _ := app.CountRecords("alerts")
assert.EqualValues(t, 2, alerts, "should have 2 alerts")
user1AlertCount, _ := app.CountRecords("alerts", dbx.HashExp{"user": user1.Id})
assert.EqualValues(t, 1, user1AlertCount, "should have 1 alert")
user2AlertCount, _ := app.CountRecords("alerts", dbx.HashExp{"user": user2.Id})
assert.EqualValues(t, 1, user2AlertCount, "should have 1 alert")
},
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
user1AlertCount, _ := app.CountRecords("alerts", dbx.HashExp{"user": user1.Id})
assert.EqualValues(t, 1, user1AlertCount, "should have 1 alert")
user2AlertCount, _ := app.CountRecords("alerts", dbx.HashExp{"user": user2.Id})
assert.Zero(t, user2AlertCount, "should have 0 alerts")
},
},
}
for _, scenario := range scenarios {
scenario.Test(t)
}
}

View File

@@ -36,8 +36,9 @@ type Stats struct {
LoadAvg15 float64 `json:"l15,omitempty" cbor:"25,keyasint,omitempty"` LoadAvg15 float64 `json:"l15,omitempty" cbor:"25,keyasint,omitempty"`
Bandwidth [2]uint64 `json:"b,omitzero" cbor:"26,keyasint,omitzero"` // [sent bytes, recv bytes] Bandwidth [2]uint64 `json:"b,omitzero" cbor:"26,keyasint,omitzero"` // [sent bytes, recv bytes]
MaxBandwidth [2]uint64 `json:"bm,omitzero" cbor:"27,keyasint,omitzero"` // [sent bytes, recv bytes] MaxBandwidth [2]uint64 `json:"bm,omitzero" cbor:"27,keyasint,omitzero"` // [sent bytes, recv bytes]
LoadAvg [3]float64 `json:"la,omitempty" cbor:"28,keyasint"`
// 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"`
Battery [2]uint8 `json:"bat,omitzero" cbor:"29,keyasint,omitzero"` // [percent, charge state, current]
} }
type GPUData struct { type GPUData struct {
@@ -81,27 +82,27 @@ const (
) )
type Info struct { type Info struct {
Hostname string `json:"h" cbor:"0,keyasint"` Hostname string `json:"h" cbor:"0,keyasint"`
KernelVersion string `json:"k,omitempty" cbor:"1,keyasint,omitempty"` KernelVersion string `json:"k,omitempty" cbor:"1,keyasint,omitempty"`
Cores int `json:"c" cbor:"2,keyasint"` Cores int `json:"c" cbor:"2,keyasint"`
Threads int `json:"t,omitempty" cbor:"3,keyasint,omitempty"` Threads int `json:"t,omitempty" cbor:"3,keyasint,omitempty"`
CpuModel string `json:"m" cbor:"4,keyasint"` CpuModel string `json:"m" cbor:"4,keyasint"`
Uptime uint64 `json:"u" cbor:"5,keyasint"` Uptime uint64 `json:"u" cbor:"5,keyasint"`
Cpu float64 `json:"cpu" cbor:"6,keyasint"` Cpu float64 `json:"cpu" cbor:"6,keyasint"`
MemPct float64 `json:"mp" cbor:"7,keyasint"` MemPct float64 `json:"mp" cbor:"7,keyasint"`
DiskPct float64 `json:"dp" cbor:"8,keyasint"` DiskPct float64 `json:"dp" cbor:"8,keyasint"`
Bandwidth float64 `json:"b" cbor:"9,keyasint"` Bandwidth float64 `json:"b" cbor:"9,keyasint"`
AgentVersion string `json:"v" cbor:"10,keyasint"` AgentVersion string `json:"v" cbor:"10,keyasint"`
Podman bool `json:"p,omitempty" cbor:"11,keyasint,omitempty"` Podman bool `json:"p,omitempty" cbor:"11,keyasint,omitempty"`
GpuPct float64 `json:"g,omitempty" cbor:"12,keyasint,omitempty"` GpuPct float64 `json:"g,omitempty" cbor:"12,keyasint,omitempty"`
DashboardTemp float64 `json:"dt,omitempty" cbor:"13,keyasint,omitempty"` DashboardTemp float64 `json:"dt,omitempty" cbor:"13,keyasint,omitempty"`
Os Os `json:"os" cbor:"14,keyasint"` Os Os `json:"os" cbor:"14,keyasint"`
LoadAvg1 float64 `json:"l1,omitempty" cbor:"15,keyasint,omitempty"` LoadAvg1 float64 `json:"l1,omitempty" cbor:"15,keyasint,omitempty"`
LoadAvg5 float64 `json:"l5,omitempty" cbor:"16,keyasint,omitempty"` LoadAvg5 float64 `json:"l5,omitempty" cbor:"16,keyasint,omitempty"`
LoadAvg15 float64 `json:"l15,omitempty" cbor:"17,keyasint,omitempty"` LoadAvg15 float64 `json:"l15,omitempty" cbor:"17,keyasint,omitempty"`
BandwidthBytes uint64 `json:"bb" cbor:"18,keyasint"` BandwidthBytes uint64 `json:"bb" cbor:"18,keyasint"`
LoadAvg [3]float64 `json:"la,omitempty" cbor:"19,keyasint"`
// TODO: remove load fields in future release in favor of load avg array // TODO: remove load fields in future release in favor of load avg array
LoadAvg [3]float64 `json:"la,omitempty" cbor:"19,keyasint"`
} }
// Final data structure to return to the hub // Final data structure to return to the hub

View File

@@ -0,0 +1,140 @@
package ghupdate
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"fmt"
"io"
"os"
"path/filepath"
"strings"
)
// extract extracts an archive file to the destination directory.
// Supports .zip and .tar.gz files based on the file extension.
func extract(srcPath, destDir string) error {
if strings.HasSuffix(srcPath, ".tar.gz") {
return extractTarGz(srcPath, destDir)
}
// Default to zip extraction
return extractZip(srcPath, destDir)
}
// extractTarGz extracts a tar.gz archive to the destination directory.
func extractTarGz(srcPath, destDir string) error {
src, err := os.Open(srcPath)
if err != nil {
return err
}
defer src.Close()
gz, err := gzip.NewReader(src)
if err != nil {
return err
}
defer gz.Close()
tr := tar.NewReader(gz)
for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
if header.Typeflag == tar.TypeDir {
if err := os.MkdirAll(filepath.Join(destDir, header.Name), 0755); err != nil {
return err
}
continue
}
if err := os.MkdirAll(filepath.Dir(filepath.Join(destDir, header.Name)), 0755); err != nil {
return err
}
outFile, err := os.Create(filepath.Join(destDir, header.Name))
if err != nil {
return err
}
if _, err := io.Copy(outFile, tr); err != nil {
outFile.Close()
return err
}
outFile.Close()
}
return nil
}
// extractZip extracts the zip archive at "src" to "dest".
//
// Note that only dirs and regular files will be extracted.
// Symbolic links, named pipes, sockets, or any other irregular files
// are skipped because they come with too many edge cases and ambiguities.
func extractZip(src, dest string) error {
zr, err := zip.OpenReader(src)
if err != nil {
return err
}
defer zr.Close()
// normalize dest path to check later for Zip Slip
dest = filepath.Clean(dest) + string(os.PathSeparator)
for _, f := range zr.File {
err := extractFile(f, dest)
if err != nil {
return err
}
}
return nil
}
// extractFile extracts the provided zipFile into "basePath/zipFileName" path,
// creating all the necessary path directories.
func extractFile(zipFile *zip.File, basePath string) error {
path := filepath.Join(basePath, zipFile.Name)
// check for Zip Slip
if !strings.HasPrefix(path, basePath) {
return fmt.Errorf("invalid file path: %s", path)
}
r, err := zipFile.Open()
if err != nil {
return err
}
defer r.Close()
// allow only dirs or regular files
if zipFile.FileInfo().IsDir() {
if err := os.MkdirAll(path, os.ModePerm); err != nil {
return err
}
} else if zipFile.FileInfo().Mode().IsRegular() {
// ensure that the file path directories are created
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
return err
}
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, zipFile.Mode())
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, r)
if err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,349 @@
// Package ghupdate implements a new command to self update the current
// executable with the latest GitHub release. This is based on PocketBase's
// ghupdate package with modifications.
package ghupdate
import (
"beszel"
"context"
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/blang/semver"
)
// Minimal color functions using ANSI escape codes
const (
colorReset = "\033[0m"
ColorYellow = "\033[33m"
colorGreen = "\033[32m"
colorCyan = "\033[36m"
colorGray = "\033[90m"
)
func ColorPrint(color, text string) {
fmt.Println(color + text + colorReset)
}
func ColorPrintf(color, format string, args ...interface{}) {
fmt.Printf(color+format+colorReset+"\n", args...)
}
// HttpClient is a base HTTP client interface (usually used for test purposes).
type HttpClient interface {
Do(req *http.Request) (*http.Response, error)
}
// Config defines the config options of the ghupdate plugin.
//
// NB! This plugin is considered experimental and its config options may change in the future.
type Config struct {
// Owner specifies the account owner of the repository (default to "pocketbase").
Owner string
// Repo specifies the name of the repository (default to "pocketbase").
Repo string
// ArchiveExecutable specifies the name of the executable file in the release archive
// (default to "pocketbase"; an additional ".exe" check is also performed as a fallback).
ArchiveExecutable string
// Optional context to use when fetching and downloading the latest release.
Context context.Context
// The HTTP client to use when fetching and downloading the latest release.
// Defaults to `http.DefaultClient`.
HttpClient HttpClient
// The data directory to use when fetching and downloading the latest release.
DataDir string
}
type updater struct {
config Config
currentVersion string
}
func Update(config Config) (updated bool, err error) {
p := &updater{
currentVersion: beszel.Version,
config: config,
}
return p.update()
}
func (p *updater) update() (updated bool, err error) {
ColorPrint(ColorYellow, "Fetching release information...")
if p.config.DataDir == "" {
p.config.DataDir = os.TempDir()
}
if p.config.Owner == "" {
p.config.Owner = "henrygd"
}
if p.config.Repo == "" {
p.config.Repo = "beszel"
}
if p.config.Context == nil {
p.config.Context = context.Background()
}
if p.config.HttpClient == nil {
p.config.HttpClient = http.DefaultClient
}
var latest *release
var useMirror bool
latest, err = fetchLatestRelease(
p.config.Context,
p.config.HttpClient,
fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", p.config.Owner, p.config.Repo),
)
// 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 {
return false, err
}
currentVersion := semver.MustParse(strings.TrimPrefix(p.currentVersion, "v"))
newVersion := semver.MustParse(strings.TrimPrefix(latest.Tag, "v"))
if newVersion.LTE(currentVersion) {
ColorPrintf(colorGreen, "You already have the latest version %s.", p.currentVersion)
return false, nil
}
suffix := archiveSuffix(p.config.ArchiveExecutable, runtime.GOOS, runtime.GOARCH)
asset, err := latest.findAssetBySuffix(suffix)
if err != nil {
return false, err
}
releaseDir := filepath.Join(p.config.DataDir, ".beszel_update")
defer os.RemoveAll(releaseDir)
ColorPrintf(ColorYellow, "Downloading %s...", asset.Name)
// download the release asset
assetPath := filepath.Join(releaseDir, asset.Name)
if err := downloadFile(p.config.Context, p.config.HttpClient, asset.DownloadUrl, assetPath, useMirror); err != nil {
return false, err
}
ColorPrintf(ColorYellow, "Extracting %s...", asset.Name)
extractDir := filepath.Join(releaseDir, "extracted_"+asset.Name)
defer os.RemoveAll(extractDir)
// Extract the archive (automatically detects format)
if err := extract(assetPath, extractDir); err != nil {
return false, err
}
ColorPrint(ColorYellow, "Replacing the executable...")
oldExec, err := os.Executable()
if err != nil {
return false, err
}
renamedOldExec := oldExec + ".old"
defer os.Remove(renamedOldExec)
newExec := filepath.Join(extractDir, p.config.ArchiveExecutable)
if _, err := os.Stat(newExec); err != nil {
// try again with an .exe extension
newExec = newExec + ".exe"
if _, fallbackErr := os.Stat(newExec); fallbackErr != nil {
return false, fmt.Errorf("the executable in the extracted path is missing or it is inaccessible: %v, %v", err, fallbackErr)
}
}
// rename the current executable
if err := os.Rename(oldExec, renamedOldExec); err != nil {
return false, fmt.Errorf("failed to rename the current executable: %w", err)
}
tryToRevertExecChanges := func() {
if revertErr := os.Rename(renamedOldExec, oldExec); revertErr != nil {
slog.Debug(
"Failed to revert executable",
slog.String("old", renamedOldExec),
slog.String("new", oldExec),
slog.String("error", revertErr.Error()),
)
}
}
// replace with the extracted binary
if err := os.Rename(newExec, oldExec); err != nil {
// If rename fails due to cross-device link, try copying instead
if isCrossDeviceError(err) {
if err := copyFile(newExec, oldExec); err != nil {
tryToRevertExecChanges()
return false, fmt.Errorf("failed replacing the executable: %w", err)
}
} else {
tryToRevertExecChanges()
return false, fmt.Errorf("failed replacing the executable: %w", err)
}
}
ColorPrint(colorGray, "---")
ColorPrint(colorGreen, "Update completed successfully! You can start the executable as usual.")
// print the release notes
if latest.Body != "" {
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))
ColorPrint(colorCyan, releaseNotes)
fmt.Print("\n")
}
return true, nil
}
func fetchLatestRelease(
ctx context.Context,
client HttpClient,
url string,
) (*release, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
rawBody, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
}
// http.Client doesn't treat non 2xx responses as error
if res.StatusCode >= 400 {
return nil, fmt.Errorf(
"(%d) failed to fetch latest releases:\n%s",
res.StatusCode,
string(rawBody),
)
}
result := &release{}
if err := json.Unmarshal(rawBody, result); err != nil {
return nil, err
}
return result, nil
}
func downloadFile(
ctx context.Context,
client HttpClient,
url string,
destPath string,
useMirror bool,
) error {
if useMirror {
url = strings.Replace(url, "github.com", "gh.beszel.dev", 1)
}
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return err
}
res, err := client.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
// http.Client doesn't treat non 2xx responses as error
if res.StatusCode >= 400 {
return fmt.Errorf("(%d) failed to send download file request", res.StatusCode)
}
// ensure that the dest parent dir(s) exist
if err := os.MkdirAll(filepath.Dir(destPath), os.ModePerm); err != nil {
return err
}
dest, err := os.Create(destPath)
if err != nil {
return err
}
defer dest.Close()
if _, err := io.Copy(dest, res.Body); err != nil {
return err
}
return nil
}
// isCrossDeviceError checks if the error is due to a cross-device link
func isCrossDeviceError(err error) bool {
return err != nil && (strings.Contains(err.Error(), "cross-device") ||
strings.Contains(err.Error(), "EXDEV"))
}
// copyFile copies a file from src to dst, preserving permissions
func copyFile(src, dst string) error {
sourceFile, err := os.Open(src)
if err != nil {
return err
}
defer sourceFile.Close()
destFile, err := os.Create(dst)
if err != nil {
return err
}
defer destFile.Close()
// Copy the file contents
if _, err := io.Copy(destFile, sourceFile); err != nil {
return err
}
// Preserve the original file permissions
sourceInfo, err := sourceFile.Stat()
if err != nil {
return err
}
return destFile.Chmod(sourceInfo.Mode())
}
func archiveSuffix(binaryName, goos, goarch string) string {
if goos == "windows" {
return fmt.Sprintf("%s_%s_%s.zip", binaryName, goos, goarch)
}
return fmt.Sprintf("%s_%s_%s.tar.gz", binaryName, goos, goarch)
}

View File

@@ -0,0 +1,45 @@
package ghupdate
import (
"path/filepath"
"testing"
)
func TestReleaseFindAssetBySuffix(t *testing.T) {
r := release{
Assets: []*releaseAsset{
{Name: "test1.zip", Id: 1},
{Name: "test2.zip", Id: 2},
{Name: "test22.zip", Id: 22},
{Name: "test3.zip", Id: 3},
},
}
asset, err := r.findAssetBySuffix("2.zip")
if err != nil {
t.Fatalf("Expected nil, got err: %v", err)
}
if asset.Id != 2 {
t.Fatalf("Expected asset with id %d, got %v", 2, asset)
}
}
func TestExtractFailure(t *testing.T) {
testDir := t.TempDir()
// Test with missing zip file
missingZipPath := filepath.Join(testDir, "missing_test.zip")
extractedPath := filepath.Join(testDir, "zip_extract")
if err := extract(missingZipPath, extractedPath); err == nil {
t.Fatal("Expected Extract to fail due to missing zip file")
}
// Test with missing tar.gz file
missingTarPath := filepath.Join(testDir, "missing_test.tar.gz")
if err := extract(missingTarPath, extractedPath); err == nil {
t.Fatal("Expected Extract to fail due to missing tar.gz file")
}
}

View File

@@ -0,0 +1,36 @@
package ghupdate
import (
"errors"
"strings"
)
type releaseAsset struct {
Name string `json:"name"`
DownloadUrl string `json:"browser_download_url"`
Id int `json:"id"`
Size int `json:"size"`
}
type release struct {
Name string `json:"name"`
Tag string `json:"tag_name"`
Published string `json:"published_at"`
Url string `json:"html_url"`
Body string `json:"body"`
Assets []*releaseAsset `json:"assets"`
Id int `json:"id"`
}
// findAssetBySuffix returns the first available asset containing the specified suffix.
func (r *release) findAssetBySuffix(suffix string) (*releaseAsset, error) {
if suffix != "" {
for _, asset := range r.Assets {
if strings.HasSuffix(asset.Name, suffix) {
return asset, nil
}
}
}
return nil, errors.New("missing asset containing " + suffix)
}

View File

@@ -10,7 +10,6 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/pocketbase/dbx" "github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"github.com/spf13/cast" "github.com/spf13/cast"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
@@ -279,9 +278,8 @@ func createFingerprintRecord(app core.App, systemID, token string) error {
// Returns the current config.yml file as a JSON object // Returns the current config.yml file as a JSON object
func GetYamlConfig(e *core.RequestEvent) error { func GetYamlConfig(e *core.RequestEvent) error {
info, _ := e.RequestInfo() if e.Auth.GetString("role") != "admin" {
if info.Auth == nil || info.Auth.GetString("role") != "admin" { return e.ForbiddenError("Requires admin role", nil)
return apis.NewForbiddenError("Forbidden", nil)
} }
configContent, err := generateYAML(e.App) configContent, err := generateYAML(e.App)
if err != nil { if err != nil {

View File

@@ -224,48 +224,48 @@ func (h *Hub) registerCronJobs(_ *core.ServeEvent) error {
// custom api routes // custom api routes
func (h *Hub) registerApiRoutes(se *core.ServeEvent) error { func (h *Hub) registerApiRoutes(se *core.ServeEvent) error {
// returns public key and version // auth protected routes
se.Router.GET("/api/beszel/getkey", func(e *core.RequestEvent) error { apiAuth := se.Router.Group("/api/beszel")
info, _ := e.RequestInfo() apiAuth.Bind(apis.RequireAuth())
if info.Auth == nil { // auth optional routes
return apis.NewForbiddenError("Forbidden", nil) apiNoAuth := se.Router.Group("/api/beszel")
}
return e.JSON(http.StatusOK, map[string]string{"key": h.pubKey, "v": beszel.Version}) // create first user endpoint only needed if no users exist
}) if totalUsers, _ := se.App.CountRecords("users"); totalUsers == 0 {
apiNoAuth.POST("/create-user", h.um.CreateFirstUser)
}
// check if first time setup on login page // check if first time setup on login page
se.Router.GET("/api/beszel/first-run", func(e *core.RequestEvent) error { apiNoAuth.GET("/first-run", func(e *core.RequestEvent) error {
total, err := h.CountRecords("users") total, err := e.App.CountRecords("users")
return e.JSON(http.StatusOK, map[string]bool{"firstRun": err == nil && total == 0}) return e.JSON(http.StatusOK, map[string]bool{"firstRun": err == nil && total == 0})
}) })
// get public key and version
apiAuth.GET("/getkey", func(e *core.RequestEvent) error {
return e.JSON(http.StatusOK, map[string]string{"key": h.pubKey, "v": beszel.Version})
})
// send test notification // send test notification
se.Router.GET("/api/beszel/send-test-notification", h.SendTestNotification) apiAuth.POST("/test-notification", h.SendTestNotification)
// API endpoint to get config.yml content // get config.yml content
se.Router.GET("/api/beszel/config-yaml", config.GetYamlConfig) apiAuth.GET("/config-yaml", config.GetYamlConfig)
// handle agent websocket connection // handle agent websocket connection
se.Router.GET("/api/beszel/agent-connect", h.handleAgentConnect) apiNoAuth.GET("/agent-connect", h.handleAgentConnect)
// get or create universal tokens // get or create universal tokens
se.Router.GET("/api/beszel/universal-token", h.getUniversalToken) apiAuth.GET("/universal-token", h.getUniversalToken)
// create first user endpoint only needed if no users exist // update / delete user alerts
if totalUsers, _ := h.CountRecords("users"); totalUsers == 0 { apiAuth.POST("/user-alerts", alerts.UpsertUserAlerts)
se.Router.POST("/api/beszel/create-user", h.um.CreateFirstUser) apiAuth.DELETE("/user-alerts", alerts.DeleteUserAlerts)
}
return nil return nil
} }
// Handler for universal token API endpoint (create, read, delete) // Handler for universal token API endpoint (create, read, delete)
func (h *Hub) getUniversalToken(e *core.RequestEvent) error { func (h *Hub) getUniversalToken(e *core.RequestEvent) error {
info, err := e.RequestInfo()
if err != nil || info.Auth == nil {
return apis.NewForbiddenError("Forbidden", nil)
}
tokenMap := universalTokenMap.GetMap() tokenMap := universalTokenMap.GetMap()
userID := info.Auth.Id userID := e.Auth.Id
query := e.Request.URL.Query() query := e.Request.URL.Query()
token := query.Get("token") token := query.Get("token")
tokenSet := token != ""
if !tokenSet { if token == "" {
// return existing token if it exists // return existing token if it exists
if token, _, ok := tokenMap.GetByValue(userID); ok { if token, _, ok := tokenMap.GetByValue(userID); ok {
return e.JSON(http.StatusOK, map[string]any{"token": token, "active": true}) return e.JSON(http.StatusOK, map[string]any{"token": token, "active": true})

View File

@@ -4,27 +4,37 @@
package hub_test package hub_test
import ( import (
"beszel/internal/tests" beszelTests "beszel/internal/tests"
"testing" "testing"
"bytes"
"crypto/ed25519" "crypto/ed25519"
"encoding/json"
"encoding/pem" "encoding/pem"
"io"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/pocketbase/pocketbase/core"
pbTests "github.com/pocketbase/pocketbase/tests"
"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"
) )
func getTestHub(t testing.TB) *tests.TestHub { // marshal to json and return an io.Reader (for use in ApiScenario.Body)
hub, _ := tests.NewTestHub(t.TempDir()) func jsonReader(v any) io.Reader {
return hub data, err := json.Marshal(v)
if err != nil {
panic(err)
}
return bytes.NewReader(data)
} }
func TestMakeLink(t *testing.T) { func TestMakeLink(t *testing.T) {
hub := getTestHub(t) hub, _ := beszelTests.NewTestHub(t.TempDir())
tests := []struct { tests := []struct {
name string name string
@@ -114,7 +124,7 @@ func TestMakeLink(t *testing.T) {
} }
func TestGetSSHKey(t *testing.T) { func TestGetSSHKey(t *testing.T) {
hub := getTestHub(t) hub, _ := beszelTests.NewTestHub(t.TempDir())
// Test Case 1: Key generation (no existing key) // Test Case 1: Key generation (no existing key)
t.Run("KeyGeneration", func(t *testing.T) { t.Run("KeyGeneration", func(t *testing.T) {
@@ -254,3 +264,340 @@ func TestGetSSHKey(t *testing.T) {
} }
}) })
} }
func TestApiRoutesAuthentication(t *testing.T) {
hub, _ := beszelTests.NewTestHub(t.TempDir())
defer hub.Cleanup()
hub.StartHub()
// Create test user and get auth token
user, err := beszelTests.CreateUser(hub, "testuser@example.com", "password123")
require.NoError(t, err, "Failed to create test user")
adminUser, err := beszelTests.CreateRecord(hub, "users", map[string]any{
"email": "admin@example.com",
"password": "password123",
"role": "admin",
})
require.NoError(t, err, "Failed to create admin user")
adminUserToken, err := adminUser.NewAuthToken()
// superUser, err := beszelTests.CreateRecord(hub, core.CollectionNameSuperusers, map[string]any{
// "email": "superuser@example.com",
// "password": "password123",
// })
// require.NoError(t, err, "Failed to create superuser")
userToken, err := user.NewAuthToken()
require.NoError(t, err, "Failed to create auth token")
// Create test system for user-alerts endpoints
system, err := beszelTests.CreateRecord(hub, "systems", map[string]any{
"name": "test-system",
"users": []string{user.Id},
"host": "127.0.0.1",
})
require.NoError(t, err, "Failed to create test system")
testAppFactory := func(t testing.TB) *pbTests.TestApp {
return hub.TestApp
}
scenarios := []beszelTests.ApiScenario{
// Auth Protected Routes - Should require authentication
{
Name: "POST /test-notification - no auth should fail",
Method: http.MethodPost,
URL: "/api/beszel/test-notification",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"url": "generic://127.0.0.1",
}),
},
{
Name: "POST /test-notification - with auth should succeed",
Method: http.MethodPost,
URL: "/api/beszel/test-notification",
TestAppFactory: testAppFactory,
Headers: map[string]string{
"Authorization": userToken,
},
Body: jsonReader(map[string]any{
"url": "generic://127.0.0.1",
}),
ExpectedStatus: 200,
ExpectedContent: []string{"sending message"},
},
{
Name: "GET /config-yaml - no auth should fail",
Method: http.MethodGet,
URL: "/api/beszel/config-yaml",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /config-yaml - with user auth should fail",
Method: http.MethodGet,
URL: "/api/beszel/config-yaml",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 403,
ExpectedContent: []string{"Requires admin"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /config-yaml - with admin auth should succeed",
Method: http.MethodGet,
URL: "/api/beszel/config-yaml",
Headers: map[string]string{
"Authorization": adminUserToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"test-system"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /universal-token - no auth should fail",
Method: http.MethodGet,
URL: "/api/beszel/universal-token",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /universal-token - with auth should succeed",
Method: http.MethodGet,
URL: "/api/beszel/universal-token",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"active", "token"},
TestAppFactory: testAppFactory,
},
{
Name: "POST /user-alerts - no auth should fail",
Method: http.MethodPost,
URL: "/api/beszel/user-alerts",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "CPU",
"value": 80,
"min": 10,
"systems": []string{system.Id},
}),
},
{
Name: "POST /user-alerts - with auth should succeed",
Method: http.MethodPost,
URL: "/api/beszel/user-alerts",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"success\":true"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "CPU",
"value": 80,
"min": 10,
"systems": []string{system.Id},
}),
},
{
Name: "DELETE /user-alerts - no auth should fail",
Method: http.MethodDelete,
URL: "/api/beszel/user-alerts",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "CPU",
"systems": []string{system.Id},
}),
},
{
Name: "DELETE /user-alerts - with auth should succeed",
Method: http.MethodDelete,
URL: "/api/beszel/user-alerts",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"success\":true"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "CPU",
"systems": []string{system.Id},
}),
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
// Create an alert to delete
beszelTests.CreateRecord(app, "alerts", map[string]any{
"name": "CPU",
"system": system.Id,
"user": user.Id,
"value": 80,
"min": 10,
})
},
},
// Auth Optional Routes - Should work without authentication
{
Name: "GET /getkey - no auth should fail",
Method: http.MethodGet,
URL: "/api/beszel/getkey",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /getkey - with auth should also succeed",
Method: http.MethodGet,
URL: "/api/beszel/getkey",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"key\":", "\"v\":"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /first-run - no auth should succeed",
Method: http.MethodGet,
URL: "/api/beszel/first-run",
ExpectedStatus: 200,
ExpectedContent: []string{"\"firstRun\":false"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /first-run - with auth should also succeed",
Method: http.MethodGet,
URL: "/api/beszel/first-run",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"firstRun\":false"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /agent-connect - no auth should succeed (websocket upgrade fails but route is accessible)",
Method: http.MethodGet,
URL: "/api/beszel/agent-connect",
ExpectedStatus: 400,
ExpectedContent: []string{},
TestAppFactory: testAppFactory,
},
{
Name: "POST /test-notification - invalid auth token should fail",
Method: http.MethodPost,
URL: "/api/beszel/test-notification",
Body: jsonReader(map[string]any{
"url": "generic://127.0.0.1",
}),
Headers: map[string]string{
"Authorization": "invalid-token",
},
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "POST /user-alerts - invalid auth token should fail",
Method: http.MethodPost,
URL: "/api/beszel/user-alerts",
Headers: map[string]string{
"Authorization": "invalid-token",
},
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "CPU",
"value": 80,
"min": 10,
"systems": []string{system.Id},
}),
},
}
for _, scenario := range scenarios {
scenario.Test(t)
}
}
func TestCreateUserEndpointAvailability(t *testing.T) {
t.Run("CreateUserEndpoint available when no users exist", func(t *testing.T) {
hub, _ := beszelTests.NewTestHub(t.TempDir())
defer hub.Cleanup()
// Ensure no users exist
userCount, err := hub.CountRecords("users")
require.NoError(t, err)
require.Zero(t, userCount, "Should start with no users")
hub.StartHub()
testAppFactory := func(t testing.TB) *pbTests.TestApp {
return hub.TestApp
}
scenario := 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: testAppFactory,
}
scenario.Test(t)
// Verify user was created
userCount, err = hub.CountRecords("users")
require.NoError(t, err)
require.EqualValues(t, 1, userCount, "Should have created one user")
})
t.Run("CreateUserEndpoint not available when users exist", func(t *testing.T) {
hub, _ := beszelTests.NewTestHub(t.TempDir())
defer hub.Cleanup()
// Create a user first
_, err := beszelTests.CreateUser(hub, "existing@example.com", "password")
require.NoError(t, err)
hub.StartHub()
testAppFactory := func(t testing.TB) *pbTests.TestApp {
return hub.TestApp
}
scenario := beszelTests.ApiScenario{
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": "another@example.com",
"password": "password123",
}),
ExpectedStatus: 404,
ExpectedContent: []string{"wasn't found"},
TestAppFactory: testAppFactory,
}
scenario.Test(t)
})
}

View File

@@ -29,7 +29,7 @@ func TestSystemManagerNew(t *testing.T) {
user, err := tests.CreateUser(hub, "test@test.com", "testtesttest") user, err := tests.CreateUser(hub, "test@test.com", "testtesttest")
require.NoError(t, err) require.NoError(t, err)
synctest.Run(func() { synctest.Test(t, func(t *testing.T) {
sm.Initialize() sm.Initialize()
record, err := tests.CreateRecord(hub, "systems", map[string]any{ record, err := tests.CreateRecord(hub, "systems", map[string]any{
@@ -110,9 +110,11 @@ func TestSystemManagerNew(t *testing.T) {
err = hub.Delete(record) err = hub.Delete(record)
require.NoError(t, err) require.NoError(t, err)
assert.False(t, sm.HasSystem(record.Id), "System should not exist in the store after deletion") assert.False(t, sm.HasSystem(record.Id), "System should not exist in the store after deletion")
})
testOld(t, hub) testOld(t, hub)
synctest.Test(t, func(t *testing.T) {
time.Sleep(time.Second) time.Sleep(time.Second)
synctest.Wait() synctest.Wait()

View File

@@ -1,57 +1,73 @@
package hub package hub
import ( import (
"beszel" "beszel/internal/ghupdate"
"fmt" "fmt"
"log"
"os" "os"
"strings" "os/exec"
"github.com/blang/semver"
"github.com/rhysd/go-github-selfupdate/selfupdate"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// Update updates beszel to the latest version // Update updates beszel to the latest version
func Update(_ *cobra.Command, _ []string) { func Update(_ *cobra.Command, _ []string) {
var latest *selfupdate.Release dataDir := os.TempDir()
var found bool
var err error // set dataDir to ./beszel_data if it exists
currentVersion := semver.MustParse(beszel.Version) if _, err := os.Stat("./beszel_data"); err == nil {
fmt.Println("beszel", currentVersion) dataDir = "./beszel_data"
fmt.Println("Checking for updates...") }
updater, _ := selfupdate.NewUpdater(selfupdate.Config{
Filters: []string{"beszel_"}, updated, err := ghupdate.Update(ghupdate.Config{
ArchiveExecutable: "beszel",
DataDir: dataDir,
}) })
latest, found, err = updater.DetectLatest("henrygd/beszel")
if err != nil { if err != nil {
fmt.Println("Error checking for updates:", err) log.Fatal(err)
os.Exit(1)
} }
if !updated {
if !found {
fmt.Println("No updates found")
os.Exit(0)
}
fmt.Println("Latest version:", latest.Version)
if latest.Version.LTE(currentVersion) {
fmt.Println("You are up to date")
return return
} }
var binaryPath string // Try to restart the service if it's running
fmt.Printf("Updating from %s to %s...\n", currentVersion, latest.Version) restartService()
binaryPath, err = os.Executable() }
if err != nil {
fmt.Println("Error getting binary path:", err) // restartService attempts to restart the beszel service
os.Exit(1) func restartService() {
} // Check if we're running as a service by looking for systemd
err = selfupdate.UpdateTo(latest.AssetURL, binaryPath) if _, err := exec.LookPath("systemctl"); err == nil {
if err != nil { // Check if beszel service exists and is active
fmt.Println("Please try rerunning with sudo. Error:", err) cmd := exec.Command("systemctl", "is-active", "beszel.service")
os.Exit(1) if err := cmd.Run(); err == nil {
} fmt.Println("Restarting beszel service...")
fmt.Printf("Successfully updated to %s\n\n%s\n", latest.Version, strings.TrimSpace(latest.ReleaseNotes)) restartCmd := exec.Command("systemctl", "restart", "beszel.service")
if err := restartCmd.Run(); err != nil {
fmt.Printf("Warning: Failed to restart service: %v\n", err)
fmt.Println("Please restart the service manually: sudo systemctl restart beszel")
} else {
fmt.Println("Service restarted successfully")
}
return
}
}
// Check for OpenRC (Alpine Linux)
if _, err := exec.LookPath("rc-service"); err == nil {
cmd := exec.Command("rc-service", "beszel", "status")
if err := cmd.Run(); err == nil {
fmt.Println("Restarting beszel service...")
restartCmd := exec.Command("rc-service", "beszel", "restart")
if err := restartCmd.Run(); err != nil {
fmt.Printf("Warning: Failed to restart service: %v\n", err)
fmt.Println("Please restart the service manually: sudo rc-service beszel restart")
} else {
fmt.Println("Service restarted successfully")
}
return
}
}
fmt.Println("Note: Service restart not attempted. If running as a service, restart manually.")
} }

View File

@@ -172,6 +172,8 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
tempStats = system.Stats{} tempStats = system.Stats{}
sum := &sumStats sum := &sumStats
stats := &tempStats stats := &tempStats
// necessary because uint8 is not big enough for the sum
batterySum := 0
count := float64(len(records)) count := float64(len(records))
tempCount := float64(0) tempCount := float64(0)
@@ -208,6 +210,8 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
sum.LoadAvg[2] += stats.LoadAvg[2] sum.LoadAvg[2] += stats.LoadAvg[2]
sum.Bandwidth[0] += stats.Bandwidth[0] sum.Bandwidth[0] += stats.Bandwidth[0]
sum.Bandwidth[1] += stats.Bandwidth[1] sum.Bandwidth[1] += stats.Bandwidth[1]
batterySum += int(stats.Battery[0])
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.MaxNetworkSent = max(sum.MaxNetworkSent, stats.MaxNetworkSent, stats.NetworkSent) sum.MaxNetworkSent = max(sum.MaxNetworkSent, stats.MaxNetworkSent, stats.NetworkSent)
@@ -290,6 +294,7 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
sum.LoadAvg[2] = twoDecimals(sum.LoadAvg[2] / count) sum.LoadAvg[2] = twoDecimals(sum.LoadAvg[2] / count)
sum.Bandwidth[0] = sum.Bandwidth[0] / uint64(count) sum.Bandwidth[0] = sum.Bandwidth[0] / uint64(count)
sum.Bandwidth[1] = sum.Bandwidth[1] / uint64(count) sum.Bandwidth[1] = sum.Bandwidth[1] / uint64(count)
sum.Battery[0] = uint8(batterySum / int(count))
// Average temperatures // Average temperatures
if sum.Temperatures != nil && tempCount > 0 { if sum.Temperatures != nil && tempCount > 0 {
for key := range sum.Temperatures { for key := range sum.Temperatures {

View File

@@ -0,0 +1,309 @@
package tests
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"maps"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
pbtests "github.com/pocketbase/pocketbase/tests"
"github.com/pocketbase/pocketbase/tools/hook"
)
// NOTE: This is a copy of https://github.com/pocketbase/pocketbase/blob/master/tests/api.go
// with the following changes:
// - Removed automatic cleanup of the test app in ApiScenario.Test (Aug 17 2025)
// ApiScenario defines a single api request test case/scenario.
type ApiScenario struct {
// Name is the test name.
Name string
// Method is the HTTP method of the test request to use.
Method string
// URL is the url/path of the endpoint you want to test.
URL string
// Body specifies the body to send with the request.
//
// For example:
//
// strings.NewReader(`{"title":"abc"}`)
Body io.Reader
// Headers specifies the headers to send with the request (e.g. "Authorization": "abc")
Headers map[string]string
// Delay adds a delay before checking the expectations usually
// to ensure that all fired non-awaited go routines have finished
Delay time.Duration
// Timeout specifies how long to wait before cancelling the request context.
//
// A zero or negative value means that there will be no timeout.
Timeout time.Duration
// expectations
// ---------------------------------------------------------------
// ExpectedStatus specifies the expected response HTTP status code.
ExpectedStatus int
// List of keywords that MUST exist in the response body.
//
// Either ExpectedContent or NotExpectedContent must be set if the response body is non-empty.
// Leave both fields empty if you want to ensure that the response didn't have any body (e.g. 204).
ExpectedContent []string
// List of keywords that MUST NOT exist in the response body.
//
// Either ExpectedContent or NotExpectedContent must be set if the response body is non-empty.
// Leave both fields empty if you want to ensure that the response didn't have any body (e.g. 204).
NotExpectedContent []string
// List of hook events to check whether they were fired or not.
//
// You can use the wildcard "*" event key if you want to ensure
// that no other hook events except those listed have been fired.
//
// For example:
//
// map[string]int{ "*": 0 } // no hook events were fired
// map[string]int{ "*": 0, "EventA": 2 } // no hook events, except EventA were fired
// map[string]int{ "EventA": 2, "EventB": 0 } // ensures that EventA was fired exactly 2 times and EventB exactly 0 times.
ExpectedEvents map[string]int
// test hooks
// ---------------------------------------------------------------
TestAppFactory func(t testing.TB) *pbtests.TestApp
BeforeTestFunc func(t testing.TB, app *pbtests.TestApp, e *core.ServeEvent)
AfterTestFunc func(t testing.TB, app *pbtests.TestApp, res *http.Response)
}
// Test executes the test scenario.
//
// Example:
//
// func TestListExample(t *testing.T) {
// scenario := tests.ApiScenario{
// Name: "list example collection",
// Method: http.MethodGet,
// URL: "/api/collections/example/records",
// ExpectedStatus: 200,
// ExpectedContent: []string{
// `"totalItems":3`,
// `"id":"0yxhwia2amd8gec"`,
// `"id":"achvryl401bhse3"`,
// `"id":"llvuca81nly1qls"`,
// },
// ExpectedEvents: map[string]int{
// "OnRecordsListRequest": 1,
// "OnRecordEnrich": 3,
// },
// }
//
// scenario.Test(t)
// }
func (scenario *ApiScenario) Test(t *testing.T) {
t.Run(scenario.normalizedName(), func(t *testing.T) {
scenario.test(t)
})
}
// Benchmark benchmarks the test scenario.
//
// Example:
//
// func BenchmarkListExample(b *testing.B) {
// scenario := tests.ApiScenario{
// Name: "list example collection",
// Method: http.MethodGet,
// URL: "/api/collections/example/records",
// ExpectedStatus: 200,
// ExpectedContent: []string{
// `"totalItems":3`,
// `"id":"0yxhwia2amd8gec"`,
// `"id":"achvryl401bhse3"`,
// `"id":"llvuca81nly1qls"`,
// },
// ExpectedEvents: map[string]int{
// "OnRecordsListRequest": 1,
// "OnRecordEnrich": 3,
// },
// }
//
// scenario.Benchmark(b)
// }
func (scenario *ApiScenario) Benchmark(b *testing.B) {
b.Run(scenario.normalizedName(), func(b *testing.B) {
for i := 0; i < b.N; i++ {
scenario.test(b)
}
})
}
func (scenario *ApiScenario) normalizedName() string {
var name = scenario.Name
if name == "" {
name = fmt.Sprintf("%s:%s", scenario.Method, scenario.URL)
}
return name
}
func (scenario *ApiScenario) test(t testing.TB) {
var testApp *pbtests.TestApp
if scenario.TestAppFactory != nil {
testApp = scenario.TestAppFactory(t)
if testApp == nil {
t.Fatal("TestAppFactory must return a non-nill app instance")
}
} else {
var testAppErr error
testApp, testAppErr = pbtests.NewTestApp()
if testAppErr != nil {
t.Fatalf("Failed to initialize the test app instance: %v", testAppErr)
}
}
// defer testApp.Cleanup()
baseRouter, err := apis.NewRouter(testApp)
if err != nil {
t.Fatal(err)
}
// manually trigger the serve event to ensure that custom app routes and middlewares are registered
serveEvent := new(core.ServeEvent)
serveEvent.App = testApp
serveEvent.Router = baseRouter
serveErr := testApp.OnServe().Trigger(serveEvent, func(e *core.ServeEvent) error {
if scenario.BeforeTestFunc != nil {
scenario.BeforeTestFunc(t, testApp, e)
}
// reset the event counters in case a hook was triggered from a before func (eg. db save)
testApp.ResetEventCalls()
// add middleware to timeout long-running requests (eg. keep-alive routes)
e.Router.Bind(&hook.Handler[*core.RequestEvent]{
Func: func(re *core.RequestEvent) error {
slowTimer := time.AfterFunc(3*time.Second, func() {
t.Logf("[WARN] Long running test %q", scenario.Name)
})
defer slowTimer.Stop()
if scenario.Timeout > 0 {
ctx, cancelFunc := context.WithTimeout(re.Request.Context(), scenario.Timeout)
defer cancelFunc()
re.Request = re.Request.Clone(ctx)
}
return re.Next()
},
Priority: -9999,
})
recorder := httptest.NewRecorder()
req := httptest.NewRequest(scenario.Method, scenario.URL, scenario.Body)
// set default header
req.Header.Set("content-type", "application/json")
// set scenario headers
for k, v := range scenario.Headers {
req.Header.Set(k, v)
}
// execute request
mux, err := e.Router.BuildMux()
if err != nil {
t.Fatalf("Failed to build router mux: %v", err)
}
mux.ServeHTTP(recorder, req)
res := recorder.Result()
if res.StatusCode != scenario.ExpectedStatus {
t.Errorf("Expected status code %d, got %d", scenario.ExpectedStatus, res.StatusCode)
}
if scenario.Delay > 0 {
time.Sleep(scenario.Delay)
}
if len(scenario.ExpectedContent) == 0 && len(scenario.NotExpectedContent) == 0 {
if len(recorder.Body.Bytes()) != 0 {
t.Errorf("Expected empty body, got \n%v", recorder.Body.String())
}
} else {
// normalize json response format
buffer := new(bytes.Buffer)
err := json.Compact(buffer, recorder.Body.Bytes())
var normalizedBody string
if err != nil {
// not a json...
normalizedBody = recorder.Body.String()
} else {
normalizedBody = buffer.String()
}
for _, item := range scenario.ExpectedContent {
if !strings.Contains(normalizedBody, item) {
t.Errorf("Cannot find %v in response body \n%v", item, normalizedBody)
break
}
}
for _, item := range scenario.NotExpectedContent {
if strings.Contains(normalizedBody, item) {
t.Errorf("Didn't expect %v in response body \n%v", item, normalizedBody)
break
}
}
}
remainingEvents := maps.Clone(testApp.EventCalls)
var noOtherEventsShouldRemain bool
for event, expectedNum := range scenario.ExpectedEvents {
if event == "*" && expectedNum <= 0 {
noOtherEventsShouldRemain = true
continue
}
actualNum := remainingEvents[event]
if actualNum != expectedNum {
t.Errorf("Expected event %s to be called %d, got %d", event, expectedNum, actualNum)
}
delete(remainingEvents, event)
}
if noOtherEventsShouldRemain && len(remainingEvents) > 0 {
t.Errorf("Missing expected remaining events:\n%#v\nAll triggered app events are:\n%#v", remainingEvents, testApp.EventCalls)
}
if scenario.AfterTestFunc != nil {
scenario.AfterTestFunc(t, testApp, res)
}
return nil
})
if serveErr != nil {
t.Fatalf("Failed to trigger app serve hook: %v", serveErr)
}
}

View File

@@ -6,9 +6,12 @@ package tests
import ( import (
"beszel/internal/hub" "beszel/internal/hub"
"fmt"
"testing"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tests" "github.com/pocketbase/pocketbase/tests"
"github.com/stretchr/testify/assert"
_ "github.com/pocketbase/pocketbase/migrations" _ "github.com/pocketbase/pocketbase/migrations"
) )
@@ -86,3 +89,10 @@ func CreateRecord(app core.App, collectionName string, fields map[string]any) (*
return record, app.Save(record) return record, app.Save(record)
} }
func ClearCollection(t testing.TB, app core.App, collectionName string) error {
_, err := app.DB().NewQuery(fmt.Sprintf("DELETE from %s", collectionName)).Execute()
recordCount, err := app.CountRecords(collectionName)
assert.EqualValues(t, recordCount, 0, "should have 0 records after clearing")
return err
}

View File

@@ -14,18 +14,6 @@ type UserManager struct {
app core.App app core.App
} }
type UserSettings struct {
ChartTime string `json:"chartTime"`
NotificationEmails []string `json:"emails"`
NotificationWebhooks []string `json:"webhooks"`
// UnitTemp uint8 `json:"unitTemp"` // 0 for Celsius, 1 for Fahrenheit
// UnitNet uint8 `json:"unitNet"` // 0 for bytes, 1 for bits
// UnitDisk uint8 `json:"unitDisk"` // 0 for bytes, 1 for bits
// New field for alert history retention (e.g., "1m", "3m", "6m", "1y")
AlertHistoryRetention string `json:"alertHistoryRetention,omitempty"`
}
func NewUserManager(app core.App) *UserManager { func NewUserManager(app core.App) *UserManager {
return &UserManager{ return &UserManager{
app: app, app: app,
@@ -44,7 +32,10 @@ func (um *UserManager) InitializeUserRole(e *core.RecordEvent) error {
func (um *UserManager) InitializeUserSettings(e *core.RecordEvent) error { func (um *UserManager) InitializeUserSettings(e *core.RecordEvent) error {
record := e.Record record := e.Record
// intialize settings with defaults (zero values can be ignored) // intialize settings with defaults (zero values can be ignored)
settings := UserSettings{ settings := struct {
ChartTime string `json:"chartTime"`
Emails []string `json:"emails"`
}{
ChartTime: "1h", ChartTime: "1h",
} }
record.UnmarshalJSONField("settings", &settings) record.UnmarshalJSONField("settings", &settings)
@@ -59,10 +50,7 @@ func (um *UserManager) InitializeUserSettings(e *core.RecordEvent) error {
log.Println("failed to get user email", "err", err) log.Println("failed to get user email", "err", err)
return err return err
} }
settings.NotificationEmails = []string{user.Email} settings.Emails = []string{user.Email}
if len(settings.NotificationWebhooks) == 0 {
settings.NotificationWebhooks = []string{""}
}
record.Set("settings", settings) record.Set("settings", settings)
return e.Next() return e.Next()
} }

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "beszel", "name": "beszel",
"private": true, "private": true,
"version": "0.12.2", "version": "0.12.5",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@@ -13,25 +13,25 @@
"dependencies": { "dependencies": {
"@henrygd/queue": "^1.0.7", "@henrygd/queue": "^1.0.7",
"@henrygd/semaphore": "^0.0.2", "@henrygd/semaphore": "^0.0.2",
"@lingui/detect-locale": "^5.3.3", "@lingui/detect-locale": "^5.4.1",
"@lingui/macro": "^5.3.3", "@lingui/macro": "^5.4.1",
"@lingui/react": "^5.3.3", "@lingui/react": "^5.4.1",
"@nanostores/react": "^0.7.3", "@nanostores/react": "^0.7.3",
"@nanostores/router": "^0.11.0", "@nanostores/router": "^0.11.0",
"@radix-ui/react-alert-dialog": "^1.1.14", "@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-direction": "^1.1.1", "@radix-ui/react-direction": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-label": "^2.1.7", "@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-select": "^2.2.5", "@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slider": "^1.3.5", "@radix-ui/react-slider": "^1.3.6",
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.5", "@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.12", "@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-toast": "^1.2.14", "@radix-ui/react-toast": "^1.2.15",
"@radix-ui/react-tooltip": "^1.2.7", "@radix-ui/react-tooltip": "^1.2.8",
"@tanstack/react-table": "^8.21.3", "@tanstack/react-table": "^8.21.3",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
@@ -40,28 +40,26 @@
"lucide-react": "^0.452.0", "lucide-react": "^0.452.0",
"nanostores": "^0.11.4", "nanostores": "^0.11.4",
"pocketbase": "^0.26.2", "pocketbase": "^0.26.2",
"react": "^18.3.1", "react": "^19.1.1",
"react-dom": "^18.3.1", "react-dom": "^19.1.1",
"recharts": "^2.15.4", "recharts": "^2.15.4",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^3.3.1",
"tailwindcss-animate": "^1.0.7",
"valibot": "^0.42.1" "valibot": "^0.42.1"
}, },
"devDependencies": { "devDependencies": {
"@lingui/cli": "^5.3.3", "@lingui/cli": "^5.4.1",
"@lingui/swc-plugin": "^5.5.2", "@lingui/swc-plugin": "^5.6.1",
"@lingui/vite-plugin": "^5.3.3", "@lingui/vite-plugin": "^5.4.1",
"@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/container-queries": "^0.1.1",
"@types/bun": "^1.2.19", "@tailwindcss/vite": "^4.1.12",
"@types/react": "^18.3.23", "@types/bun": "^1.2.20",
"@types/react-dom": "^18.3.7", "@types/react": "^19.1.11",
"@vitejs/plugin-react-swc": "^3.11.0", "@types/react-dom": "^19.1.7",
"autoprefixer": "^10.4.21", "@vitejs/plugin-react-swc": "^4.0.1",
"postcss": "^8.5.6", "tailwindcss": "^4.1.12",
"tailwindcss": "^3.4.17", "tw-animate-css": "^1.3.7",
"tailwindcss-rtl": "^0.9.0", "typescript": "^5.9.2",
"typescript": "^5.8.3", "vite": "^7.1.3"
"vite": "^6.3.5"
}, },
"overrides": { "overrides": {
"@nanostores/router": { "@nanostores/router": {

View File

@@ -1,6 +0,0 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@@ -20,6 +20,7 @@ import { ChevronDownIcon, ExternalLinkIcon, PlusIcon } from "lucide-react"
import { memo, useEffect, useRef, useState } from "react" import { memo, useEffect, useRef, useState } from "react"
import { $router, basePath, Link, navigate } from "./router" import { $router, basePath, Link, navigate } from "./router"
import { SystemRecord } from "@/types" import { SystemRecord } from "@/types"
import { SystemStatus } from "@/lib/enums"
import { AppleIcon, DockerIcon, TuxIcon, WindowsIcon } from "./ui/icons" import { AppleIcon, DockerIcon, TuxIcon, WindowsIcon } from "./ui/icons"
import { InputCopy } from "./ui/input-copy" import { InputCopy } from "./ui/input-copy"
import { getPagePath } from "@nanostores/router" import { getPagePath } from "@nanostores/router"
@@ -105,7 +106,7 @@ export const SystemDialog = ({ setOpen, system }: { setOpen: (open: boolean) =>
try { try {
setOpen(false) setOpen(false)
if (system) { if (system) {
await pb.collection("systems").update(system.id, { ...data, status: "pending" }) await pb.collection("systems").update(system.id, { ...data, status: SystemStatus.Pending })
} else { } else {
const createdSystem = await pb.collection("systems").create(data) const createdSystem = await pb.collection("systems").create(data)
await pb.collection("fingerprints").create({ await pb.collection("fingerprints").create({
@@ -165,9 +166,7 @@ export const SystemDialog = ({ setOpen, system }: { setOpen: (open: boolean) =>
<Trans> <Trans>
Copy the installation command for the agent below, or register agents automatically with a{" "} Copy the installation command for the agent below, or register agents automatically with a{" "}
<Link <Link
onClick={() => { onClick={() => setOpen(false)}
setOpen(false)
}}
href={getPagePath($router, "settings", { name: "tokens" })} href={getPagePath($router, "settings", { name: "tokens" })}
className="link" className="link"
> >
@@ -274,7 +273,7 @@ interface CopyButtonProps {
text: string text: string
onClick: () => void onClick: () => void
dropdownItems: DropdownItem[] dropdownItems: DropdownItem[]
icon?: React.ReactElement icon?: React.ReactElement<any>
} }
const CopyButton = memo((props: CopyButtonProps) => { const CopyButton = memo((props: CopyButtonProps) => {

View File

@@ -2,7 +2,8 @@ import { ColumnDef } from "@tanstack/react-table"
import { AlertsHistoryRecord } from "@/types" import { AlertsHistoryRecord } from "@/types"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge"
import { alertInfo, formatShortDate, toFixedFloat, formatDuration, cn } from "@/lib/utils" import { formatShortDate, toFixedFloat, formatDuration, cn } from "@/lib/utils"
import { alertInfo } from "@/lib/alerts"
import { Trans } from "@lingui/react/macro" import { Trans } from "@lingui/react/macro"
import { t } from "@lingui/core/macro" import { t } from "@lingui/core/macro"

View File

@@ -1,153 +1,36 @@
import { t } from "@lingui/core/macro" import { t } from "@lingui/core/macro"
import { Trans } from "@lingui/react/macro"
import { memo, useMemo, useState } from "react" import { memo, useMemo, useState } from "react"
import { useStore } from "@nanostores/react" import { useStore } from "@nanostores/react"
import { $alerts } from "@/lib/stores" import { $alerts } from "@/lib/stores"
import { import { BellIcon } from "lucide-react"
Dialog, import { cn } from "@/lib/utils"
DialogTrigger,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { BellIcon, GlobeIcon, ServerIcon } from "lucide-react"
import { alertInfo, cn } from "@/lib/utils"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { AlertRecord, SystemRecord } from "@/types" import { SystemRecord } from "@/types"
import { $router, Link } from "../router" import { AlertDialogContent } from "./alerts-sheet"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"
import { Checkbox } from "../ui/checkbox"
import { SystemAlert, SystemAlertGlobal } from "./alerts-system"
import { getPagePath } from "@nanostores/router"
export default memo(function AlertsButton({ system }: { system: SystemRecord }) { export default memo(function AlertsButton({ system }: { system: SystemRecord }) {
const alerts = useStore($alerts)
const [opened, setOpened] = useState(false) const [opened, setOpened] = useState(false)
const alerts = useStore($alerts)
const hasAlert = alerts.some((alert) => alert.system === system.id) const hasSystemAlert = alerts[system.id]?.size > 0
return useMemo( return useMemo(
() => ( () => (
<Dialog> <Sheet>
<DialogTrigger asChild> <SheetTrigger asChild>
<Button variant="ghost" size="icon" aria-label={t`Alerts`} data-nolink onClick={() => setOpened(true)}> <Button variant="ghost" size="icon" aria-label={t`Alerts`} data-nolink onClick={() => setOpened(true)}>
<BellIcon <BellIcon
className={cn("h-[1.2em] w-[1.2em] pointer-events-none", { className={cn("h-[1.2em] w-[1.2em] pointer-events-none", {
"fill-primary": hasAlert, "fill-primary": hasSystemAlert,
})} })}
/> />
</Button> </Button>
</DialogTrigger> </SheetTrigger>
<DialogContent className="max-h-full sm:max-h-[95svh] overflow-auto max-w-[37rem]"> <SheetContent className="max-h-full overflow-auto w-145 !max-w-full p-4 sm:p-6">
{opened && <AlertDialogContent system={system} />} {opened && <AlertDialogContent system={system} />}
</DialogContent> </SheetContent>
</Dialog> </Sheet>
), ),
[opened, hasAlert] [opened, hasSystemAlert]
) )
// return useMemo(
// () => (
// <Sheet>
// <SheetTrigger asChild>
// <Button variant="ghost" size="icon" aria-label={t`Alerts`} data-nolink onClick={() => setOpened(true)}>
// <BellIcon
// className={cn("h-[1.2em] w-[1.2em] pointer-events-none", {
// "fill-primary": hasAlert,
// })}
// />
// </Button>
// </SheetTrigger>
// <SheetContent className="max-h-full overflow-auto w-[35em] p-4 sm:p-5">
// {opened && <AlertDialogContent system={system} />}
// </SheetContent>
// </Sheet>
// ),
// [opened, hasAlert]
// )
}) })
function AlertDialogContent({ system }: { system: SystemRecord }) {
const alerts = useStore($alerts)
const [overwriteExisting, setOverwriteExisting] = useState<boolean | "indeterminate">(false)
/* key to prevent re-rendering */
const alertsSignature: string[] = []
const systemAlerts = alerts.filter((alert) => {
if (alert.system === system.id) {
alertsSignature.push(alert.name, alert.min, alert.value)
return true
}
return false
}) as AlertRecord[]
return useMemo(() => {
const data = Object.keys(alertInfo).map((name) => {
const alert = alertInfo[name as keyof typeof alertInfo]
return {
name: name as keyof typeof alertInfo,
alert,
system,
}
})
return (
<>
<DialogHeader>
<DialogTitle className="text-xl">
<Trans>Alerts</Trans>
</DialogTitle>
<DialogDescription>
<Trans>
See{" "}
<Link href={getPagePath($router, "settings", { name: "notifications" })} className="link">
notification settings
</Link>{" "}
to configure how you receive alerts.
</Trans>
</DialogDescription>
</DialogHeader>
<Tabs defaultValue="system">
<TabsList className="mb-1 -mt-0.5">
<TabsTrigger value="system">
<ServerIcon className="me-2 h-3.5 w-3.5" />
{system.name}
</TabsTrigger>
<TabsTrigger value="global">
<GlobeIcon className="me-1.5 h-3.5 w-3.5" />
<Trans>All Systems</Trans>
</TabsTrigger>
</TabsList>
<TabsContent value="system">
<div className="grid gap-3">
{data.map((d) => (
<SystemAlert key={d.name} system={system} data={d} systemAlerts={systemAlerts} />
))}
</div>
</TabsContent>
<TabsContent value="global">
<label
htmlFor="ovw"
className="mb-3 flex gap-2 items-center justify-center cursor-pointer border rounded-sm py-3 px-4 border-destructive text-destructive font-semibold text-sm"
>
<Checkbox
id="ovw"
className="text-destructive border-destructive data-[state=checked]:bg-destructive"
checked={overwriteExisting}
onCheckedChange={setOverwriteExisting}
/>
<Trans>Overwrite existing alerts</Trans>
</label>
<div className="grid gap-3">
{data.map((d) => (
<SystemAlertGlobal key={d.name} data={d} overwrite={overwriteExisting} />
))}
</div>
</TabsContent>
</Tabs>
</>
)
}, [alertsSignature.join(""), overwriteExisting])
}

View File

@@ -0,0 +1,298 @@
import { t } from "@lingui/core/macro"
import { Trans, Plural } from "@lingui/react/macro"
import { $alerts, $systems, pb } from "@/lib/stores"
import { cn, debounce } from "@/lib/utils"
import { alertInfo } from "@/lib/alerts"
import { Switch } from "@/components/ui/switch"
import { AlertInfo, AlertRecord, SystemRecord } from "@/types"
import { lazy, memo, Suspense, useMemo, useState } from "react"
import { toast } from "@/components/ui/use-toast"
import { useStore } from "@nanostores/react"
import { getPagePath } from "@nanostores/router"
import { Checkbox } from "@/components/ui/checkbox"
import { DialogTitle, DialogDescription } from "@/components/ui/dialog"
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
import { ServerIcon, GlobeIcon } from "lucide-react"
import { $router, Link } from "@/components/router"
import { DialogHeader } from "@/components/ui/dialog"
const Slider = lazy(() => import("@/components/ui/slider"))
const endpoint = "/api/beszel/user-alerts"
const alertDebounce = 100
const alertKeys = Object.keys(alertInfo) as (keyof typeof alertInfo)[]
const failedUpdateToast = (error: unknown) => {
console.error(error)
toast({
title: t`Failed to update alert`,
description: t`Please check logs for more details.`,
variant: "destructive",
})
}
/** Create or update alerts for a given name and systems */
const upsertAlerts = debounce(
async ({ name, value, min, systems }: { name: string; value: number; min: number; systems: string[] }) => {
try {
await pb.send<{ success: boolean }>(endpoint, {
method: "POST",
// overwrite is always true because we've done filtering client side
body: { name, value, min, systems, overwrite: true },
})
} catch (error) {
failedUpdateToast(error)
}
},
alertDebounce
)
/** Delete alerts for a given name and systems */
const deleteAlerts = debounce(async ({ name, systems }: { name: string; systems: string[] }) => {
try {
await pb.send<{ success: boolean }>(endpoint, {
method: "DELETE",
body: { name, systems },
})
} catch (error) {
failedUpdateToast(error)
}
}, alertDebounce)
export const AlertDialogContent = memo(function AlertDialogContent({ system }: { system: SystemRecord }) {
const alerts = useStore($alerts)
const [overwriteExisting, setOverwriteExisting] = useState<boolean | "indeterminate">(false)
const [currentTab, setCurrentTab] = useState("system")
const systemAlerts = alerts[system.id] ?? new Map()
// We need to keep a copy of alerts when we switch to global tab. If we always compare to
// current alerts, it will only be updated when first checked, then won't be updated because
// after that it exists.
const alertsWhenGlobalSelected = useMemo(() => {
return currentTab === "global" ? structuredClone(alerts) : alerts
}, [currentTab])
return (
<>
<DialogHeader>
<DialogTitle className="text-xl">
<Trans>Alerts</Trans>
</DialogTitle>
<DialogDescription>
<Trans>
See{" "}
<Link href={getPagePath($router, "settings", { name: "notifications" })} className="link">
notification settings
</Link>{" "}
to configure how you receive alerts.
</Trans>
</DialogDescription>
</DialogHeader>
<Tabs defaultValue="system" onValueChange={setCurrentTab}>
<TabsList className="mb-1 -mt-0.5">
<TabsTrigger value="system">
<ServerIcon className="me-2 h-3.5 w-3.5" />
{system.name}
</TabsTrigger>
<TabsTrigger value="global">
<GlobeIcon className="me-1.5 h-3.5 w-3.5" />
<Trans>All Systems</Trans>
</TabsTrigger>
</TabsList>
<TabsContent value="system">
<div className="grid gap-3">
{alertKeys.map((name) => (
<AlertContent
key={name}
alertKey={name}
data={alertInfo[name as keyof typeof alertInfo]}
alert={systemAlerts.get(name)}
system={system}
/>
))}
</div>
</TabsContent>
<TabsContent value="global">
<label
htmlFor="ovw"
className="mb-3 flex gap-2 items-center justify-center cursor-pointer border rounded-sm py-3 px-4 border-destructive text-destructive font-semibold text-sm"
>
<Checkbox
id="ovw"
className="text-destructive border-destructive data-[state=checked]:bg-destructive"
checked={overwriteExisting}
onCheckedChange={setOverwriteExisting}
/>
<Trans>Overwrite existing alerts</Trans>
</label>
<div className="grid gap-3">
{alertKeys.map((name) => (
<AlertContent
key={name}
alertKey={name}
system={system}
alert={systemAlerts.get(name)}
data={alertInfo[name as keyof typeof alertInfo]}
global={true}
overwriteExisting={!!overwriteExisting}
initialAlertsState={alertsWhenGlobalSelected}
/>
))}
</div>
</TabsContent>
</Tabs>
</>
)
})
export function AlertContent({
alertKey,
data: alertData,
system,
alert,
global = false,
overwriteExisting = false,
initialAlertsState = {},
}: {
alertKey: string
data: AlertInfo
system: SystemRecord
alert?: AlertRecord
global?: boolean
overwriteExisting?: boolean
initialAlertsState?: Record<string, Map<string, AlertRecord>>
}) {
const { name } = alertData
const singleDescription = alertData.singleDesc?.()
const [checked, setChecked] = useState(global ? false : !!alert)
const [min, setMin] = useState(alert?.min || 10)
const [value, setValue] = useState(alert?.value || (singleDescription ? 0 : alertData.start ?? 80))
const Icon = alertData.icon
/** Get system ids to update */
function getSystemIds(): string[] {
// if not global, update only the current system
if (!global) {
return [system.id]
}
// if global, update all systems when overwriteExisting is true
// update only systems without an existing alert when overwriteExisting is false
const allSystems = $systems.get()
const systemIds: string[] = []
for (const system of allSystems) {
if (overwriteExisting || !initialAlertsState[system.id]?.has(alertKey)) {
systemIds.push(system.id)
}
}
return systemIds
}
function sendUpsert(min: number, value: number) {
const systems = getSystemIds()
systems.length &&
upsertAlerts({
name: alertKey,
value,
min,
systems,
})
}
return (
<div className="rounded-lg border border-muted-foreground/15 hover:border-muted-foreground/20 transition-colors duration-100 group">
<label
htmlFor={`s${name}`}
className={cn("flex flex-row items-center justify-between gap-4 cursor-pointer p-4", {
"pb-0": checked,
})}
>
<div className="grid gap-1 select-none">
<p className="font-semibold flex gap-3 items-center">
<Icon className="h-4 w-4 opacity-85" /> {alertData.name()}
</p>
{!checked && <span className="block text-sm text-muted-foreground">{alertData.desc()}</span>}
</div>
<Switch
id={`s${name}`}
checked={checked}
onCheckedChange={(newChecked) => {
setChecked(newChecked)
if (newChecked) {
// if alert checked, create or update alert
sendUpsert(min, value)
} else {
// if unchecked, delete alert (unless global and overwriteExisting is false)
deleteAlerts({ name: alertKey, systems: getSystemIds() })
// when force deleting all alerts of a type, also remove them from initialAlertsState
if (overwriteExisting) {
for (const curAlerts of Object.values(initialAlertsState)) {
curAlerts.delete(alertKey)
}
}
}
}}
/>
</label>
{checked && (
<div className="grid sm:grid-cols-2 mt-1.5 gap-5 px-4 pb-5 tabular-nums text-muted-foreground">
<Suspense fallback={<div className="h-10" />}>
{!singleDescription && (
<div>
<p id={`v${name}`} className="text-sm block h-8">
<Trans>
Average exceeds{" "}
<strong className="text-foreground">
{value}
{alertData.unit}
</strong>
</Trans>
</p>
<div className="flex gap-3">
<Slider
aria-labelledby={`v${name}`}
defaultValue={[value]}
onValueCommit={(val) => sendUpsert(min, val[0])}
onValueChange={(val) => setValue(val[0])}
step={alertData.step ?? 1}
min={alertData.min ?? 1}
max={alertData.max ?? 99}
/>
</div>
</div>
)}
<div className={cn(singleDescription && "col-span-full lowercase")}>
<p id={`t${name}`} className="text-sm block h-8 first-letter:uppercase">
{singleDescription && (
<>
{singleDescription}
{` `}
</>
)}
<Trans>
For <strong className="text-foreground">{min}</strong>{" "}
<Plural value={min} one="minute" other="minutes" />
</Trans>
</p>
<div className="flex gap-3">
<Slider
aria-labelledby={`v${name}`}
defaultValue={[min]}
onValueCommit={(minVal) => sendUpsert(minVal[0], value)}
onValueChange={(val) => setMin(val[0])}
min={1}
max={60}
/>
</div>
</div>
</Suspense>
</div>
)}
</div>
)
}

View File

@@ -1,311 +0,0 @@
import { t } from "@lingui/core/macro"
import { Trans, Plural } from "@lingui/react/macro"
import { $alerts, $systems, pb } from "@/lib/stores"
import { alertInfo, cn } from "@/lib/utils"
import { Switch } from "@/components/ui/switch"
import { AlertInfo, AlertRecord, SystemRecord } from "@/types"
import { lazy, Suspense, useMemo, useState } from "react"
import { toast } from "../ui/use-toast"
import { BatchService } from "pocketbase"
import { getSemaphore } from "@henrygd/semaphore"
interface AlertData {
checked?: boolean
val?: number
min?: number
updateAlert?: (checked: boolean, value: number, min: number) => void
name: keyof typeof alertInfo
alert: AlertInfo
system: SystemRecord
}
const Slider = lazy(() => import("@/components/ui/slider"))
const failedUpdateToast = () =>
toast({
title: t`Failed to update alert`,
description: t`Please check logs for more details.`,
variant: "destructive",
})
export function SystemAlert({
system,
systemAlerts,
data,
}: {
system: SystemRecord
systemAlerts: AlertRecord[]
data: AlertData
}) {
const alert = systemAlerts.find((alert) => alert.name === data.name)
data.updateAlert = async (checked: boolean, value: number, min: number) => {
try {
if (alert && !checked) {
await pb.collection("alerts").delete(alert.id)
} else if (alert && checked) {
await pb.collection("alerts").update(alert.id, { value, min, triggered: false })
} else if (checked) {
pb.collection("alerts").create({
system: system.id,
user: pb.authStore.record!.id,
name: data.name,
value: value,
min: min,
})
}
} catch (e) {
failedUpdateToast()
}
}
if (alert) {
data.checked = true
data.val = alert.value
data.min = alert.min || 1
}
return <AlertContent data={data} />
}
export const SystemAlertGlobal = ({ data, overwrite }: { data: AlertData; overwrite: boolean | "indeterminate" }) => {
data.checked = false
data.val = data.min = 0
// set of system ids that have an alert for this name when the component is mounted
const existingAlertsSystems = useMemo(() => {
const map = new Set<string>()
const alerts = $alerts.get()
for (const alert of alerts) {
if (alert.name === data.name) {
map.add(alert.system)
}
}
return map
}, [])
data.updateAlert = async (checked: boolean, value: number, min: number) => {
const sem = getSemaphore("alerts")
await sem.acquire()
try {
// if another update is waiting behind, don't start this one
if (sem.size() > 1) {
return
}
const recordData: Partial<AlertRecord> = {
value,
min,
triggered: false,
}
const batch = batchWrapper("alerts", 25)
const systems = $systems.get()
const currentAlerts = $alerts.get()
// map of current alerts with this name right now by system id
const currentAlertsSystems = new Map<string, AlertRecord>()
for (const alert of currentAlerts) {
if (alert.name === data.name) {
currentAlertsSystems.set(alert.system, alert)
}
}
if (overwrite) {
existingAlertsSystems.clear()
}
const processSystem = async (system: SystemRecord): Promise<void> => {
const existingAlert = existingAlertsSystems.has(system.id)
if (!overwrite && existingAlert) {
return
}
const currentAlert = currentAlertsSystems.get(system.id)
// delete existing alert if unchecked
if (!checked && currentAlert) {
return batch.remove(currentAlert.id)
}
if (checked && currentAlert) {
// update existing alert if checked
return batch.update(currentAlert.id, recordData)
}
if (checked) {
// create new alert if checked and not existing
return batch.create({
system: system.id,
user: pb.authStore.record!.id,
name: data.name,
...recordData,
})
}
}
// make sure current system is updated in the first batch
await processSystem(data.system)
for (const system of systems) {
if (system.id === data.system.id) {
continue
}
if (sem.size() > 1) {
return
}
await processSystem(system)
}
await batch.send()
} finally {
sem.release()
}
}
return <AlertContent data={data} />
}
/**
* Creates a wrapper for performing batch operations on a specified collection.
*/
function batchWrapper(collection: string, batchSize: number) {
let batch: BatchService | undefined
let count = 0
const create = async <T extends Record<string, any>>(options: T) => {
batch ||= pb.createBatch()
batch.collection(collection).create(options)
if (++count >= batchSize) {
await send()
}
}
const update = async <T extends Record<string, any>>(id: string, data: T) => {
batch ||= pb.createBatch()
batch.collection(collection).update(id, data)
if (++count >= batchSize) {
await send()
}
}
const remove = async (id: string) => {
batch ||= pb.createBatch()
batch.collection(collection).delete(id)
if (++count >= batchSize) {
await send()
}
}
const send = async () => {
if (count) {
await batch?.send({ requestKey: null })
batch = undefined
count = 0
}
}
return {
update,
remove,
send,
create,
}
}
function AlertContent({ data }: { data: AlertData }) {
const { name } = data
const singleDescription = data.alert.singleDesc?.()
const [checked, setChecked] = useState(data.checked || false)
const [min, setMin] = useState(data.min || 10)
const [value, setValue] = useState(data.val || (singleDescription ? 0 : data.alert.start ?? 80))
const Icon = alertInfo[name].icon
return (
<div className="rounded-lg border border-muted-foreground/15 hover:border-muted-foreground/20 transition-colors duration-100 group">
<label
htmlFor={`s${name}`}
className={cn("flex flex-row items-center justify-between gap-4 cursor-pointer p-4", {
"pb-0": checked,
})}
>
<div className="grid gap-1 select-none">
<p className="font-semibold flex gap-3 items-center">
<Icon className="h-4 w-4 opacity-85" /> {data.alert.name()}
</p>
{!checked && <span className="block text-sm text-muted-foreground">{data.alert.desc()}</span>}
</div>
<Switch
id={`s${name}`}
checked={checked}
onCheckedChange={(newChecked) => {
setChecked(newChecked)
data.updateAlert?.(newChecked, value, min)
}}
/>
</label>
{checked && (
<div className="grid sm:grid-cols-2 mt-1.5 gap-5 px-4 pb-5 tabular-nums text-muted-foreground">
<Suspense fallback={<div className="h-10" />}>
{!singleDescription && (
<div>
<p id={`v${name}`} className="text-sm block h-8">
<Trans>
Average exceeds{" "}
<strong className="text-foreground">
{value}
{data.alert.unit}
</strong>
</Trans>
</p>
<div className="flex gap-3">
<Slider
aria-labelledby={`v${name}`}
defaultValue={[value]}
onValueCommit={(val) => {
data.updateAlert?.(true, val[0], min)
}}
onValueChange={(val) => {
setValue(val[0])
}}
step={data.alert.step ?? 1}
min={data.alert.min ?? 1}
max={alertInfo[name].max ?? 99}
/>
</div>
</div>
)}
<div className={cn(singleDescription && "col-span-full lowercase")}>
<p id={`t${name}`} className="text-sm block h-8 first-letter:uppercase">
{singleDescription && (
<>
{singleDescription}
{` `}
</>
)}
<Trans>
For <strong className="text-foreground">{min}</strong>{" "}
<Plural value={min} one="minute" other="minutes" />
</Trans>
</p>
<div className="flex gap-3">
<Slider
aria-labelledby={`v${name}`}
defaultValue={[min]}
onValueCommit={(min) => {
data.updateAlert?.(true, value, min[0])
}}
onValueChange={(val) => {
setMin(val[0])
}}
min={1}
max={60}
/>
</div>
</div>
</Suspense>
</div>
)}
</div>
)
}

View File

@@ -18,6 +18,7 @@ export default function AreaChartDefault({
tickFormatter, tickFormatter,
contentFormatter, contentFormatter,
dataPoints, dataPoints,
domain,
}: // logRender = false, }: // logRender = false,
{ {
chartData: ChartData chartData: ChartData
@@ -26,6 +27,7 @@ export default function AreaChartDefault({
tickFormatter: (value: number, index: number) => string tickFormatter: (value: number, index: number) => string
contentFormatter: ({ value, payload }: { value: number; payload: SystemStatsRecord }) => string contentFormatter: ({ value, payload }: { value: number; payload: SystemStatsRecord }) => string
dataPoints?: DataPoint[] dataPoints?: DataPoint[]
domain?: [number, number]
// logRender?: boolean // logRender?: boolean
}) { }) {
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth() const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
@@ -51,7 +53,7 @@ export default function AreaChartDefault({
orientation={chartData.orientation} orientation={chartData.orientation}
className="tracking-tighter" className="tracking-tighter"
width={yAxisWidth} width={yAxisWidth}
domain={[0, max ?? "auto"]} domain={domain ?? [0, max ?? "auto"]}
tickFormatter={(value, index) => updateYAxisWidth(tickFormatter(value, index))} tickFormatter={(value, index) => updateYAxisWidth(tickFormatter(value, index))}
tickLine={false} tickLine={false}
axisLine={false} axisLine={false}
@@ -68,7 +70,7 @@ export default function AreaChartDefault({
} }
/> />
{dataPoints?.map((dataPoint, i) => { {dataPoints?.map((dataPoint, i) => {
const color = `hsl(var(--chart-${dataPoint.color}))` const color = `var(--chart-${dataPoint.color})`
return ( return (
<Area <Area
key={i} key={i}

View File

@@ -29,41 +29,36 @@ export default memo(function ContainerChart({
const isNetChart = chartType === ChartType.Network const isNetChart = chartType === ChartType.Network
const chartConfig = useMemo(() => { const chartConfig = useMemo(() => {
let config = {} as Record< const config = {} as Record<string, { label: string; color: string }>
string, const totalUsage = new Map<string, number>()
{
label: string // calculate total usage of each container
color: string for (const stats of containerData) {
} for (const key in stats) {
> if (!key || key === "created") continue
const totalUsage = {} as Record<string, number>
for (let stats of containerData) { const currentTotal = totalUsage.get(key) ?? 0
for (let key in stats) { const increment = isNetChart
if (!key || key === "created") { ? (stats[key]?.nr ?? 0) + (stats[key]?.ns ?? 0)
continue : // @ts-ignore
} stats[key]?.[dataKey] ?? 0
if (!(key in totalUsage)) {
totalUsage[key] = 0 totalUsage.set(key, currentTotal + increment)
}
if (isNetChart) {
totalUsage[key] += (stats[key]?.nr ?? 0) + (stats[key]?.ns ?? 0)
} else {
// @ts-ignore
totalUsage[key] += stats[key]?.[dataKey] ?? 0
}
} }
} }
let keys = Object.keys(totalUsage)
keys.sort((a, b) => (totalUsage[a] > totalUsage[b] ? -1 : 1)) // Sort keys and generate colors based on usage
const length = keys.length const sortedEntries = Array.from(totalUsage.entries()).sort(([, a], [, b]) => b - a)
for (let i = 0; i < length; i++) {
const key = keys[i] const length = sortedEntries.length
sortedEntries.forEach(([key], i) => {
const hue = ((i * 360) / length) % 360 const hue = ((i * 360) / length) % 360
config[key] = { config[key] = {
label: key, label: key,
color: `hsl(${hue}, 60%, 55%)`, color: `hsl(${hue}, 60%, 55%)`,
} }
} })
return config satisfies ChartConfig return config satisfies ChartConfig
}, [chartData]) }, [chartData])
@@ -124,6 +119,8 @@ export default memo(function ContainerChart({
return obj return obj
}, []) }, [])
const filterLower = filter?.toLowerCase()
// console.log('rendered at', new Date()) // console.log('rendered at', new Date())
if (containerData.length === 0) { if (containerData.length === 0) {
@@ -165,7 +162,7 @@ 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 = filter && !key.toLowerCase().includes(filter.toLowerCase()) const filtered = filterLower && !key.toLowerCase().includes(filterLower)
let fillOpacity = filtered ? 0.05 : 0.4 let fillOpacity = filtered ? 0.05 : 0.4
let strokeOpacity = filtered ? 0.1 : 1 let strokeOpacity = filtered ? 0.1 : 1
return ( return (

View File

@@ -69,9 +69,9 @@ export default memo(function DiskChart({
dataKey={dataKey} dataKey={dataKey}
name={t`Disk Usage`} name={t`Disk Usage`}
type="monotoneX" type="monotoneX"
fill="hsl(var(--chart-4))" fill="var(--chart-4)"
fillOpacity={0.4} fillOpacity={0.4}
stroke="hsl(var(--chart-4))" stroke="var(--chart-4)"
// animationDuration={1200} // animationDuration={1200}
isAnimationActive={false} isAnimationActive={false}
/> />

View File

@@ -68,9 +68,9 @@ export default memo(function MemChart({ chartData }: { chartData: ChartData }) {
order={3} order={3}
dataKey="stats.mu" dataKey="stats.mu"
type="monotoneX" type="monotoneX"
fill="hsl(var(--chart-2))" fill="var(--chart-2)"
fillOpacity={0.4} fillOpacity={0.4}
stroke="hsl(var(--chart-2))" stroke="var(--chart-2)"
stackId="1" stackId="1"
isAnimationActive={false} isAnimationActive={false}
/> />

View File

@@ -58,9 +58,9 @@ export default memo(function SwapChart({ chartData }: { chartData: ChartData })
dataKey="stats.su" dataKey="stats.su"
name={t`Used`} name={t`Used`}
type="monotoneX" type="monotoneX"
fill="hsl(var(--chart-2))" fill="var(--chart-2)"
fillOpacity={0.4} fillOpacity={0.4}
stroke="hsl(var(--chart-2))" stroke="var(--chart-2)"
isAnimationActive={false} isAnimationActive={false}
/> />
</AreaChart> </AreaChart>

View File

@@ -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}

View File

@@ -288,7 +288,7 @@ export function UserAuthForm({
// }} // }}
/> />
)} )}
<span className="translate-y-[1px]">{provider.displayName}</span> <span className="translate-y-px">{provider.displayName}</span>
</button> </button>
))} ))}
</div> </div>
@@ -299,7 +299,7 @@ export function UserAuthForm({
<DialogTrigger asChild> <DialogTrigger asChild>
<button type="button" className={cn(buttonVariants({ variant: "outline" }))}> <button type="button" className={cn(buttonVariants({ variant: "outline" }))}>
<img className="me-2 h-4 w-4 dark:invert" src={prependBasePath("/_/images/oauth2/github.svg")} alt="" /> <img className="me-2 h-4 w-4 dark:invert" src={prependBasePath("/_/images/oauth2/github.svg")} alt="" />
<span className="translate-y-[1px]">GitHub</span> <span className="translate-y-px">GitHub</span>
</button> </button>
</DialogTrigger> </DialogTrigger>
<DialogContent style={{ maxWidth: 440, width: "90%" }}> <DialogContent style={{ maxWidth: 440, width: "90%" }}>

View File

@@ -1,56 +1,21 @@
import { Trans } from "@lingui/react/macro"; import { t } from "@lingui/core/macro"
import { t } from "@lingui/core/macro"; import { MoonStarIcon, SunIcon } from "lucide-react"
import { LaptopIcon, MoonStarIcon, SunIcon } from "lucide-react"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { useTheme } from "@/components/theme-provider" import { useTheme } from "@/components/theme-provider"
import { cn } from "@/lib/utils"
export function ModeToggle() { export function ModeToggle() {
const { theme, setTheme } = useTheme() const { theme, setTheme } = useTheme()
const options = [
{
theme: "light",
Icon: SunIcon,
label: <Trans comment="Light theme">Light</Trans>,
},
{
theme: "dark",
Icon: MoonStarIcon,
label: <Trans comment="Dark theme">Dark</Trans>,
},
{
theme: "system",
Icon: LaptopIcon,
label: <Trans comment="System theme">System</Trans>,
},
]
return ( return (
<DropdownMenu> <Button
<DropdownMenuTrigger asChild> variant={"ghost"}
<Button variant={"ghost"} size="icon" aria-label={t`Toggle theme`}> size="icon"
<SunIcon className="h-[1.2rem] w-[1.2rem] dark:opacity-0" /> aria-label={t`Toggle theme`}
<MoonStarIcon className="absolute h-[1.2rem] w-[1.2rem] opacity-0 dark:opacity-100" /> onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
</Button> >
</DropdownMenuTrigger> <SunIcon className="h-[1.2rem] w-[1.2rem] transition-all -rotate-90 dark:opacity-0 dark:rotate-0" />
<DropdownMenuContent> <MoonStarIcon className="absolute h-[1.2rem] w-[1.2rem] transition-all opacity-0 -rotate-90 dark:opacity-100 dark:rotate-0" />
{options.map((opt) => { </Button>
const selected = opt.theme === theme
return (
<DropdownMenuItem
key={opt.theme}
className={cn("px-2.5", selected ? "font-semibold" : "")}
onClick={() => setTheme(opt.theme as "dark" | "light" | "system")}
>
<opt.Icon className={cn("me-2 h-4 w-4 opacity-80", selected && "opacity-100")} />
{opt.label}
</DropdownMenuItem>
)
})}
</DropdownMenuContent>
</DropdownMenu>
) )
} }

View File

@@ -1,4 +1,4 @@
import { Trans } from "@lingui/react/macro"; import { Trans } from "@lingui/react/macro"
import { useState, lazy, Suspense } from "react" import { useState, lazy, Suspense } from "react"
import { Button, buttonVariants } from "@/components/ui/button" import { Button, buttonVariants } from "@/components/ui/button"
import { import {

View File

@@ -35,18 +35,20 @@ export const navigate = (urlString: string) => {
$router.open(urlString) $router.open(urlString)
} }
function onClick(e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) { export function Link(props: React.AnchorHTMLAttributes<HTMLAnchorElement>) {
e.preventDefault() return (
$router.open(new URL((e.currentTarget as HTMLAnchorElement).href).pathname) <a
} {...props}
onClick={(e) => {
export const Link = (props: React.AnchorHTMLAttributes<HTMLAnchorElement>) => { e.preventDefault()
let clickFn = onClick const href = props.href || ""
if (props.onClick) { if (e.ctrlKey || e.metaKey) {
clickFn = (e) => { window.open(href, "_blank")
onClick(e) } else {
props.onClick?.(e) navigate(href)
} props.onClick?.(e)
} }
return <a {...props} onClick={clickFn}></a> }}
></a>
)
} }

View File

@@ -4,36 +4,19 @@ import { $alerts, $systems, pb } 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 { alertInfo, updateRecordList, updateSystemList } from "@/lib/utils" import { getSystemNameFromId, updateRecordList, updateSystemList } from "@/lib/utils"
import { AlertRecord, SystemRecord } 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"
const SystemsTable = lazy(() => import("../systems-table/systems-table")) const SystemsTable = lazy(() => import("../systems-table/systems-table"))
export const Home = memo(() => { export default memo(function () {
const alerts = useStore($alerts)
const systems = useStore($systems)
const { t } = useLingui() const { t } = useLingui()
/* key to prevent re-rendering of active alerts */
const alertsKey: string[] = []
const activeAlerts = useMemo(() => {
const activeAlerts = alerts.filter((alert) => {
const active = alert.triggered && alert.name in alertInfo
if (!active) {
return false
}
alert.sysname = systems.find((system) => system.id === alert.system)?.name
alertsKey.push(alert.id)
return true
})
return activeAlerts
}, [systems, alerts])
useEffect(() => { useEffect(() => {
document.title = t`Dashboard` + " / Beszel" document.title = t`Dashboard` + " / Beszel"
}, [t]) }, [t])
@@ -46,20 +29,15 @@ export const Home = memo(() => {
pb.collection<SystemRecord>("systems").subscribe("*", (e) => { pb.collection<SystemRecord>("systems").subscribe("*", (e) => {
updateRecordList(e, $systems) updateRecordList(e, $systems)
}) })
pb.collection<AlertRecord>("alerts").subscribe("*", (e) => {
updateRecordList(e, $alerts)
})
return () => { return () => {
pb.collection("systems").unsubscribe("*") pb.collection("systems").unsubscribe("*")
// pb.collection('alerts').unsubscribe('*')
} }
}, []) }, [])
return useMemo( return useMemo(
() => ( () => (
<> <>
{/* show active alerts */} <ActiveAlerts />
{activeAlerts.length > 0 && <ActiveAlerts key={activeAlerts.length} activeAlerts={activeAlerts} />}
<Suspense> <Suspense>
<SystemsTable /> <SystemsTable />
</Suspense> </Suspense>
@@ -83,55 +61,79 @@ export const Home = memo(() => {
</div> </div>
</> </>
), ),
[alertsKey.join("")] []
) )
}) })
const ActiveAlerts = memo(({ activeAlerts }: { activeAlerts: AlertRecord[] }) => { const ActiveAlerts = () => {
return ( const alerts = useStore($alerts)
<Card className="mb-4">
<CardHeader className="pb-4 px-2 sm:px-6 max-sm:pt-5 max-sm:pb-1"> const { activeAlerts, alertsKey } = useMemo(() => {
<div className="px-2 sm:px-1"> const activeAlerts: AlertRecord[] = []
<CardTitle> // key to prevent re-rendering if alerts change but active alerts didn't
<Trans>Active Alerts</Trans> const alertsKey: string[] = []
</CardTitle>
</div> for (const systemId of Object.keys(alerts)) {
</CardHeader> for (const alert of alerts[systemId].values()) {
<CardContent className="max-sm:p-2"> if (alert.triggered && alert.name in alertInfo) {
{activeAlerts.length > 0 && ( activeAlerts.push(alert)
<div className="grid sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-3"> alertsKey.push(`${alert.system}${alert.value}${alert.min}`)
{activeAlerts.map((alert) => { }
const info = alertInfo[alert.name as keyof typeof alertInfo] }
return ( }
<Alert
key={alert.id} return { activeAlerts, alertsKey }
className="hover:-translate-y-[1px] duration-200 bg-transparent border-foreground/10 hover:shadow-md shadow-black" }, [alerts])
>
<info.icon className="h-4 w-4" /> return useMemo(() => {
<AlertTitle> if (activeAlerts.length === 0) {
{alert.sysname} {info.name().toLowerCase().replace("cpu", "CPU")} return null
</AlertTitle> }
<AlertDescription> return (
{alert.name === "Status" ? ( <Card className="mb-4">
<Trans>Connection is down</Trans> <CardHeader className="pb-4 px-2 sm:px-6 max-sm:pt-5 max-sm:pb-1">
) : ( <div className="px-2 sm:px-1">
<Trans> <CardTitle>
Exceeds {alert.value} <Trans>Active Alerts</Trans>
{info.unit} in last <Plural value={alert.min} one="# minute" other="# minutes" /> </CardTitle>
</Trans>
)}
</AlertDescription>
<Link
href={getPagePath($router, "system", { name: alert.sysname! })}
className="absolute inset-0 w-full h-full"
aria-label="View system"
></Link>
</Alert>
)
})}
</div> </div>
)} </CardHeader>
</CardContent> <CardContent className="max-sm:p-2">
</Card> {activeAlerts.length > 0 && (
) <div className="grid sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-3">
}) {activeAlerts.map((alert) => {
const info = alertInfo[alert.name as keyof typeof alertInfo]
return (
<Alert
key={alert.id}
className="hover:-translate-y-px duration-200 bg-transparent border-foreground/10 hover:shadow-md shadow-black/5"
>
<info.icon className="h-4 w-4" />
<AlertTitle>
{getSystemNameFromId(alert.system)} {info.name().toLowerCase().replace("cpu", "CPU")}
</AlertTitle>
<AlertDescription>
{alert.name === "Status" ? (
<Trans>Connection is down</Trans>
) : (
<Trans>
Exceeds {alert.value}
{info.unit} in last <Plural value={alert.min} one="# minute" other="# minutes" />
</Trans>
)}
</AlertDescription>
<Link
href={getPagePath($router, "system", { name: getSystemNameFromId(alert.system) })}
className="absolute inset-0 w-full h-full"
aria-label="View system"
></Link>
</Alert>
)
})}
</div>
)}
</CardContent>
</Card>
)
}, [alertsKey.join("")])
}

View File

@@ -1,5 +1,6 @@
import { pb } from "@/lib/stores" import { pb } from "@/lib/stores"
import { alertInfo, cn, formatDuration, formatShortDate } from "@/lib/utils" import { cn, formatDuration, formatShortDate } from "@/lib/utils"
import { alertInfo } from "@/lib/alerts"
import { AlertsHistoryRecord } from "@/types" import { AlertsHistoryRecord } from "@/types"
import { import {
getCoreRowModel, getCoreRowModel,

View File

@@ -1,5 +1,5 @@
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 { 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"

View File

@@ -12,29 +12,17 @@ import languages from "@/lib/languages"
import { dynamicActivate } from "@/lib/i18n" import { dynamicActivate } from "@/lib/i18n"
import { useLingui } from "@lingui/react/macro" import { useLingui } from "@lingui/react/macro"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
// import { setLang } from "@/lib/i18n"
import { Unit } from "@/lib/enums" import { Unit } from "@/lib/enums"
export default function SettingsProfilePage({ userSettings }: { userSettings: UserSettings }) { export default function SettingsProfilePage({ userSettings }: { userSettings: UserSettings }) {
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const { i18n } = useLingui() const { i18n } = useLingui()
// Remove all per-metric threshold state and UI
// Only keep general yellow/red threshold state and UI
const [yellow, setYellow] = useState(userSettings.meterThresholds?.yellow ?? 65)
const [red, setRed] = useState(userSettings.meterThresholds?.red ?? 90)
function handleResetThresholds() {
setYellow(65)
setRed(90)
}
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) { async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault() e.preventDefault()
setIsLoading(true) setIsLoading(true)
const formData = new FormData(e.target as HTMLFormElement) const formData = new FormData(e.target as HTMLFormElement)
const data = Object.fromEntries(formData) as Partial<UserSettings> const data = Object.fromEntries(formData) as Partial<UserSettings>
data.meterThresholds = { yellow, red }
await saveSettings(data) await saveSettings(data)
setIsLoading(false) setIsLoading(false)
} }
@@ -51,8 +39,8 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
</div> </div>
<Separator className="my-4" /> <Separator className="my-4" />
<form onSubmit={handleSubmit} className="space-y-5"> <form onSubmit={handleSubmit} className="space-y-5">
<div className="space-y-2"> <div className="grid gap-2">
<div className="mb-4"> <div className="mb-2">
<h3 className="mb-1 text-lg font-medium flex items-center gap-2"> <h3 className="mb-1 text-lg font-medium flex items-center gap-2">
<LanguagesIcon className="h-4 w-4" /> <LanguagesIcon className="h-4 w-4" />
<Trans>Language</Trans> <Trans>Language</Trans>
@@ -85,8 +73,8 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
</Select> </Select>
</div> </div>
<Separator /> <Separator />
<div className="space-y-2"> <div className="grid gap-2">
<div className="mb-4"> <div className="mb-2">
<h3 className="mb-1 text-lg font-medium"> <h3 className="mb-1 text-lg font-medium">
<Trans>Chart options</Trans> <Trans>Chart options</Trans>
</h3> </h3>
@@ -114,47 +102,8 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
</p> </p>
</div> </div>
<Separator /> <Separator />
<div className="space-y-2"> <div className="grid gap-2">
<div className="mb-4"> <div className="mb-2">
<h3 className="mb-1 text-lg font-medium">
<Trans>Dashboard meter thresholds</Trans>
</h3>
<p className="text-sm text-muted-foreground leading-relaxed">
<Trans>Choose when the dashboard meters changes colors, based on percentage values.</Trans>
</p>
</div>
<div className="flex gap-4 items-end">
<div>
<Label htmlFor="yellow-threshold"><Trans>Warning threshold (%)</Trans></Label>
<Input
type="number"
id="yellow-threshold"
min={1}
max={100}
value={yellow}
onChange={e => setYellow(Number(e.target.value))}
/>
</div>
<div>
<Label htmlFor="red-threshold"><Trans>Danger threshold (%)</Trans></Label>
<Input
type="number"
id="red-threshold"
min={1}
max={100}
value={red}
onChange={e => setRed(Number(e.target.value))}
/>
</div>
<Button type="button" variant="outline" onClick={handleResetThresholds} disabled={isLoading} className="mt-4">
<Trans>Reset to default</Trans>
</Button>
</div>
</div>
<Separator />
{/* Unit preferences section fixed and wrapped in a div */}
<div className="space-y-2">
<div className="mb-4">
<h3 className="mb-1 text-lg font-medium"> <h3 className="mb-1 text-lg font-medium">
<Trans comment="Temperature / network units">Unit preferences</Trans> <Trans comment="Temperature / network units">Unit preferences</Trans>
</h3> </h3>
@@ -163,7 +112,7 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
</p> </p>
</div> </div>
<div className="grid sm:grid-cols-3 gap-4"> <div className="grid sm:grid-cols-3 gap-4">
<div className="space-y-2"> <div className="grid gap-2">
<Label className="block" htmlFor="unitTemp"> <Label className="block" htmlFor="unitTemp">
<Trans>Temperature unit</Trans> <Trans>Temperature unit</Trans>
</Label> </Label>
@@ -185,7 +134,7 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<div className="space-y-2"> <div className="grid gap-2">
<Label className="block" htmlFor="unitNet"> <Label className="block" htmlFor="unitNet">
<Trans comment="Context: Bytes or bits">Network unit</Trans> <Trans comment="Context: Bytes or bits">Network unit</Trans>
</Label> </Label>
@@ -207,7 +156,7 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<div className="space-y-2"> <div className="grid gap-2">
<Label className="block" htmlFor="unitDisk"> <Label className="block" htmlFor="unitDisk">
<Trans>Disk unit</Trans> <Trans>Disk unit</Trans>
</Label> </Label>
@@ -232,6 +181,47 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
</div> </div>
</div> </div>
<Separator /> <Separator />
<div className="grid gap-2">
<div className="mb-2">
<h3 className="mb-1 text-lg font-medium">
<Trans>Warning thresholds</Trans>
</h3>
<p className="text-sm text-muted-foreground leading-relaxed">
<Trans>Set percentage thresholds for meter colors.</Trans>
</p>
</div>
<div className="grid grid-cols-2 lg:grid-cols-3 gap-4 items-end">
<div className="grid gap-2">
<Label htmlFor="colorWarn">
<Trans>Warning (%)</Trans>
</Label>
<Input
id="colorWarn"
name="colorWarn"
type="number"
min={1}
max={100}
className="min-w-24"
defaultValue={userSettings.colorWarn ?? 65}
/>
</div>
<div className="grid gap-1">
<Label htmlFor="colorCrit">
<Trans>Critical (%)</Trans>
</Label>
<Input
id="colorCrit"
name="colorCrit"
type="number"
min={1}
max={100}
className="min-w-24"
defaultValue={userSettings.colorCrit ?? 90}
/>
</div>
</div>
</div>
<Separator />
<Button type="submit" className="flex items-center gap-1.5 disabled:opacity-100" disabled={isLoading}> <Button type="submit" className="flex items-center gap-1.5 disabled:opacity-100" disabled={isLoading}>
{isLoading ? <LoaderCircleIcon className="h-4 w-4 animate-spin" /> : <SaveIcon className="h-4 w-4" />} {isLoading ? <LoaderCircleIcon className="h-4 w-4 animate-spin" /> : <SaveIcon className="h-4 w-4" />}
<Trans>Save Settings</Trans> <Trans>Save Settings</Trans>

View File

@@ -10,7 +10,7 @@ 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, pb } from "@/lib/stores.ts"
import { toast } from "@/components/ui/use-toast.ts" import { toast } from "@/components/ui/use-toast.ts"
import { UserSettings } from "@/types.js" import { UserSettings } from "@/types"
import General from "./general.tsx" import General from "./general.tsx"
import Notifications from "./notifications.tsx" import Notifications from "./notifications.tsx"
import ConfigYaml from "./config-yaml.tsx" import ConfigYaml from "./config-yaml.tsx"

View File

@@ -87,8 +87,8 @@ const SettingsNotificationsPage = ({ userSettings }: { userSettings: UserSetting
</div> </div>
<Separator className="my-4" /> <Separator className="my-4" />
<div className="space-y-5"> <div className="space-y-5">
<div className="space-y-2"> <div className="grid gap-2">
<div className="mb-4"> <div className="mb-2">
<h3 className="mb-1 text-lg font-medium"> <h3 className="mb-1 text-lg font-medium">
<Trans>Email notifications</Trans> <Trans>Email notifications</Trans>
</h3> </h3>
@@ -178,7 +178,7 @@ const ShoutrrrUrlCard = ({ url, onUrlChange, onRemove }: ShoutrrrUrlCardProps) =
const sendTestNotification = async () => { const sendTestNotification = async () => {
setIsLoading(true) setIsLoading(true)
const res = await pb.send("/api/beszel/send-test-notification", { url }) const res = await pb.send("/api/beszel/test-notification", { method: "POST", body: { url } })
if ("err" in res && !res.err) { if ("err" in res && !res.err) {
toast({ toast({
title: t`Test notification sent`, title: t`Test notification sent`,

View File

@@ -56,8 +56,8 @@ export function SidebarNav({ className, items, ...props }: SidebarNavProps) {
href={item.href} href={item.href}
className={cn( className={cn(
buttonVariants({ variant: "ghost" }), buttonVariants({ variant: "ghost" }),
"flex items-center gap-3 justify-start truncate", "flex items-center gap-3 justify-start truncate duration-50",
page?.path === item.href ? "bg-muted hover:bg-muted" : "hover:bg-muted/50" page?.path === item.href ? "bg-muted hover:bg-accent/70" : "hover:bg-accent/50"
)} )}
> >
{item.icon && <item.icon className="size-4 shrink-0" />} {item.icon && <item.icon className="size-4 shrink-0" />}

View File

@@ -292,11 +292,11 @@ const SectionTable = memo(({ fingerprints = [] }: { fingerprints: FingerprintRec
<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.5">{fingerprint.expand.system.name}</TableCell> <TableCell className="font-medium ps-5 py-2">{fingerprint.expand.system.name}</TableCell>
<TableCell className="font-mono text-[0.95em] py-2.5">{fingerprint.token}</TableCell> <TableCell className="font-mono text-[0.95em] py-2">{fingerprint.token}</TableCell>
<TableCell className="font-mono text-[0.95em] py-2.5">{fingerprint.fingerprint}</TableCell> <TableCell className="font-mono text-[0.95em] py-2">{fingerprint.fingerprint}</TableCell>
{!isReadOnly && ( {!isReadOnly && (
<TableCell className="py-2.5 px-4 xl:px-2"> <TableCell className="py-2 px-4 xl:px-2">
<ActionsButtonTable fingerprint={fingerprint} /> <ActionsButtonTable fingerprint={fingerprint} />
</TableCell> </TableCell>
)} )}

View File

@@ -11,8 +11,8 @@ import {
$temperatureFilter, $temperatureFilter,
} 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 { ChartType, Unit, Os } from "@/lib/enums" import { ChartType, Unit, Os, SystemStatus } from "@/lib/enums"
import React, { lazy, memo, useCallback, useEffect, useMemo, useRef, useState } from "react" import React, { lazy, 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"
@@ -41,6 +41,7 @@ import { timeTicks } from "d3-time"
import { useLingui } from "@lingui/react/macro" 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"
const AreaChartDefault = lazy(() => import("../charts/area-chart")) const AreaChartDefault = lazy(() => import("../charts/area-chart"))
const ContainerChart = lazy(() => import("../charts/container-chart")) const ContainerChart = lazy(() => import("../charts/container-chart"))
@@ -382,9 +383,9 @@ export default function SystemDetail({ name }: { name: string }) {
const hasGpuPowerData = lastGpuVals.some((gpu) => gpu.p !== undefined) const hasGpuPowerData = lastGpuVals.some((gpu) => gpu.p !== undefined)
let translatedStatus: string = system.status let translatedStatus: string = system.status
if (system.status === "up") { if (system.status === SystemStatus.Up) {
translatedStatus = t({ message: "Up", comment: "Context: System is up" }) translatedStatus = t({ message: "Up", comment: "Context: System is up" })
} else if (system.status === "down") { } else if (system.status === SystemStatus.Down) {
translatedStatus = t({ message: "Down", comment: "Context: System is down" }) translatedStatus = t({ message: "Down", comment: "Context: System is down" })
} }
@@ -399,7 +400,7 @@ export default function SystemDetail({ name }: { name: string }) {
<div className="flex flex-wrap items-center gap-3 gap-y-2 text-sm opacity-90"> <div className="flex flex-wrap items-center gap-3 gap-y-2 text-sm opacity-90">
<div className="capitalize flex gap-2 items-center"> <div className="capitalize flex gap-2 items-center">
<span className={cn("relative flex h-3 w-3")}> <span className={cn("relative flex h-3 w-3")}>
{system.status === "up" && ( {system.status === SystemStatus.Up && (
<span <span
className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"
style={{ animationDuration: "1.5s" }} style={{ animationDuration: "1.5s" }}
@@ -407,10 +408,10 @@ export default function SystemDetail({ name }: { name: string }) {
)} )}
<span <span
className={cn("relative inline-flex rounded-full h-3 w-3", { className={cn("relative inline-flex rounded-full h-3 w-3", {
"bg-green-500": system.status === "up", "bg-green-500": system.status === SystemStatus.Up,
"bg-red-500": system.status === "down", "bg-red-500": system.status === SystemStatus.Down,
"bg-primary/40": system.status === "paused", "bg-primary/40": system.status === SystemStatus.Paused,
"bg-yellow-500": system.status === "pending", "bg-yellow-500": system.status === SystemStatus.Pending,
})} })}
></span> ></span>
</span> </span>
@@ -456,9 +457,9 @@ export default function SystemDetail({ name }: { name: string }) {
onClick={() => setGrid(!grid)} onClick={() => setGrid(!grid)}
> >
{grid ? ( {grid ? (
<LayoutGridIcon className="h-[1.2rem] w-[1.2rem] opacity-85" /> <LayoutGridIcon className="h-[1.2rem] w-[1.2rem] opacity-75" />
) : ( ) : (
<Rows className="h-[1.3rem] w-[1.3rem] opacity-85" /> <Rows className="h-[1.3rem] w-[1.3rem] opacity-75" />
)} )}
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
@@ -668,6 +669,35 @@ export default function SystemDetail({ name }: { name: string }) {
</ChartCard> </ChartCard>
)} )}
{/* Battery chart */}
{systemStats.at(-1)?.stats.bat && (
<ChartCard
empty={dataEmpty}
grid={grid}
title={t`Battery`}
description={`${t({
message: "Current state",
comment: "Context: Battery state",
})}: ${batteryStateTranslations[systemStats.at(-1)?.stats.bat![1] ?? 0]()}`}
>
<AreaChartDefault
chartData={chartData}
maxToggled={maxValues}
dataPoints={[
{
label: t`Charge`,
dataKey: ({ stats }) => stats?.bat?.[0],
color: "1",
opacity: 0.35,
},
]}
domain={[0, 100]}
tickFormatter={(val) => `${val}%`}
contentFormatter={({ value }) => `${value}%`}
/>
</ChartCard>
)}
{/* GPU power draw chart */} {/* GPU power draw chart */}
{hasGpuPowerData && ( {hasGpuPowerData && (
<ChartCard <ChartCard
@@ -872,10 +902,10 @@ function ChartCard({
return ( return (
<Card className={cn("pb-2 sm:pb-4 odd:last-of-type:col-span-full", { "col-span-full": !grid })} ref={ref}> <Card className={cn("pb-2 sm:pb-4 odd:last-of-type:col-span-full", { "col-span-full": !grid })} ref={ref}>
<CardHeader className="pb-5 pt-4 relative space-y-1 max-sm:py-3 max-sm:px-4"> <CardHeader className="pb-5 pt-4 gap-1 relative max-sm:py-3 max-sm:px-4">
<CardTitle className="text-xl sm:text-2xl">{title}</CardTitle> <CardTitle className="text-xl sm:text-2xl">{title}</CardTitle>
<CardDescription>{description}</CardDescription> <CardDescription>{description}</CardDescription>
{cornerEl && <div className="relative py-1 block sm:w-44 sm:absolute sm:top-2.5 sm:end-3.5">{cornerEl}</div>} {cornerEl && <div className="relative py-1 block sm:w-44 sm:absolute sm:top-3.5 sm:end-3.5">{cornerEl}</div>}
</CardHeader> </CardHeader>
<div className="ps-0 w-[calc(100%-1.5em)] h-48 md:h-52 relative group"> <div className="ps-0 w-[calc(100%-1.5em)] h-48 md:h-52 relative group">
{ {

View File

@@ -22,6 +22,7 @@ import {
decimalString, decimalString,
formatBytes, formatBytes,
formatTemperature, formatTemperature,
getMeterState,
isReadOnlyUser, isReadOnlyUser,
parseSemVer, parseSemVer,
} from "@/lib/utils" } from "@/lib/utils"
@@ -53,17 +54,22 @@ import {
} from "../ui/alert-dialog" } from "../ui/alert-dialog"
import { buttonVariants } from "../ui/button" import { buttonVariants } from "../ui/button"
import { t } from "@lingui/core/macro" import { t } from "@lingui/core/macro"
import { MeterState, SystemStatus } from "@/lib/enums"
import { $router, Link } from "../router"
import { getPagePath } from "@nanostores/router"
const STATUS_COLORS = {
[SystemStatus.Up]: "bg-green-500",
[SystemStatus.Down]: "bg-red-500",
[SystemStatus.Paused]: "bg-primary/40",
[SystemStatus.Pending]: "bg-yellow-500",
} as const
/** /**
* @param viewMode - "table" or "grid" * @param viewMode - "table" or "grid"
* @returns - Column definitions for the systems table * @returns - Column definitions for the systems table
*/ */
export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef<SystemRecord>[] { export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef<SystemRecord>[] {
const statusTranslations = {
up: () => t`Up`.toLowerCase(),
down: () => t`Down`.toLowerCase(),
paused: () => t`Paused`.toLowerCase(),
}
return [ return [
{ {
size: 200, size: 200,
@@ -71,34 +77,61 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
accessorKey: "name", accessorKey: "name",
id: "system", id: "system",
name: () => t`System`, name: () => t`System`,
filterFn: (row, _, filterVal) => { filterFn: (() => {
const filterLower = filterVal.toLowerCase() let filterInput = ""
const { name, status } = row.original let filterInputLower = ""
// Check if the filter matches the name or status for this row const nameCache = new Map<string, string>()
if ( const statusTranslations = {
name.toLowerCase().includes(filterLower) || [SystemStatus.Up]: t`Up`.toLowerCase(),
statusTranslations[status as keyof typeof statusTranslations]?.().includes(filterLower) [SystemStatus.Down]: t`Down`.toLowerCase(),
) { [SystemStatus.Paused]: t`Paused`.toLowerCase(),
return true } as const
// match filter value against name or translated status
return (row, _, newFilterInput) => {
const { name, status } = row.original
if (newFilterInput !== filterInput) {
filterInput = newFilterInput
filterInputLower = newFilterInput.toLowerCase()
}
let nameLower = nameCache.get(name)
if (nameLower === undefined) {
nameLower = name.toLowerCase()
nameCache.set(name, nameLower)
}
if (nameLower.includes(filterInputLower)) {
return true
}
const statusLower = statusTranslations[status as keyof typeof statusTranslations]
return statusLower?.includes(filterInputLower) || false
} }
return false })(),
},
enableHiding: false, enableHiding: false,
invertSorting: false, invertSorting: false,
Icon: ServerIcon, Icon: ServerIcon,
cell: (info) => ( cell: (info) => {
<span className="flex gap-2 items-center font-medium text-sm text-nowrap md:ps-1 md:pe-5"> const { name } = info.row.original
<IndicatorDot system={info.row.original} /> return (
{info.getValue() as string} <>
</span> <span className="flex gap-2 items-center font-medium text-sm text-nowrap md:ps-1 md:pe-5">
), <IndicatorDot system={info.row.original} />
{name}
</span>
<Link
href={getPagePath($router, "system", { name })}
className="inset-0 absolute size-full"
aria-label={name}
></Link>
</>
)
},
header: sortableHeader, header: sortableHeader,
}, },
{ {
accessorFn: ({ info }) => info.cpu, accessorFn: ({ info }) => info.cpu,
id: "cpu", id: "cpu",
name: () => t`CPU`, name: () => t`CPU`,
cell: CellFormatter, cell: TableCellWithMeter,
Icon: CpuIcon, Icon: CpuIcon,
header: sortableHeader, header: sortableHeader,
}, },
@@ -107,7 +140,7 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
accessorFn: ({ info }) => info.mp, accessorFn: ({ info }) => info.mp,
id: "memory", id: "memory",
name: () => t`Memory`, name: () => t`Memory`,
cell: CellFormatter, cell: TableCellWithMeter,
Icon: MemoryStickIcon, Icon: MemoryStickIcon,
header: sortableHeader, header: sortableHeader,
}, },
@@ -115,7 +148,7 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
accessorFn: ({ info }) => info.dp, accessorFn: ({ info }) => info.dp,
id: "disk", id: "disk",
name: () => t`Disk`, name: () => t`Disk`,
cell: CellFormatter, cell: TableCellWithMeter,
Icon: HardDriveIcon, Icon: HardDriveIcon,
header: sortableHeader, header: sortableHeader,
}, },
@@ -123,7 +156,7 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
accessorFn: ({ info }) => info.g, accessorFn: ({ info }) => info.g,
id: "gpu", id: "gpu",
name: () => "GPU", name: () => "GPU",
cell: CellFormatter, cell: TableCellWithMeter,
Icon: GpuIcon, Icon: GpuIcon,
header: sortableHeader, header: sortableHeader,
}, },
@@ -153,21 +186,23 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
} }
const max = Math.max(...loadAverages) const max = Math.max(...loadAverages)
if (max === 0 && (status === "paused" || minor < 12)) { if (max === 0 && (status === SystemStatus.Paused || minor < 12)) {
return null return null
} }
function getDotColor() { const normalizedLoad = max / (sysInfo.t ?? 1)
const normalized = max / (sysInfo.t ?? 1) const threshold = getMeterState(normalizedLoad * 100)
if (status !== "up") return "bg-primary/30"
if (normalized < 0.7) return "bg-green-500"
if (normalized < 1) return "bg-yellow-500"
return "bg-red-600"
}
return ( return (
<div className="flex items-center gap-[.35em] w-full tabular-nums tracking-tight"> <div className="flex items-center gap-[.35em] w-full tabular-nums tracking-tight">
<span className={cn("inline-block size-2 rounded-full me-0.5", getDotColor())} /> <span
className={cn("inline-block size-2 rounded-full me-0.5", {
[STATUS_COLORS[SystemStatus.Up]]: threshold === MeterState.Good,
[STATUS_COLORS[SystemStatus.Pending]]: threshold === MeterState.Warn,
[STATUS_COLORS[SystemStatus.Down]]: threshold === MeterState.Crit,
[STATUS_COLORS[SystemStatus.Paused]]: status !== SystemStatus.Up,
})}
/>
{loadAverages?.map((la, i) => ( {loadAverages?.map((la, i) => (
<span key={i}>{decimalString(la, la >= 10 ? 1 : 2)}</span> <span key={i}>{decimalString(la, la >= 10 ? 1 : 2)}</span>
))} ))}
@@ -184,10 +219,10 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
header: sortableHeader, header: sortableHeader,
cell(info) { cell(info) {
const sys = info.row.original const sys = info.row.original
if (sys.status === "paused") { const userSettings = useStore($userSettings, { keys: ["unitNet"] })
if (sys.status === SystemStatus.Paused) {
return null return null
} }
const userSettings = useStore($userSettings)
const { value, unit } = formatBytes(info.getValue() as number, true, userSettings.unitNet, false) const { value, unit } = formatBytes(info.getValue() as number, true, userSettings.unitNet, false)
return ( return (
<span className="tabular-nums whitespace-nowrap"> <span className="tabular-nums whitespace-nowrap">
@@ -206,10 +241,10 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
header: sortableHeader, header: sortableHeader,
cell(info) { cell(info) {
const val = info.getValue() as number const val = info.getValue() as number
const userSettings = useStore($userSettings, { keys: ["unitTemp"] })
if (!val) { if (!val) {
return null return null
} }
const userSettings = useStore($userSettings)
const { value, unit } = formatTemperature(val, userSettings.unitTemp) const { value, unit } = formatTemperature(val, userSettings.unitTemp)
return ( return (
<span className={cn("tabular-nums whitespace-nowrap", viewMode === "table" && "ps-0.5")}> <span className={cn("tabular-nums whitespace-nowrap", viewMode === "table" && "ps-0.5")}>
@@ -234,13 +269,13 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
} }
const system = info.row.original const system = info.row.original
return ( return (
<span className={cn("flex gap-2 items-center md:pe-5 tabular-nums", viewMode === "table" && "ps-0.5")}> <span className={cn("flex gap-1.5 items-center md:pe-5 tabular-nums", viewMode === "table" && "ps-0.5")}>
<IndicatorDot <IndicatorDot
system={system} system={system}
className={ className={
(system.status !== "up" && "bg-primary/30") || (system.status !== SystemStatus.Up && STATUS_COLORS[SystemStatus.Paused]) ||
(version === globalThis.BESZEL.HUB_VERSION && "bg-green-500") || (version === globalThis.BESZEL.HUB_VERSION && STATUS_COLORS[SystemStatus.Up]) ||
"bg-yellow-500" STATUS_COLORS[SystemStatus.Pending]
} }
/> />
<span className="truncate max-w-14">{info.getValue() as string}</span> <span className="truncate max-w-14">{info.getValue() as string}</span>
@@ -254,7 +289,7 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
name: () => t({ message: "Actions", comment: "Table column" }), name: () => t({ message: "Actions", comment: "Table column" }),
size: 50, size: 50,
cell: ({ row }) => ( cell: ({ row }) => (
<div className="flex justify-end items-center gap-1 -ms-3"> <div className="relative z-10 flex justify-end items-center gap-1 -ms-3">
<AlertButton system={row.original} /> <AlertButton system={row.original} />
<ActionsButton system={row.original} /> <ActionsButton system={row.original} />
</div> </div>
@@ -279,39 +314,32 @@ function sortableHeader(context: HeaderContext<SystemRecord, unknown>) {
</Button> </Button>
) )
} }
function CellFormatter(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 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">
<span className="min-w-8">{decimalString(val, val >= 10 ? 1 : 2)}%</span> <span className="min-w-8">{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="grow 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 !== "up" && "bg-primary/30") ||
(val < 65 && "bg-green-500") ||
(val < 90 && "bg-yellow-500") ||
"bg-red-600"
)}
style={{
transform: `scalex(${val / 100})`,
}}
></span>
</span> </span>
</div> </div>
) )
} }
export function IndicatorDot({ system, className }: { system: SystemRecord; className?: ClassValue }) { export function IndicatorDot({ system, className }: { system: SystemRecord; className?: ClassValue }) {
className ||= { className ||= STATUS_COLORS[system.status as keyof typeof STATUS_COLORS] || ""
"bg-green-500": system.status === "up",
"bg-red-500": system.status === "down",
"bg-primary/40": system.status === "paused",
"bg-yellow-500": system.status === "pending",
}
return ( return (
<span <span
className={cn("flex-shrink-0 size-2 rounded-full", className)} className={cn("shrink-0 size-2 rounded-full", className)}
// style={{ marginBottom: "-1px" }} // style={{ marginBottom: "-1px" }}
/> />
) )
@@ -329,7 +357,7 @@ export const ActionsButton = memo(({ system }: { system: SystemRecord }) => {
<> <>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" size={"icon"} data-nolink> <Button variant="ghost" size={"icon"}>
<span className="sr-only"> <span className="sr-only">
<Trans>Open menu</Trans> <Trans>Open menu</Trans>
</span> </span>
@@ -352,11 +380,11 @@ export const ActionsButton = memo(({ system }: { system: SystemRecord }) => {
className={cn(isReadOnlyUser() && "hidden")} className={cn(isReadOnlyUser() && "hidden")}
onClick={() => { onClick={() => {
pb.collection("systems").update(id, { pb.collection("systems").update(id, {
status: status === "paused" ? "pending" : "paused", status: status === SystemStatus.Paused ? SystemStatus.Pending : SystemStatus.Paused,
}) })
}} }}
> >
{status === "paused" ? ( {status === SystemStatus.Paused ? (
<> <>
<PlayCircleIcon className="me-2.5 size-4" /> <PlayCircleIcon className="me-2.5 size-4" />
<Trans>Resume</Trans> <Trans>Resume</Trans>

View File

@@ -41,59 +41,17 @@ import { memo, useEffect, useMemo, useState } from "react"
import { $systems } from "@/lib/stores" import { $systems } from "@/lib/stores"
import { useStore } from "@nanostores/react" import { useStore } from "@nanostores/react"
import { cn, useLocalStorage } from "@/lib/utils" import { cn, useLocalStorage } from "@/lib/utils"
import { $router, Link, navigate } 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 "../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"
type ViewMode = "table" | "grid" type ViewMode = "table" | "grid"
function CellFormatter(info: CellContext<SystemRecord, unknown>) {
const val = (info.getValue() as number) || 0
const userSettings = useStore($userSettings)
const yellow = userSettings?.meterThresholds?.yellow ?? 65
const red = userSettings?.meterThresholds?.red ?? 90
return (
<div className="flex gap-2 items-center tabular-nums tracking-tight">
<span className="min-w-8">{decimalString(val, 1)}%</span>
<span className="grow min-w-8 block bg-muted h-[1em] relative rounded-sm overflow-hidden">
<span
className={cn(
"absolute inset-0 w-full h-full origin-left",
(info.row.original.status !== "up" && "bg-primary/30") ||
(val < yellow! && "bg-green-500") ||
(val < red! && "bg-yellow-500") ||
"bg-red-600"
)}
style={{
transform: `scalex(${val / 100})`,
}}
></span>
</span>
</div>
)
}
function sortableHeader(context: HeaderContext<SystemRecord, unknown>) {
const { column } = context
// @ts-ignore
const { Icon, hideSort, name }: { Icon: React.ElementType; name: () => string; hideSort: boolean } = column.columnDef
return (
<Button
variant="ghost"
className="h-9 px-3 flex"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
{Icon && <Icon className="me-2 size-4" />}
{name()}
{hideSort || <ArrowUpDownIcon className="ms-2 size-4" />}
</Button>
)
}
export default function SystemsTable() { export default function SystemsTable() {
const data = useStore($systems) const data = useStore($systems)
const { i18n, t } = useLingui() const { i18n, t } = useLingui()
@@ -111,7 +69,7 @@ export default function SystemsTable() {
} }
}, [filter]) }, [filter])
const columnDefs = useMemo(() => SystemsTableColumns(viewMode), []) const columnDefs = useMemo(() => SystemsTableColumns(viewMode), [viewMode])
const table = useReactTable({ const table = useReactTable({
data, data,
@@ -334,15 +292,9 @@ const SystemTableRow = memo(
return ( return (
<TableRow <TableRow
// data-state={row.getIsSelected() && "selected"} // data-state={row.getIsSelected() && "selected"}
className={cn("cursor-pointer transition-opacity", { className={cn("cursor-pointer transition-opacity relative", {
"opacity-50": system.status === "paused", "opacity-50": system.status === SystemStatus.Paused,
})} })}
onClick={(e) => {
const target = e.target as HTMLElement
if (!target.closest("[data-nolink]") && e.currentTarget.contains(target)) {
navigate(getPagePath($router, "system", { name: system.name }))
}
}}
> >
{row.getVisibleCells().map((cell) => ( {row.getVisibleCells().map((cell) => (
<TableCell <TableCell
@@ -350,7 +302,7 @@ const SystemTableRow = memo(
style={{ style={{
width: cell.column.getSize(), width: cell.column.getSize(),
}} }}
className={cn("overflow-hidden relative", length > 10 ? "py-2" : "py-2.5")} className={length > 10 ? "py-2" : "py-2.5"}
> >
{flexRender(cell.column.columnDef.cell, cell.getContext())} {flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell> </TableCell>
@@ -373,7 +325,7 @@ const SystemCard = memo(
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",
{ {
"opacity-50": system.status === "paused", "opacity-50": system.status === SystemStatus.Paused,
} }
)} )}
> >
@@ -388,14 +340,14 @@ const SystemCard = memo(
</div> </div>
</CardTitle> </CardTitle>
{table.getColumn("actions")?.getIsVisible() && ( {table.getColumn("actions")?.getIsVisible() && (
<div className="flex gap-1 flex-shrink-0 relative z-10"> <div className="flex gap-1 shrink-0 relative z-10">
<AlertButton system={system} /> <AlertButton system={system} />
<ActionsButton system={system} /> <ActionsButton system={system} />
</div> </div>
)} )}
</div> </div>
</CardHeader> </CardHeader>
<CardContent className="space-y-2.5 text-sm px-5 pt-3.5 pb-4"> <CardContent className="grid gap-2.5 text-sm px-5 pt-3.5 pb-4">
{table.getAllColumns().map((column) => { {table.getAllColumns().map((column) => {
if (!column.getIsVisible() || column.id === "system" || column.id === "actions") return null if (!column.getIsVisible() || column.id === "system" || column.id === "actions") return null
const cell = row.getAllCells().find((cell) => cell.column.id === column.id) const cell = row.getAllCells().find((cell) => cell.column.id === column.id)

View File

@@ -34,7 +34,7 @@ const AlertDialogContent = React.forwardRef<
<AlertDialogPrimitive.Content <AlertDialogPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-card p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-50% data-[state=closed]:slide-out-to-top-48% data-[state=open]:slide-in-from-left-50% data-[state=open]:slide-in-from-top-48% sm:rounded-lg",
className className
)} )}
{...props} {...props}
@@ -44,7 +44,7 @@ const AlertDialogContent = React.forwardRef<
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => ( const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col space-y-2 text-center sm:text-start", className)} {...props} /> <div className={cn("grid gap-2 text-center sm:text-start", className)} {...props} />
) )
AlertDialogHeader.displayName = "AlertDialogHeader" AlertDialogHeader.displayName = "AlertDialogHeader"

View File

@@ -4,7 +4,7 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const badgeVariants = cva( const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2",
{ {
variants: { variants: {
variant: { variant: {

View File

@@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const buttonVariants = cva( const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 cursor-pointer",
{ {
variants: { variants: {
variant: { variant: {
@@ -13,7 +13,7 @@ const buttonVariants = cva(
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border bg-background hover:bg-accent/70 dark:hover:bg-accent/50 hover:text-accent-foreground", outline: "border bg-background hover:bg-accent/70 dark:hover:bg-accent/50 hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground", ghost: "hover:bg-accent/70 hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline", link: "text-primary underline-offset-4 hover:underline",
}, },
size: { size: {

View File

@@ -5,16 +5,14 @@ import { cn } from "@/lib/utils"
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => ( const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
className={cn("rounded-lg border border-border/60 bg-card text-card-foreground shadow-sm", className)} className={cn("rounded-lg border border-border/60 bg-card text-card-foreground shadow-xs", className)}
{...props} {...props}
/> />
)) ))
Card.displayName = "Card" Card.displayName = "Card"
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>( const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => ( ({ className, ...props }, ref) => <div ref={ref} className={cn("grid gap-1.5 p-6", className)} {...props} />
<div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
)
) )
CardHeader.displayName = "CardHeader" CardHeader.displayName = "CardHeader"

View File

@@ -4,6 +4,8 @@ import * as RechartsPrimitive from "recharts"
import { chartTimeData, cn } from "@/lib/utils" import { chartTimeData, cn } from "@/lib/utils"
import { ChartData } from "@/types" import { ChartData } from "@/types"
import type { JSX } from "react"
// Format: { THEME_NAME: CSS_SELECTOR } // Format: { THEME_NAME: CSS_SELECTOR }
const THEMES = { light: "", dark: ".dark" } as const const THEMES = { light: "", dark: ".dark" } as const
@@ -42,11 +44,12 @@ const ChartContainer = React.forwardRef<
return ( return (
//<ChartContext.Provider value={{ config }}> //<ChartContext.Provider value={{ config }}>
//</ChartContext.Provider>
<div <div
data-chart={chartId} data-chart={chartId}
ref={ref} ref={ref}
className={cn( className={cn(
"text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line-line]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none", "text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line-line]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-hidden [&_.recharts-surface]:outline-hidden",
className className
)} )}
{...props} {...props}
@@ -54,7 +57,6 @@ const ChartContainer = React.forwardRef<
{/* <ChartStyle id={chartId} config={config} /> */} {/* <ChartStyle id={chartId} config={config} /> */}
<RechartsPrimitive.ResponsiveContainer>{children}</RechartsPrimitive.ResponsiveContainer> <RechartsPrimitive.ResponsiveContainer>{children}</RechartsPrimitive.ResponsiveContainer>
</div> </div>
//</ChartContext.Provider>
) )
}) })
ChartContainer.displayName = "Chart" ChartContainer.displayName = "Chart"
@@ -169,7 +171,7 @@ const ChartTooltipContent = React.forwardRef<
<div <div
ref={ref} ref={ref}
className={cn( className={cn(
"grid min-w-[7rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl", "grid min-w-28 items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
className className
)} )}
> >
@@ -196,7 +198,7 @@ const ChartTooltipContent = React.forwardRef<
<itemConfig.icon /> <itemConfig.icon />
) : ( ) : (
<div <div
className={cn("shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]", { className={cn("shrink-0 rounded-[2px] border-border bg-(--color-bg)", {
"h-2.5 w-2.5": indicator === "dot", "h-2.5 w-2.5": indicator === "dot",
"w-1": indicator === "line", "w-1": indicator === "line",
"w-0 border-[1.5px] border-dashed bg-transparent": indicator === "dashed", "w-0 border-[1.5px] border-dashed bg-transparent": indicator === "dashed",
@@ -226,7 +228,7 @@ const ChartTooltipContent = React.forwardRef<
{itemConfig?.label || item.name} {itemConfig?.label || item.name}
</span> </span>
{item.value !== undefined && ( {item.value !== undefined && (
<span className="font-medium tabular-nums text-foreground"> <span className="font-medium text-foreground">
{content && typeof content === "function" {content && typeof content === "function"
? content(item, key) ? content(item, key)
: item.value.toLocaleString() + (unit ? unit : "")} : item.value.toLocaleString() + (unit ? unit : "")}
@@ -265,7 +267,7 @@ const ChartLegendContent = React.forwardRef<
<div <div
ref={ref} ref={ref}
className={cn( className={cn(
"flex items-center justify-center gap-4 gap-y-1 flex-wrap", "flex items-center justify-center gap-4 gap-y-1 flex-wrap ps-4",
verticalAlign === "top" ? "pb-3" : "pt-3", verticalAlign === "top" ? "pb-3" : "pt-3",
className className
)} )}

View File

@@ -13,7 +13,7 @@ const Checkbox = React.forwardRef<
<CheckboxPrimitive.Root <CheckboxPrimitive.Root
ref={ref} ref={ref}
className={cn( className={cn(
"peer size-4 flex items-center justify-center shrink-0 rounded-[.3em] border border-input ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground", "peer size-4 flex items-center justify-center shrink-0 rounded-[.3em] border border-input ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className className
)} )}

View File

@@ -12,7 +12,7 @@ const Command = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<CommandPrimitive <CommandPrimitive
ref={ref} ref={ref}
className={cn("flex h-full w-full flex-col overflow-hidden bg-popover text-popover-foreground", className)} className={cn("flex h-full w-full flex-col overflow-hidden bg-card", className)}
{...props} {...props}
/> />
)) ))
@@ -44,7 +44,7 @@ const CommandInput = React.forwardRef<
<CommandPrimitive.Input <CommandPrimitive.Input
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50", "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",
className className
)} )}
{...props} {...props}
@@ -105,7 +105,7 @@ const CommandItem = React.forwardRef<
<CommandPrimitive.Item <CommandPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default opacity-70 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:opacity-90 data-[disabled='true']:pointer-events-none data-[disabled='true']:opacity-50", "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 className
)} )}
{...props} {...props}

View File

@@ -36,13 +36,13 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content <DialogPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-card p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-card p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-50% data-[state=closed]:slide-out-to-top-48% data-[state=open]:slide-in-from-left-50% data-[state=open]:slide-in-from-top-48% sm:rounded-lg",
className className
)} )}
{...props} {...props}
> >
{children} {children}
<DialogPrimitive.Close className="absolute end-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"> <DialogPrimitive.Close className="absolute end-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" /> <X className="h-4 w-4" />
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
</DialogPrimitive.Close> </DialogPrimitive.Close>
@@ -52,7 +52,7 @@ const DialogContent = React.forwardRef<
DialogContent.displayName = DialogPrimitive.Content.displayName DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => ( const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col space-y-1.5 text-center sm:text-start", className)} {...props} /> <div className={cn("grid gap-1.5 text-center sm:text-start", className)} {...props} />
) )
DialogHeader.displayName = "DialogHeader" DialogHeader.displayName = "DialogHeader"

View File

@@ -25,7 +25,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
<DropdownMenuPrimitive.SubTrigger <DropdownMenuPrimitive.SubTrigger
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default select-none items-center rounded-sm px-2.5 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent", "flex select-none items-center rounded-sm px-2.5 py-1.5 text-sm outline-hidden focus:bg-accent/70 data-[state=open]:bg-accent/70",
inset && "ps-8", inset && "ps-8",
className className
)} )}
@@ -44,7 +44,7 @@ const DropdownMenuSubContent = React.forwardRef<
<DropdownMenuPrimitive.SubContent <DropdownMenuPrimitive.SubContent
ref={ref} ref={ref}
className={cn( className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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",
className className
)} )}
{...props} {...props}
@@ -61,7 +61,7 @@ const DropdownMenuContent = React.forwardRef<
ref={ref} ref={ref}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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",
className className
)} )}
{...props} {...props}
@@ -79,7 +79,7 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item <DropdownMenuPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2.5 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "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 cursor-default select-none items-center rounded-sm py-1.5 ps-8 pe-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "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",
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-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "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",
className className
)} )}
{...props} {...props}

View File

@@ -11,11 +11,11 @@ export function InputCopy({ value, id, name }: { value: string; id: string; name
<Input readOnly id={id} name={name} value={value} required></Input> <Input readOnly id={id} name={name} value={value} required></Input>
<div <div
className={ className={
"h-6 w-24 bg-gradient-to-r rtl:bg-gradient-to-l from-transparent to-background to-65% absolute top-2 end-1 pointer-events-none" "h-6 w-24 bg-linear-to-r rtl:bg-linear-to-l from-transparent to-background to-65% absolute top-2 end-1 pointer-events-none"
} }
></div> ></div>
<TooltipProvider delayDuration={100} disableHoverableContent> <TooltipProvider delayDuration={100} disableHoverableContent>
<Tooltip> <Tooltip disableHoverableContent={true}>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <Button
type="button" type="button"

View File

@@ -33,7 +33,7 @@ const InputTags = React.forwardRef<HTMLInputElement, InputTagsProps>(
return ( return (
<div <div
className={cn( className={cn(
"bg-background min-h-10 flex w-full flex-wrap gap-2 rounded-md border px-3 py-2 text-sm placeholder:text-muted-foreground has-[:focus-visible]:outline-none ring-offset-background has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-ring has-[:focus-visible]:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", "bg-background min-h-10 flex w-full flex-wrap gap-2 rounded-md border px-3 py-2 text-sm placeholder:text-muted-foreground has-focus-visible:outline-hidden ring-offset-background has-focus-visible:ring-2 has-focus-visible:ring-ring has-focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className className
)} )}
> >
@@ -53,7 +53,7 @@ const InputTags = React.forwardRef<HTMLInputElement, InputTagsProps>(
</Badge> </Badge>
))} ))}
<input <input
className="flex-1 outline-none bg-background placeholder:text-muted-foreground" className="flex-1 outline-hidden bg-background placeholder:text-muted-foreground"
value={pendingDataPoint} value={pendingDataPoint}
onChange={(e) => setPendingDataPoint(e.target.value)} onChange={(e) => setPendingDataPoint(e.target.value)}
onKeyDown={(e) => { onKeyDown={(e) => {

View File

@@ -9,7 +9,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(({ className, type,
<input <input
type={type} type={type}
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-none 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",
className className
)} )}
ref={ref} ref={ref}

View File

@@ -17,7 +17,7 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger <SelectPrimitive.Trigger
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-10 w-full items-center justify-between rounded-md border bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", "flex h-10 w-full items-center justify-between rounded-md border bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className className
)} )}
{...props} {...props}
@@ -66,7 +66,7 @@ const SelectContent = React.forwardRef<
<SelectPrimitive.Content <SelectPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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", "relative z-50 max-h-96 min-w-32 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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",
position === "popper" && position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className className
@@ -79,7 +79,7 @@ const SelectContent = React.forwardRef<
className={cn( className={cn(
"p-1", "p-1",
position === "popper" && position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]" "h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width)"
)} )}
> >
{children} {children}
@@ -105,7 +105,7 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.Item <SelectPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 ps-8 pe-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex w-full 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",
className className
)} )}
{...props} {...props}

View File

@@ -11,7 +11,7 @@ const Separator = React.forwardRef<
ref={ref} ref={ref}
decorative={decorative} decorative={decorative}
orientation={orientation} orientation={orientation}
className={cn("shrink-0 bg-border", orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]", className)} className={cn("shrink-0 bg-border", orientation === "horizontal" ? "h-px w-full" : "h-full w-px", className)}
{...props} {...props}
/> />
)) ))

View File

@@ -0,0 +1,101 @@
import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog"
import { XIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
return <SheetPrimitive.Root data-slot="sheet" {...props} />
}
function SheetTrigger({ ...props }: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
}
function SheetClose({ ...props }: React.ComponentProps<typeof SheetPrimitive.Close>) {
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
}
function SheetPortal({ ...props }: React.ComponentProps<typeof SheetPrimitive.Portal>) {
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
}
function SheetOverlay({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
return (
<SheetPrimitive.Overlay
data-slot="sheet-overlay"
className={cn(
"data-[state=open]:animate-in duration-500 isolate data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/40",
className
)}
{...props}
/>
)
}
function SheetContent({
className,
children,
side = "right",
...props
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
side?: "top" | "right" | "bottom" | "left"
}) {
return (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
data-slot="sheet-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-[400ms]",
side === "right" &&
"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
side === "left" &&
"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
side === "top" &&
"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
side === "bottom" &&
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
className
)}
{...props}
>
{children}
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
<XIcon className="size-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
)
}
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
return <div data-slot="sheet-header" className={cn("flex flex-col gap-1.5 p-4", className)} {...props} />
}
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
return <div data-slot="sheet-footer" className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} />
}
function SheetTitle({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Title>) {
return (
<SheetPrimitive.Title
data-slot="sheet-title"
className={cn("text-foreground font-semibold", className)}
{...props}
/>
)
}
function SheetDescription({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Description>) {
return (
<SheetPrimitive.Description
data-slot="sheet-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
}
export { Sheet, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription }

View File

@@ -15,7 +15,7 @@ const Slider = React.forwardRef<
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary"> <SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
<SliderPrimitive.Range className="absolute h-full bg-primary" /> <SliderPrimitive.Range className="absolute h-full bg-primary" />
</SliderPrimitive.Track> </SliderPrimitive.Track>
<SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" /> <SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
</SliderPrimitive.Root> </SliderPrimitive.Root>
)) ))
Slider.displayName = SliderPrimitive.Root.displayName Slider.displayName = SliderPrimitive.Root.displayName

View File

@@ -9,7 +9,7 @@ const Switch = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SwitchPrimitives.Root <SwitchPrimitives.Root
className={cn( className={cn(
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input", "peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className className
)} )}
{...props} {...props}
@@ -17,7 +17,7 @@ const Switch = React.forwardRef<
> >
<SwitchPrimitives.Thumb <SwitchPrimitives.Thumb
className={cn( className={cn(
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 rtl:data-[state=checked]:-translate-x-5 data-[state=unchecked]:translate-x-0" "pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=checked]:rtl:-translate-x-5 data-[state=unchecked]:translate-x-0"
)} )}
/> />
</SwitchPrimitives.Root> </SwitchPrimitives.Root>

View File

@@ -27,7 +27,7 @@ TableBody.displayName = "TableBody"
const TableFooter = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>( const TableFooter = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
({ className, ...props }, ref) => ( ({ className, ...props }, ref) => (
<tfoot ref={ref} className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)} {...props} /> <tfoot ref={ref} className={cn("border-t bg-muted/50 font-medium last:[&>tr]:border-b-0", className)} {...props} />
) )
) )
TableFooter.displayName = "TableFooter" TableFooter.displayName = "TableFooter"
@@ -37,7 +37,7 @@ const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTML
<tr <tr
ref={ref} ref={ref}
className={cn( className={cn(
"border-b border-border/60 hover:bg-muted/40 dark:hover:bg-muted/20 data-[state=selected]:!bg-muted", "border-b border-border/60 hover:bg-muted/40 dark:hover:bg-muted/20 data-[state=selected]:bg-muted!",
className className
)} )}
{...props} {...props}

View File

@@ -27,7 +27,7 @@ const TabsTrigger = React.forwardRef<
<TabsPrimitive.Trigger <TabsPrimitive.Trigger
ref={ref} ref={ref}
className={cn( className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm", "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-xs cursor-pointer",
className className
)} )}
{...props} {...props}
@@ -42,7 +42,7 @@ const TabsContent = React.forwardRef<
<TabsPrimitive.Content <TabsPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", "mt-2 ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className className
)} )}
{...props} {...props}

View File

@@ -8,7 +8,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(({ classNa
return ( return (
<textarea <textarea
className={cn( className={cn(
"flex min-h-14 w-full rounded-md border bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", "flex min-h-14 w-full rounded-md border bg-background px-3 py-2 text-sm ring-offset-background 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",
className className
)} )}
ref={ref} ref={ref}

View File

@@ -14,7 +14,7 @@ const ToastViewport = React.forwardRef<
<ToastPrimitives.Viewport <ToastPrimitives.Viewport
ref={ref} ref={ref}
className={cn( className={cn(
"fixed top-0 z-[100] flex max-h-dvh w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]", "fixed top-0 z-100 flex max-h-dvh w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
className className
)} )}
{...props} {...props}
@@ -23,7 +23,7 @@ const ToastViewport = React.forwardRef<
ToastViewport.displayName = ToastPrimitives.Viewport.displayName ToastViewport.displayName = ToastPrimitives.Viewport.displayName
const toastVariants = cva( const toastVariants = cva(
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pe-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pe-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-(--radix-toast-swipe-end-x) data-[swipe=move]:translate-x-(--radix-toast-swipe-move-x) data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full sm:data-[state=open]:slide-in-from-bottom-full",
{ {
variants: { variants: {
variant: { variant: {
@@ -52,7 +52,7 @@ const ToastAction = React.forwardRef<
<ToastPrimitives.Action <ToastPrimitives.Action
ref={ref} ref={ref}
className={cn( className={cn(
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive", "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 hover:group-[.destructive]:border-destructive/30 hover:group-[.destructive]:bg-destructive hover:group-[.destructive]:text-destructive-foreground focus:group-[.destructive]:ring-destructive",
className className
)} )}
{...props} {...props}
@@ -67,7 +67,7 @@ const ToastClose = React.forwardRef<
<ToastPrimitives.Close <ToastPrimitives.Close
ref={ref} ref={ref}
className={cn( className={cn(
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600", "absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-hidden focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 hover:group-[.destructive]:text-red-50 focus:group-[.destructive]:ring-red-400 focus:group-[.destructive]:ring-offset-red-600",
className className
)} )}
toast-close="" toast-close=""

View File

@@ -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 }

View File

@@ -1,101 +1,157 @@
@tailwind base; @import "tailwindcss";
@tailwind components; @import "tw-animate-css";
@tailwind utilities;
@layer base { @custom-variant dark (&:is(.dark *));
:root {
--background: 30 8% 98.5%;
--foreground: 30 0% 0%;
--card: 30 0% 100%;
--card-foreground: 240 6.67% 2.94%;
--popover: 30 0% 100%;
--popover-foreground: 240 10% 6.2%;
--primary: 240 5.88% 10%;
--primary-foreground: 30 0% 100%;
--secondary: 240 4.76% 95.88%;
--secondary-foreground: 240 5.88% 10%;
--muted: 26 6% 94%;
--muted-foreground: 24 2.79% 35.1%;
--accent: 20 23.08% 94%;
--accent-foreground: 240 5.88% 10%;
--destructive: 0 66% 53%;
--destructive-foreground: 0 0% 98.04%;
--border: 30 8.11% 85.49%;
--input: 30 4.29% 72.55%;
--ring: 30 3.97% 49.41%;
--radius: 0.8rem;
/* charts */
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
.dark { :root {
color-scheme: dark; --background: hsl(30 8% 98%);
--background: 220 5.5% 9%; --foreground: hsl(30 0% 0%);
--foreground: 220 2% 97%; --card: hsl(30 0% 100%);
--card: 220 5.5% 10.5%; --card-foreground: hsl(240 6.67% 2.94%);
--card-foreground: 220 2% 97%; --popover: hsl(30 0% 100%);
--popover: 220 5.5% 9%; --popover-foreground: hsl(240 10% 6.2%);
--popover-foreground: 220 2% 97%; --primary: hsl(240 5.88% 10%);
--primary: 220 2% 96%; --primary-foreground: hsl(30 0% 100%);
--primary-foreground: 220 4% 10%; --secondary: hsl(240 4.76% 95.88%);
--secondary: 220 4% 16%; --secondary-foreground: hsl(240 5.88% 10%);
--secondary-foreground: 220 0% 98%; --muted: hsl(26 6% 94%);
--muted: 220 6% 16%; --muted-foreground: hsl(24 2.79% 35.1%);
--muted-foreground: 220 4% 67%; --accent: hsl(20 23.08% 94%);
--accent: 220 5% 15.5%; --accent-foreground: hsl(240 5.88% 10%);
--accent-foreground: 220 2% 98%; --destructive: hsl(0 66% 53%);
--destructive: 0 62% 46%; --destructive-foreground: hsl(0 0% 98.04%);
--destructive-foreground: 0 0% 97%; --border: hsl(30 8.11% 85.49%);
--border: 220 3% 16%; --input: hsl(30 4.29% 72.55%);
--input: 220 4% 22%; --ring: hsl(30 3.97% 49.41%);
--ring: 220 4% 80%; --radius: 0.8rem;
--radius: 0.8rem; --chart-1: hsl(220 70% 50%);
} --chart-2: hsl(160 60% 45%);
--chart-3: hsl(30 80% 55%);
--chart-4: hsl(280 65% 60%);
--chart-5: hsl(340 75% 55%);
} }
/* Fonts */ .dark {
@supports (font-variation-settings: normal) { color-scheme: dark;
:root { --background: hsl(220 5.5% 9%);
font-family: Inter, InterVariable, sans-serif; --foreground: hsl(220 2% 97%);
} --card: hsl(220 5.5% 10.5%);
--card-foreground: hsl(220 2% 97%);
--popover: hsl(220 5.5% 9%);
--popover-foreground: hsl(220 2% 97%);
--primary: hsl(220 2% 96%);
--primary-foreground: hsl(220 4% 10%);
--secondary: hsl(220 4% 16%);
--secondary-foreground: hsl(220 0% 98%);
--muted: hsl(220 6% 16%);
--muted-foreground: hsl(220 4% 67%);
--accent: hsl(220 5% 15.5%);
--accent-foreground: hsl(220 2% 98%);
--destructive: hsl(0 62% 46%);
--destructive-foreground: hsl(0 0% 97%);
--border: hsl(220 3% 16%);
--input: hsl(220 4% 22%);
--ring: hsl(220 4% 80%);
--radius: 0.8rem;
} }
@font-face {
font-family: InterVariable; @theme inline {
font-style: normal; --font-sans: Inter, InterVariable, sans-serif;
font-weight: 100 900;
font-display: swap; --breakpoint-xs: 26.6rem;
src: url("/static/InterVariable.woff2?v=4.0") format("woff2"); --breakpoint-450: 28rem;
--breakpoint-2xl: 90rem;
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-green-50: hsl(140 60% 95%);
--color-green-100: hsl(140 50% 90%);
--color-green-200: hsl(140 49% 80%);
--color-green-300: hsl(140 48% 70%);
--color-green-400: hsl(140 49% 60%);
--color-green-500: hsl(140 50% 48%);
--color-green-600: hsl(140 52% 38%);
--color-green-700: hsl(140 53% 29%);
--color-green-800: hsl(140 54% 20%);
--color-green-900: hsl(140 54% 12%);
--color-green-950: hsl(140 57% 6%);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
}
@layer utilities {
/* Fonts */
@supports (font-variation-settings: normal) {
:root {
font-family: Inter, InterVariable, sans-serif;
}
}
@font-face {
font-family: InterVariable;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url("/static/InterVariable.woff2?v=4.0") format("woff2");
}
} }
@layer base { @layer base {
* { * {
@apply border-border; @apply border-border outline-ring/50;
overflow-anchor: none; overflow-anchor: none;
} }
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
} }
button {
cursor: pointer;
}
} }
@layer utilities { @utility container {
.link { @apply max-w-360 mx-auto px-4;
@apply text-primary font-medium underline-offset-4 hover:underline; }
}
@utility link {
@apply text-primary font-medium underline-offset-4 hover:underline;
}
@utility ns-dialog {
/* New system dialog width */ /* New system dialog width */
.ns-dialog { min-width: 30.3rem;
min-width: 30.3rem; :where(:lang(zh), :lang(zh-CN), :lang(ko)) & {
}
:where(:lang(zh), :lang(zh-CN), :lang(ko)) .ns-dialog {
min-width: 27.9rem; min-width: 27.9rem;
} }
} }
.recharts-tooltip-wrapper { .recharts-tooltip-wrapper {
z-index: 1; z-index: 1;
@apply tabular-nums;
} }
.recharts-yAxis { .recharts-yAxis {

View File

@@ -0,0 +1,170 @@
import type { AlertInfo, AlertRecord } from "@/types"
import type { RecordSubscription } from "pocketbase"
import { pb, $alerts } from "@/lib/stores"
import { EthernetIcon } from "@/components/ui/icons"
import { ServerIcon, CpuIcon, MemoryStickIcon, HardDriveIcon, ThermometerIcon, HourglassIcon } from "lucide-react"
import { t } from "@lingui/core/macro"
/** Alert info for each alert type */
export const alertInfo: Record<string, AlertInfo> = {
Status: {
name: () => t`Status`,
unit: "",
icon: ServerIcon,
desc: () => t`Triggers when status switches between up and down`,
/** "for x minutes" is appended to desc when only one value */
singleDesc: () => t`System` + " " + t`Down`,
},
CPU: {
name: () => t`CPU Usage`,
unit: "%",
icon: CpuIcon,
desc: () => t`Triggers when CPU usage exceeds a threshold`,
},
Memory: {
name: () => t`Memory Usage`,
unit: "%",
icon: MemoryStickIcon,
desc: () => t`Triggers when memory usage exceeds a threshold`,
},
Disk: {
name: () => t`Disk Usage`,
unit: "%",
icon: HardDriveIcon,
desc: () => t`Triggers when usage of any disk exceeds a threshold`,
},
Bandwidth: {
name: () => t`Bandwidth`,
unit: " MB/s",
icon: EthernetIcon,
desc: () => t`Triggers when combined up/down exceeds a threshold`,
max: 125,
},
Temperature: {
name: () => t`Temperature`,
unit: "°C",
icon: ThermometerIcon,
desc: () => t`Triggers when any sensor exceeds a threshold`,
},
LoadAvg1: {
name: () => t`Load Average 1m`,
unit: "",
icon: HourglassIcon,
max: 100,
min: 0.1,
start: 10,
step: 0.1,
desc: () => t`Triggers when 1 minute load average exceeds a threshold`,
},
LoadAvg5: {
name: () => t`Load Average 5m`,
unit: "",
icon: HourglassIcon,
max: 100,
min: 0.1,
start: 10,
step: 0.1,
desc: () => t`Triggers when 5 minute load average exceeds a threshold`,
},
LoadAvg15: {
name: () => t`Load Average 15m`,
unit: "",
icon: HourglassIcon,
min: 0.1,
max: 100,
start: 10,
step: 0.1,
desc: () => t`Triggers when 15 minute load average exceeds a threshold`,
},
} as const
/** Helper to manage user alerts */
export const alertManager = (() => {
const collection = pb.collection<AlertRecord>("alerts")
let unsub: () => void
/** Fields to fetch from alerts collection */
const fields = "id,name,system,value,min,triggered"
/** Fetch alerts from collection */
async function fetchAlerts(): Promise<AlertRecord[]> {
return await collection.getFullList<AlertRecord>({ fields, sort: "updated" })
}
/** Format alerts into a map of system id to alert name to alert record */
function add(alerts: AlertRecord[]) {
for (const alert of alerts) {
const systemId = alert.system
const systemAlerts = $alerts.get()[systemId] ?? new Map()
const newAlerts = new Map(systemAlerts)
newAlerts.set(alert.name, alert)
$alerts.setKey(systemId, newAlerts)
}
}
function remove(alerts: Pick<AlertRecord, "name" | "system">[]) {
for (const alert of alerts) {
const systemId = alert.system
const systemAlerts = $alerts.get()[systemId]
const newAlerts = new Map(systemAlerts)
newAlerts.delete(alert.name)
$alerts.setKey(systemId, newAlerts)
}
}
const actionFns = {
create: add,
update: add,
delete: remove,
}
// batch alert updates to prevent unnecessary re-renders when adding many alerts at once
const batchUpdate = (() => {
const batch = new Map<string, RecordSubscription<AlertRecord>>()
let timeout: ReturnType<typeof setTimeout>
return (data: RecordSubscription<AlertRecord>) => {
const { record } = data
batch.set(`${record.system}${record.name}`, data)
clearTimeout(timeout!)
timeout = setTimeout(() => {
const groups = { create: [], update: [], delete: [] } as Record<string, AlertRecord[]>
for (const { action, record } of batch.values()) {
groups[action]?.push(record)
}
for (const key in groups) {
if (groups[key].length) {
actionFns[key as keyof typeof actionFns]?.(groups[key])
}
}
batch.clear()
}, 50)
}
})()
async function subscribe() {
unsub = await collection.subscribe("*", batchUpdate, { fields })
}
function unsubscribe() {
unsub?.()
}
async function refresh() {
const records = await fetchAlerts()
add(records)
}
return {
/** Add alerts to store */
add,
/** Remove alerts from store */
remove,
/** Subscribe to alerts */
subscribe,
/** Unsubscribe from alerts */
unsubscribe,
/** Refresh alerts with latest data from hub */
refresh,
}
})()

View File

@@ -21,3 +21,28 @@ export enum Unit {
Celsius, Celsius,
Fahrenheit, Fahrenheit,
} }
/** Meter state for color */
export enum MeterState {
Good,
Warn,
Crit,
}
/** System status states */
export enum SystemStatus {
Up = "up",
Down = "down",
Pending = "pending",
Paused = "paused",
}
/** Battery state */
export enum BatteryState {
Unknown,
Empty,
Full,
Charging,
Discharging,
Idle,
}

View File

@@ -4,6 +4,8 @@ import type { Messages } from "@lingui/core"
import languages from "@/lib/languages" import languages from "@/lib/languages"
import { detect, fromStorage, fromNavigator } from "@lingui/detect-locale" import { detect, fromStorage, fromNavigator } from "@lingui/detect-locale"
import { messages as enMessages } from "@/locales/en/en" import { messages as enMessages } from "@/locales/en/en"
import { BatteryState } from "./enums"
import { t } from "@lingui/core/macro"
// activates locale // activates locale
function activateLocale(locale: string, messages: Messages = enMessages) { function activateLocale(locale: string, messages: Messages = enMessages) {
@@ -54,3 +56,14 @@ export function getLocale() {
} }
return locale return locale
} }
////////////////////////////////////////////////////////
export const batteryStateTranslations = {
[BatteryState.Unknown]: () => t({ message: "Unknown", comment: "Context: Battery state" }),
[BatteryState.Empty]: () => t({ message: "Empty", comment: "Context: Battery state" }),
[BatteryState.Full]: () => t({ message: "Full", comment: "Context: Battery state" }),
[BatteryState.Charging]: () => t({ message: "Charging", comment: "Context: Battery state" }),
[BatteryState.Discharging]: () => t({ message: "Discharging", comment: "Context: Battery state" }),
[BatteryState.Idle]: () => t({ message: "Idle", comment: "Context: Battery state" }),
} as const

View File

@@ -1,7 +1,8 @@
import PocketBase from "pocketbase" import PocketBase from "pocketbase"
import { atom, map, PreinitializedWritableAtom } from "nanostores" import { atom, map } from "nanostores"
import { AlertRecord, ChartTimes, SystemRecord, UserSettings } from "@/types" import { AlertMap, ChartTimes, SystemRecord, UserSettings } from "@/types"
import { basePath } from "@/components/router" import { basePath } from "@/components/router"
import { Unit } from "./enums"
/** PocketBase JS Client */ /** PocketBase JS Client */
export const pb = new PocketBase(basePath) export const pb = new PocketBase(basePath)
@@ -10,37 +11,40 @@ export const pb = new PocketBase(basePath)
export const $authenticated = atom(pb.authStore.isValid) export const $authenticated = atom(pb.authStore.isValid)
/** List of system records */ /** List of system records */
export const $systems = atom([] as SystemRecord[]) export const $systems = atom<SystemRecord[]>([])
/** List of alert records */ /** Map of alert records by system id and alert name */
export const $alerts = atom([] as AlertRecord[]) export const $alerts = map<AlertMap>({})
/** SSH public key */ /** SSH public key */
export const $publicKey = atom("") export const $publicKey = atom("")
/** Chart time period */ /** Chart time period */
export const $chartTime = atom("1h") as PreinitializedWritableAtom<ChartTimes> export const $chartTime = atom<ChartTimes>("1h")
/** Whether to display average or max chart values */ /** Whether to display average or max chart values */
export const $maxValues = atom(false) export const $maxValues = atom(false)
// export const UserSettingsSchema = v.object({
// chartTime: v.picklist(["1h", "12h", "24h", "1w", "30d"]),
// emails: v.optional(v.array(v.pipe(v.string(), v.email())), [pb?.authStore?.record?.email ?? ""]),
// webhooks: v.optional(v.array(v.string())),
// colorWarn: v.optional(v.pipe(v.number(), v.minValue(1), v.maxValue(100))),
// colorDanger: v.optional(v.pipe(v.number(), v.minValue(1), v.maxValue(100))),
// unitTemp: v.optional(v.enum(Unit)),
// unitNet: v.optional(v.enum(Unit)),
// unitDisk: v.optional(v.enum(Unit)),
// })
/** User settings */ /** User settings */
export const $userSettings = map<UserSettings>({ export const $userSettings = map<UserSettings>({
chartTime: "1h", chartTime: "1h",
emails: [pb.authStore.record?.email || ""], emails: [pb.authStore.record?.email || ""],
meterThresholds: { unitNet: Unit.Bytes,
yellow: 65, unitTemp: Unit.Celsius,
red: 90,
},
// unitTemp: "celsius",
// unitNet: "mbps",
// unitDisk: "mbps",
})
// update local storage on change
$userSettings.subscribe((value) => {
// console.log('user settings changed', value)
$chartTime.set(value.chartTime)
}) })
// update chart time on change
$userSettings.subscribe((value) => $chartTime.set(value.chartTime))
/** Container chart filter */ /** Container chart filter */
export const $containerFilter = atom("") export const $containerFilter = atom("")

View File

@@ -84,7 +84,7 @@ export function useIntersectionObserver({
entry: undefined, entry: undefined,
})) }))
const callbackRef = useRef<UseIntersectionObserverOptions["onChange"]>() const callbackRef = useRef<UseIntersectionObserverOptions["onChange"]>(undefined)
callbackRef.current = onChange callbackRef.current = onChange

View File

@@ -3,24 +3,13 @@ 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 { $alerts, $copyContent, $systems, $userSettings, pb } from "./stores"
import { import type { ChartTimeData, ChartTimes, FingerprintRecord, SemVer, SystemRecord, UserSettings } from "@/types"
AlertInfo,
AlertRecord,
ChartTimeData,
ChartTimes,
FingerprintRecord,
SemVer,
SystemRecord,
UserSettings,
} from "@/types"
import { RecordModel, RecordSubscription } from "pocketbase" import { RecordModel, RecordSubscription } from "pocketbase"
import { WritableAtom } from "nanostores" import { WritableAtom } from "nanostores"
import { timeDay, timeHour } from "d3-time" import { timeDay, timeHour } from "d3-time"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import { CpuIcon, HardDriveIcon, MemoryStickIcon, ServerIcon } from "lucide-react"
import { EthernetIcon, HourglassIcon, ThermometerIcon } from "@/components/ui/icons"
import { prependBasePath } from "@/components/router" import { prependBasePath } from "@/components/router"
import { Unit } from "./enums" import { MeterState, Unit } from "./enums"
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)) return twMerge(clsx(inputs))
@@ -84,21 +73,13 @@ export const updateSystemList = (() => {
/** Logs the user out by clearing the auth store and unsubscribing from realtime updates. */ /** Logs the user out by clearing the auth store and unsubscribing from realtime updates. */
export async function logOut() { export async function logOut() {
$systems.set([]) $systems.set([])
$alerts.set([]) $alerts.set({})
$userSettings.set({} as UserSettings) $userSettings.set({} as UserSettings)
sessionStorage.setItem("lo", "t") // prevent auto login on logout sessionStorage.setItem("lo", "t") // prevent auto login on logout
pb.authStore.clear() pb.authStore.clear()
pb.realtime.unsubscribe() pb.realtime.unsubscribe()
} }
export const updateAlerts = () => {
pb.collection("alerts")
.getFullList<AlertRecord>({ fields: "id,name,system,value,min,triggered", sort: "updated" })
.then((records) => {
$alerts.set(records)
})
}
const hourWithMinutesFormatter = new Intl.DateTimeFormat(undefined, { const hourWithMinutesFormatter = new Intl.DateTimeFormat(undefined, {
hour: "numeric", hour: "numeric",
minute: "numeric", minute: "numeric",
@@ -368,79 +349,6 @@ export async function updateUserSettings() {
export const chartMargin = { top: 12 } export const chartMargin = { top: 12 }
/** Alert info for each alert type */
export const alertInfo: Record<string, AlertInfo> = {
Status: {
name: () => t`Status`,
unit: "",
icon: ServerIcon,
desc: () => t`Triggers when status switches between up and down`,
/** "for x minutes" is appended to desc when only one value */
singleDesc: () => t`System` + " " + t`Down`,
},
CPU: {
name: () => t`CPU Usage`,
unit: "%",
icon: CpuIcon,
desc: () => t`Triggers when CPU usage exceeds a threshold`,
},
Memory: {
name: () => t`Memory Usage`,
unit: "%",
icon: MemoryStickIcon,
desc: () => t`Triggers when memory usage exceeds a threshold`,
},
Disk: {
name: () => t`Disk Usage`,
unit: "%",
icon: HardDriveIcon,
desc: () => t`Triggers when usage of any disk exceeds a threshold`,
},
Bandwidth: {
name: () => t`Bandwidth`,
unit: " MB/s",
icon: EthernetIcon,
desc: () => t`Triggers when combined up/down exceeds a threshold`,
max: 125,
},
Temperature: {
name: () => t`Temperature`,
unit: "°C",
icon: ThermometerIcon,
desc: () => t`Triggers when any sensor exceeds a threshold`,
},
LoadAvg1: {
name: () => t`Load Average 1m`,
unit: "",
icon: HourglassIcon,
max: 100,
min: 0.1,
start: 10,
step: 0.1,
desc: () => t`Triggers when 1 minute load average exceeds a threshold`,
},
LoadAvg5: {
name: () => t`Load Average 5m`,
unit: "",
icon: HourglassIcon,
max: 100,
min: 0.1,
start: 10,
step: 0.1,
desc: () => t`Triggers when 5 minute load average exceeds a threshold`,
},
LoadAvg15: {
name: () => t`Load Average 15m`,
unit: "",
icon: HourglassIcon,
min: 0.1,
max: 100,
start: 10,
step: 0.1,
desc: () => t`Triggers when 15 minute load average exceeds a threshold`,
},
}
/** /**
* Retuns value of system host, truncating full path if socket. * Retuns value of system host, truncating full path if socket.
* @example * @example
@@ -450,7 +358,13 @@ export const alertInfo: Record<string, AlertInfo> = {
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)
/** Generate a random token for the agent */ /** Generate a random token for the agent */
export const generateToken = () => crypto?.randomUUID() ?? (performance.now() * Math.random()).toString(16) export const generateToken = () => {
try {
return crypto?.randomUUID()
} catch (e) {
return Array.from({ length: 2 }, () => (performance.now() * Math.random()).toString(16).replace(".", "-")).join("-")
}
}
/** Get the hub URL from the global BESZEL object */ /** Get the hub URL from the global BESZEL object */
export const getHubURL = () => BESZEL?.HUB_URL || window.location.origin export const getHubURL = () => BESZEL?.HUB_URL || window.location.origin
@@ -507,3 +421,30 @@ export const parseSemVer = (semVer = ""): SemVer => {
const parts = semVer.split(".").map(Number) const parts = semVer.split(".").map(Number)
return { major: parts?.[0] ?? 0, minor: parts?.[1] ?? 0, patch: parts?.[2] ?? 0 } return { major: parts?.[0] ?? 0, minor: parts?.[1] ?? 0, patch: parts?.[2] ?? 0 }
} }
/** Get meter state from 0-100 value. Used for color coding meters. */
export function getMeterState(value: number): MeterState {
const { colorWarn = 65, colorCrit = 90 } = $userSettings.get()
return value >= colorCrit ? MeterState.Crit : value >= colorWarn ? MeterState.Warn : MeterState.Good
}
export function debounce<T extends (...args: any[]) => any>(func: T, wait: number): (...args: Parameters<T>) => void {
let timeout: ReturnType<typeof setTimeout>
return (...args: Parameters<T>) => {
clearTimeout(timeout)
timeout = setTimeout(() => func(...args), wait)
}
}
/* returns the name of a system from its id */
export const getSystemNameFromId = (() => {
const cache = new Map<string, string>()
return (systemId: string): string => {
if (cache.has(systemId)) {
return cache.get(systemId)!
}
const sysName = $systems.get().find((s) => s.id === systemId)?.name ?? ""
cache.set(systemId, sysName)
return sysName
}
})()

View File

@@ -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-07-25 22:44\n" "PO-Revision-Date: 2025-08-25 01:15\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"
@@ -69,8 +69,8 @@ msgid "5 min"
msgstr "5 دقائق" msgstr "5 دقائق"
#. Table column #. Table column
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions" msgid "Actions"
msgstr "إجراءات" msgstr "إجراءات"
@@ -108,27 +108,27 @@ msgstr "تعديل خيارات العرض للرسوم البيانية."
msgid "Admin" msgid "Admin"
msgstr "مسؤول" msgstr "مسؤول"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "وكيل" msgstr "وكيل"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/layout.tsx
msgid "Alert History" msgid "Alert History"
msgstr "سجل التنبيهات" msgstr "سجل التنبيهات"
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alert-button.tsx
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Alerts" msgid "Alerts"
msgstr "التنبيهات" msgstr "التنبيهات"
#: src/components/alerts/alerts-sheet.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/alerts/alert-button.tsx
msgid "All Systems" msgid "All Systems"
msgstr "جميع الأنظمة" msgstr "جميع الأنظمة"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Are you sure you want to delete {name}?" msgid "Are you sure you want to delete {name}?"
msgstr "هل أنت متأكد أنك تريد حذف {name}؟" msgstr "هل أنت متأكد أنك تريد حذف {name}؟"
@@ -148,8 +148,8 @@ msgstr "متوسط"
msgid "Average CPU utilization of containers" msgid "Average CPU utilization of containers"
msgstr "متوسط استخدام وحدة المعالجة المركزية للحاويات" msgstr "متوسط استخدام وحدة المعالجة المركزية للحاويات"
#. placeholder {0}: data.alert.unit #. placeholder {0}: alertData.unit
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Average exceeds <0>{value}{0}</0>" msgid "Average exceeds <0>{value}{0}</0>"
msgstr "المتوسط يتجاوز <0>{value}{0}</0>" msgstr "المتوسط يتجاوز <0>{value}{0}</0>"
@@ -166,16 +166,20 @@ msgstr "متوسط استخدام وحدة المعالجة المركزية ع
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "متوسط ​​استخدام {0}" msgstr "متوسط ​​استخدام {0}"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
msgstr "النسخ الاحتياطية" msgstr "النسخ الاحتياطية"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Bandwidth" msgid "Bandwidth"
msgstr "عرض النطاق الترددي" msgstr "عرض النطاق الترددي"
#: src/components/routes/system.tsx
msgid "Battery"
msgstr "البطارية"
#: 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."
msgstr "يدعم بيزيل بروتوكول OpenID Connect والعديد من مزوّدي المصادقة عبر بروتوكول OAuth2." msgstr "يدعم بيزيل بروتوكول OpenID Connect والعديد من مزوّدي المصادقة عبر بروتوكول OAuth2."
@@ -202,8 +206,8 @@ msgstr "بايت (كيلوبايت/ثانية، ميجابايت/ثانية، ج
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "ذاكرة التخزين المؤقت / المخازن المؤقتة" msgstr "ذاكرة التخزين المؤقت / المخازن المؤقتة"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Cancel" msgid "Cancel"
msgstr "إلغاء" msgstr "إلغاء"
@@ -223,6 +227,15 @@ msgstr "تغيير وحدات عرض المقاييس."
msgid "Change general application options." msgid "Change general application options."
msgstr "تغيير خيارات التطبيق العامة." msgstr "تغيير خيارات التطبيق العامة."
#: src/components/routes/system.tsx
msgid "Charge"
msgstr "الشحن"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Charging"
msgstr "قيد الشحن"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Chart options" msgid "Chart options"
msgstr "خيارات الرسم البياني" msgstr "خيارات الرسم البياني"
@@ -261,8 +274,8 @@ msgstr "تأكيد كلمة المرور"
msgid "Connection is down" msgid "Connection is down"
msgstr "الاتصال مقطوع" msgstr "الاتصال مقطوع"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Continue" msgid "Continue"
msgstr "متابعة" msgstr "متابعة"
@@ -287,7 +300,7 @@ msgctxt "Environment variables"
msgid "Copy env" msgid "Copy env"
msgstr "نسخ متغيرات البيئة" msgstr "نسخ متغيرات البيئة"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy host" msgid "Copy host"
msgstr "نسخ المضيف" msgstr "نسخ المضيف"
@@ -296,7 +309,7 @@ msgstr "نسخ المضيف"
msgid "Copy Linux command" msgid "Copy Linux command"
msgstr "نسخ أمر لينكس" msgstr "نسخ أمر لينكس"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy name" msgid "Copy name"
msgstr "نسخ الاسم" msgstr "نسخ الاسم"
@@ -316,13 +329,13 @@ msgstr "انسخ محتوى <0>docker-compose.yml</0> للوكيل أدناه،
msgid "Copy YAML" msgid "Copy YAML"
msgstr "نسخ YAML" msgstr "نسخ YAML"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "المعالج" msgstr "المعالج"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "استخدام وحدة المعالجة المركزية" msgstr "استخدام وحدة المعالجة المركزية"
@@ -335,10 +348,14 @@ msgstr "إنشاء حساب"
msgid "Created" msgid "Created"
msgstr "أنشئت" msgstr "أنشئت"
#. Dark theme #: src/components/routes/settings/general.tsx
#: src/components/mode-toggle.tsx msgid "Critical (%)"
msgid "Dark" msgstr "حرج (%)"
msgstr "داكن"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
msgstr "الحالة الحالية"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/home.tsx #: src/components/routes/home.tsx
@@ -349,8 +366,8 @@ msgstr "لوحة التحكم"
msgid "Default time period" msgid "Default time period"
msgstr "الفترة الزمنية الافتراضية" msgstr "الفترة الزمنية الافتراضية"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Delete" msgid "Delete"
msgstr "حذف" msgstr "حذف"
@@ -358,7 +375,12 @@ msgstr "حذف"
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "حذف البصمة" msgstr "حذف البصمة"
#: src/components/systems-table/systems-table.tsx #. Context: Battery state
#: src/lib/i18n.ts
msgid "Discharging"
msgstr "قيد التفريغ"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk" msgid "Disk"
msgstr "القرص" msgstr "القرص"
@@ -370,9 +392,9 @@ msgstr "إدخال/إخراج القرص"
msgid "Disk unit" msgid "Disk unit"
msgstr "وحدة القرص" msgstr "وحدة القرص"
#: src/lib/utils.ts
#: src/components/routes/system.tsx
#: src/components/charts/disk-chart.tsx #: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Disk Usage" msgid "Disk Usage"
msgstr "استخدام القرص" msgstr "استخدام القرص"
@@ -397,10 +419,10 @@ msgid "Documentation"
msgstr "التوثيق" msgstr "التوثيق"
#. Context: System is down #. Context: System is down
#: src/lib/utils.ts
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "Down" msgid "Down"
msgstr "معطل" msgstr "معطل"
@@ -409,12 +431,12 @@ msgid "Duration"
msgstr "المدة" msgstr "المدة"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Edit" msgid "Edit"
msgstr "تعديل" msgstr "تعديل"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Email" msgid "Email"
msgstr "البريد الإشباكي" msgstr "البريد الإشباكي"
@@ -422,6 +444,11 @@ msgstr "البريد الإشباكي"
msgid "Email notifications" msgid "Email notifications"
msgstr "إشعارات البريد الإشباكي" msgstr "إشعارات البريد الإشباكي"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Empty"
msgstr "فارغة"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Enter email address to reset password" msgid "Enter email address to reset password"
msgstr "أدخل عنوان البريد الإشباكي لإعادة تعيين كلمة المرور" msgstr "أدخل عنوان البريد الإشباكي لإعادة تعيين كلمة المرور"
@@ -430,11 +457,11 @@ msgstr "أدخل عنوان البريد الإشباكي لإعادة تعيي
msgid "Enter email address..." msgid "Enter email address..."
msgstr "أدخل عنوان البريد الإشباكي..." msgstr "أدخل عنوان البريد الإشباكي..."
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Error" msgid "Error"
msgstr "خطأ" msgstr "خطأ"
@@ -469,8 +496,8 @@ msgstr "فهرنهايت (°ف)"
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "فشل في المصادقة" msgstr "فشل في المصادقة"
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Failed to save settings" msgid "Failed to save settings"
msgstr "فشل في حفظ الإعدادات" msgstr "فشل في حفظ الإعدادات"
@@ -478,13 +505,13 @@ msgstr "فشل في حفظ الإعدادات"
msgid "Failed to send test notification" msgid "Failed to send test notification"
msgstr "فشل في إرسال إشعار الاختبار" msgstr "فشل في إرسال إشعار الاختبار"
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Failed to update alert" msgid "Failed to update alert"
msgstr "فشل في تحديث التنبيه" msgstr "فشل في تحديث التنبيه"
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "تصفية..." msgstr "تصفية..."
@@ -492,7 +519,7 @@ msgstr "تصفية..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "البصمة" msgstr "البصمة"
#: src/components/alerts/alerts-system.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}}"
msgstr "لمدة <0>{min}</0> {min, plural, one {دقيقة} other {دقائق}}" msgstr "لمدة <0>{min}</0> {min, plural, one {دقيقة} other {دقائق}}"
@@ -500,9 +527,14 @@ msgstr "لمدة <0>{min}</0> {min, plural, one {دقيقة} other {دقائق}}
msgid "Forgot password?" msgid "Forgot password?"
msgstr "هل نسيت كلمة المرور؟" msgstr "هل نسيت كلمة المرور؟"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
msgstr "ممتلئة"
#. Context: General settings #. Context: General settings
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/layout.tsx
msgid "General" msgid "General"
msgstr "عام" msgstr "عام"
@@ -524,6 +556,11 @@ msgstr "أمر Homebrew"
msgid "Host / IP" msgid "Host / IP"
msgstr "مضيف / IP" msgstr "مضيف / IP"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Idle"
msgstr "خاملة"
#: 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."
msgstr "إذا فقدت كلمة المرور لحساب المسؤول الخاص بك، يمكنك إعادة تعيينها باستخدام الأمر التالي." msgstr "إذا فقدت كلمة المرور لحساب المسؤول الخاص بك، يمكنك إعادة تعيينها باستخدام الأمر التالي."
@@ -545,29 +582,24 @@ msgstr "اللغة"
msgid "Layout" msgid "Layout"
msgstr "التخطيط" msgstr "التخطيط"
#. Light theme
#: src/components/mode-toggle.tsx
msgid "Light"
msgstr "فاتح"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "متوسط التحميل" msgstr "متوسط التحميل"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 15m" msgid "Load Average 15m"
msgstr "متوسط التحميل 15 دقيقة" msgstr "متوسط التحميل 15 دقيقة"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 1m" msgid "Load Average 1m"
msgstr "متوسط التحميل 1 دقيقة" msgstr "متوسط التحميل 1 دقيقة"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 5m" msgid "Load Average 5m"
msgstr "متوسط التحميل 5 دقائق" msgstr "متوسط التحميل 5 دقائق"
#. Short label for load average #. Short label for load average
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Load Avg" msgid "Load Avg"
msgstr "متوسط التحميل" msgstr "متوسط التحميل"
@@ -579,13 +611,13 @@ msgstr "تسجيل الخروج"
msgid "Login" msgid "Login"
msgstr "تسجيل الدخول" msgstr "تسجيل الدخول"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Login attempt failed" msgid "Login attempt failed"
msgstr "فشل محاولة تسجيل الدخول" msgstr "فشل محاولة تسجيل الدخول"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Logs" msgid "Logs"
msgstr "السجلات" msgstr "السجلات"
@@ -606,12 +638,12 @@ msgstr "تعليمات الإعداد اليدوي"
msgid "Max 1 min" msgid "Max 1 min"
msgstr "الحد الأقصى دقيقة" msgstr "الحد الأقصى دقيقة"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Memory" msgid "Memory"
msgstr "الذاكرة" msgstr "الذاكرة"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
msgstr "استخدام الذاكرة" msgstr "استخدام الذاكرة"
@@ -619,12 +651,12 @@ msgstr "استخدام الذاكرة"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "استخدام الذاكرة لحاويات دوكر" msgstr "استخدام الذاكرة لحاويات دوكر"
#: src/components/alerts-history-columns.tsx
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
msgid "Name" msgid "Name"
msgstr "الاسم" msgstr "الاسم"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Net" msgid "Net"
msgstr "الشبكة" msgstr "الشبكة"
@@ -655,8 +687,8 @@ msgid "No systems found."
msgstr "لم يتم العثور على أنظمة." msgstr "لم يتم العثور على أنظمة."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Notifications" msgid "Notifications"
msgstr "الإشعارات" msgstr "الإشعارات"
@@ -668,9 +700,9 @@ msgstr "دعم OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "في كل إعادة تشغيل، سيتم تحديث الأنظمة في قاعدة البيانات لتتطابق مع الأنظمة المعرفة في الملف." msgstr "في كل إعادة تشغيل، سيتم تحديث الأنظمة في قاعدة البيانات لتتطابق مع الأنظمة المعرفة في الملف."
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu" msgid "Open menu"
msgstr "فتح القائمة" msgstr "فتح القائمة"
@@ -678,7 +710,7 @@ msgstr "فتح القائمة"
msgid "Or continue with" msgid "Or continue with"
msgstr "أو المتابعة باستخدام" msgstr "أو المتابعة باستخدام"
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "الكتابة فوق التنبيهات الحالية" msgstr "الكتابة فوق التنبيهات الحالية"
@@ -713,11 +745,11 @@ msgstr "يجب أن تكون كلمة المرور أقل من 72 بايت."
msgid "Password reset request received" msgid "Password reset request received"
msgstr "تم استلام طلب إعادة تعيين كلمة المرور" msgstr "تم استلام طلب إعادة تعيين كلمة المرور"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Pause" msgid "Pause"
msgstr "إيقاف مؤقت" msgstr "إيقاف مؤقت"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Paused" msgid "Paused"
msgstr "متوقف مؤقتا" msgstr "متوقف مؤقتا"
@@ -725,12 +757,12 @@ msgstr "متوقف مؤقتا"
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> لضمان تسليم التنبيهات."
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Please check logs for more details." msgid "Please check logs for more details."
msgstr "يرجى التحقق من السجلات لمزيد من التفاصيل." msgstr "يرجى التحقق من السجلات لمزيد من التفاصيل."
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Please check your credentials and try again" msgid "Please check your credentials and try again"
msgstr "يرجى التحقق من بيانات الاعتماد الخاصة بك والمحاولة مرة أخرى" msgstr "يرجى التحقق من بيانات الاعتماد الخاصة بك والمحاولة مرة أخرى"
@@ -792,7 +824,7 @@ msgstr "إعادة تعيين كلمة المرور"
msgid "Resolved" msgid "Resolved"
msgstr "تم حلها" msgstr "تم حلها"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
msgstr "استئناف" msgstr "استئناف"
@@ -808,8 +840,8 @@ msgstr "صفوف لكل صفحة"
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."
msgstr "احفظ العنوان باستخدام مفتاح الإدخال أو الفاصلة. اتركه فارغًا لتعطيل إشعارات البريد الإشباكي." msgstr "احفظ العنوان باستخدام مفتاح الإدخال أو الفاصلة. اتركه فارغًا لتعطيل إشعارات البريد الإشباكي."
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Save Settings" msgid "Save Settings"
msgstr "حفظ الإعدادات" msgstr "حفظ الإعدادات"
@@ -825,7 +857,7 @@ msgstr "بحث"
msgid "Search for systems or settings..." msgid "Search for systems or settings..."
msgstr "البحث عن الأنظمة أو الإعدادات..." msgstr "البحث عن الأنظمة أو الإعدادات..."
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "See <0>notification settings</0> to configure how you receive alerts." msgid "See <0>notification settings</0> to configure how you receive alerts."
msgstr "راجع <0>إعدادات الإشعارات</0> لتكوين كيفية تلقي التنبيهات." msgstr "راجع <0>إعدادات الإشعارات</0> لتكوين كيفية تلقي التنبيهات."
@@ -833,6 +865,10 @@ msgstr "راجع <0>إعدادات الإشعارات</0> لتكوين كيفي
msgid "Sent" msgid "Sent"
msgstr "تم الإرسال" msgstr "تم الإرسال"
#: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors."
msgstr "تعيين عتبات النسبة المئوية لألوان العداد."
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed." msgid "Sets the default time range for charts when a system is viewed."
msgstr "يحدد النطاق الزمني الافتراضي للرسوم البيانية عند عرض النظام." msgstr "يحدد النطاق الزمني الافتراضي للرسوم البيانية عند عرض النظام."
@@ -865,7 +901,7 @@ msgstr "الترتيب حسب"
msgid "State" msgid "State"
msgstr "الحالة" msgstr "الحالة"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "الحالة" msgstr "الحالة"
@@ -877,12 +913,10 @@ msgstr "مساحة التبديل المستخدمة من قبل النظام"
msgid "Swap Usage" msgid "Swap Usage"
msgstr "استخدام التبديل" msgstr "استخدام التبديل"
#. System theme
#: src/lib/utils.ts
#: src/components/mode-toggle.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "System" msgid "System"
msgstr "النظام" msgstr "النظام"
@@ -903,12 +937,12 @@ msgid "Table"
msgstr "جدول" msgstr "جدول"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "درجة الحرارة" msgstr "درجة الحرارة"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Temperature" msgid "Temperature"
msgstr "درجة الحرارة" msgstr "درجة الحرارة"
@@ -932,7 +966,7 @@ msgstr "تم إرسال إشعار الاختبار"
msgid "Then log into the backend and reset your user account password in the users table." msgid "Then log into the backend and reset your user account password in the users table."
msgstr "ثم قم بتسجيل الدخول إلى الواجهة الخلفية وأعد تعيين كلمة مرور حساب المستخدم الخاص بك في جدول المستخدمين." msgstr "ثم قم بتسجيل الدخول إلى الواجهة الخلفية وأعد تعيين كلمة مرور حساب المستخدم الخاص بك في جدول المستخدمين."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database." msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database."
msgstr "لا يمكن التراجع عن هذا الإجراء. سيؤدي ذلك إلى حذف جميع السجلات الحالية لـ {name} من قاعدة البيانات بشكل دائم." msgstr "لا يمكن التراجع عن هذا الإجراء. سيؤدي ذلك إلى حذف جميع السجلات الحالية لـ {name} من قاعدة البيانات بشكل دائم."
@@ -967,8 +1001,8 @@ msgid "Token"
msgstr "رمز مميز" msgstr "رمز مميز"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens & Fingerprints" msgid "Tokens & Fingerprints"
msgstr "الرموز المميزة والبصمات" msgstr "الرموز المميزة والبصمات"
@@ -980,39 +1014,39 @@ msgstr "تسمح الرموز المميزة للوكلاء بالاتصال و
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "تُستخدم الرموز المميزة والبصمات للمصادقة على اتصالات WebSocket إلى المحور." msgstr "تُستخدم الرموز المميزة والبصمات للمصادقة على اتصالات WebSocket إلى المحور."
#: src/lib/utils.ts #: 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 "يتم التفعيل عندما يتجاوز متوسط التحميل لمدة دقيقة واحدة عتبة معينة"
#: src/lib/utils.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 "يتم التفعيل عندما يتجاوز متوسط التحميل لمدة 15 دقيقة عتبة معينة" msgstr "يتم التفعيل عندما يتجاوز متوسط التحميل لمدة 15 دقيقة عتبة معينة"
#: src/lib/utils.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 "يتم التفعيل عندما يتجاوز متوسط التحميل لمدة 5 دقائق عتبة معينة" msgstr "يتم التفعيل عندما يتجاوز متوسط التحميل لمدة 5 دقائق عتبة معينة"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "يتم التفعيل عندما <20><>تجاوز أي مستشعر عتبة معينة" msgstr "يتم التفعيل عندما <20><>تجاوز أي مستشعر عتبة معينة"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when combined up/down exceeds a threshold" msgid "Triggers when combined up/down exceeds a threshold"
msgstr "يتم التفعيل عندما يتجاوز الجمع بين الصعود/الهبوط عتبة معينة" msgstr "يتم التفعيل عندما يتجاوز الجمع بين الصعود/الهبوط عتبة معينة"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
msgstr "يتم التفعيل عندما يتجاوز استخدام وحدة المعالجة المركزية عتبة معينة" msgstr "يتم التفعيل عندما يتجاوز استخدام وحدة المعالجة المركزية عتبة معينة"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
msgstr "يتم التفعيل عندما يتجاوز استخدام الذاكرة عتبة معينة" msgstr "يتم التفعيل عندما يتجاوز استخدام الذاكرة عتبة معينة"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when status switches between up and down" msgid "Triggers when status switches between up and down"
msgstr "يتم التفعيل عندما يتغير الحالة بين التشغيل والإيقاف" msgstr "يتم التفعيل عندما يتغير الحالة بين التشغيل والإيقاف"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "يتم التفعيل عندما يتجاوز استخدام أي قرص عتبة معينة" msgstr "يتم التفعيل عندما يتجاوز استخدام أي قرص عتبة معينة"
@@ -1025,9 +1059,14 @@ msgstr "تفضيلات الوحدة"
msgid "Universal token" msgid "Universal token"
msgstr "رمز مميز عالمي" msgstr "رمز مميز عالمي"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "غير معروفة"
#. Context: System is up #. Context: System is up
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Up" msgid "Up"
msgstr "قيد التشغيل" msgstr "قيد التشغيل"
@@ -1050,13 +1089,13 @@ msgstr "الاستخدام"
msgid "Usage of root partition" msgid "Usage of root partition"
msgstr "استخدام القسم الجذر" msgstr "استخدام القسم الجذر"
#: src/components/charts/swap-chart.tsx
#: src/components/charts/mem-chart.tsx #: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
msgid "Used" msgid "Used"
msgstr "مستخدم" msgstr "مستخدم"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Users" msgid "Users"
msgstr "المستخدمون" msgstr "المستخدمون"
@@ -1084,6 +1123,14 @@ msgstr "في انتظار وجود سجلات كافية للعرض"
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "هل تريد مساعدتنا في تحسين ترجماتنا؟ تحقق من <0>Crowdin</0> لمزيد من التفاصيل." msgstr "هل تريد مساعدتنا في تحسين ترجماتنا؟ تحقق من <0>Crowdin</0> لمزيد من التفاصيل."
#: src/components/routes/settings/general.tsx
msgid "Warning (%)"
msgstr "تحذير (%)"
#: src/components/routes/settings/general.tsx
msgid "Warning thresholds"
msgstr "عتبات التحذير"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Webhook / Push notifications" msgid "Webhook / Push notifications"
msgstr "إشعارات Webhook / Push" msgstr "إشعارات Webhook / Push"

View File

@@ -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-07-25 22:44\n" "PO-Revision-Date: 2025-08-25 01:16\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"
@@ -27,7 +27,7 @@ msgstr "{0, plural, one {# ден} other {# дни}}"
#. 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} от {1} селектирани."
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "{hours, plural, one {# hour} other {# hours}}" msgid "{hours, plural, one {# hour} other {# hours}}"
@@ -40,7 +40,7 @@ msgstr "1 час"
#. 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 минута"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 week" msgid "1 week"
@@ -53,7 +53,7 @@ msgstr "12 часа"
#. 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 минути"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "24 hours" msgid "24 hours"
@@ -66,18 +66,18 @@ msgstr "30 дни"
#. 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 минути"
#. Table column #. Table column
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions" msgid "Actions"
msgstr "Действия" msgstr "Действия"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Active" msgid "Active"
msgstr "" msgstr "Активен"
#: src/components/routes/home.tsx #: src/components/routes/home.tsx
msgid "Active Alerts" msgid "Active Alerts"
@@ -108,33 +108,33 @@ msgstr "Настрой опциите за показване на диагра
msgid "Admin" msgid "Admin"
msgstr "Администратор" msgstr "Администратор"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "Агент" msgstr "Агент"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/layout.tsx
msgid "Alert History" msgid "Alert History"
msgstr "" msgstr "История на нотификациите"
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alert-button.tsx
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Alerts" msgid "Alerts"
msgstr "Тревоги" msgstr "Тревоги"
#: src/components/alerts/alerts-sheet.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/alerts/alert-button.tsx
msgid "All Systems" msgid "All Systems"
msgstr "Всички системи" msgstr "Всички системи"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Are you sure you want to delete {name}?" msgid "Are you sure you want to delete {name}?"
msgstr "Сигурен ли си, че искаш да изтриеш {name}?" msgstr "Сигурен ли си, че искаш да изтриеш {name}?"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Are you sure?" msgid "Are you sure?"
msgstr "" msgstr "Сигурни ли сте?"
#: src/components/copy-to-clipboard.tsx #: src/components/copy-to-clipboard.tsx
msgid "Automatic copy requires a secure context." msgid "Automatic copy requires a secure context."
@@ -148,8 +148,8 @@ msgstr "Средно"
msgid "Average CPU utilization of containers" msgid "Average CPU utilization of containers"
msgstr "Средно използване на процесора на контейнерите" msgstr "Средно използване на процесора на контейнерите"
#. placeholder {0}: data.alert.unit #. placeholder {0}: alertData.unit
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Average exceeds <0>{value}{0}</0>" msgid "Average exceeds <0>{value}{0}</0>"
msgstr "Средната стойност надхвърля <0>{value}{0}</0>" msgstr "Средната стойност надхвърля <0>{value}{0}</0>"
@@ -166,16 +166,20 @@ msgstr "Средно използване на процесора на цяла
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Средно използване на {0}" msgstr "Средно използване на {0}"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
msgstr "Архиви" msgstr "Архиви"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Bandwidth" msgid "Bandwidth"
msgstr "Bandwidth на мрежата" msgstr "Bandwidth на мрежата"
#: src/components/routes/system.tsx
msgid "Battery"
msgstr "Батерия"
#: 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."
msgstr "Beszel поддържа OpenID Connect и много други OAuth2 доставчици за удостоверяване." msgstr "Beszel поддържа OpenID Connect и много други OAuth2 доставчици за удостоверяване."
@@ -191,19 +195,19 @@ msgstr "Двоичен код"
#: 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 "Бита (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 "Байта (KB/s, MB/s, GB/s)"
#: src/components/charts/mem-chart.tsx #: src/components/charts/mem-chart.tsx
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "Кеш / Буфери" msgstr "Кеш / Буфери"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Cancel" msgid "Cancel"
msgstr "Откажи" msgstr "Откажи"
@@ -213,16 +217,25 @@ msgstr "Внимание - възможност за загуба на данн
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Celsius (°C)" msgid "Celsius (°C)"
msgstr "" msgstr "Целзий (°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 "Промяна на единиците за показване на метриките."
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Change general application options." msgid "Change general application options."
msgstr "Смени общите опции на приложението." msgstr "Смени общите опции на приложението."
#: src/components/routes/system.tsx
msgid "Charge"
msgstr "Заряд"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Charging"
msgstr "Зареждане"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Chart options" msgid "Chart options"
msgstr "Опции на диаграмата" msgstr "Опции на диаграмата"
@@ -259,10 +272,10 @@ msgstr "Потвърди парола"
#: src/components/routes/home.tsx #: src/components/routes/home.tsx
msgid "Connection is down" msgid "Connection is down"
msgstr "" msgstr "Връзката е прекъсната"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Continue" msgid "Continue"
msgstr "Продължи" msgstr "Продължи"
@@ -285,9 +298,9 @@ msgstr "Копирай docker run"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Environment variables" msgctxt "Environment variables"
msgid "Copy env" msgid "Copy env"
msgstr "" msgstr "Копирай еnv"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy host" msgid "Copy host"
msgstr "Копирай хоста" msgstr "Копирай хоста"
@@ -296,7 +309,7 @@ msgstr "Копирай хоста"
msgid "Copy Linux command" msgid "Copy Linux command"
msgstr "Копирай linux командата" msgstr "Копирай linux командата"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy name" msgid "Copy name"
msgstr "Копирай име" msgstr "Копирай име"
@@ -306,23 +319,23 @@ msgstr "Копирай текста"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Copy the installation command for the agent below, or register agents automatically with a <0>universal token</0>." msgid "Copy the installation command for the agent below, or register agents automatically with a <0>universal token</0>."
msgstr "" msgstr "Копирайте командата за инсталиране на агента по-долу или регистрирайте агентите автоматично с <0>универсален токен</0>."
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Copy the<0>docker-compose.yml</0> content for the agent below, or register agents automatically with a <1>universal token</1>." msgid "Copy the<0>docker-compose.yml</0> content for the agent below, or register agents automatically with a <1>universal token</1>."
msgstr "" msgstr "Копирайте съдържанието на<0>docker-compose.yml</0> за агента по-долу или регистрирайте агентите автоматично с <1>универсален токен</1>."
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy YAML" msgid "Copy YAML"
msgstr "" msgstr "Копирай YAML"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "Процесор" msgstr "Процесор"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "Употреба на процесор" msgstr "Употреба на процесор"
@@ -333,12 +346,16 @@ msgstr "Създай акаунт"
#. Context: date created #. Context: date created
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Created" msgid "Created"
msgstr "" msgstr "Създаден"
#. Dark theme #: src/components/routes/settings/general.tsx
#: src/components/mode-toggle.tsx msgid "Critical (%)"
msgid "Dark" msgstr "Критично (%)"
msgstr "Тъмно"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
msgstr "Текущо състояние"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/home.tsx #: src/components/routes/home.tsx
@@ -349,16 +366,21 @@ msgstr "Табло"
msgid "Default time period" msgid "Default time period"
msgstr "Времеви диапазон по подразбиране" msgstr "Времеви диапазон по подразбиране"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Delete" msgid "Delete"
msgstr "Изтрий" msgstr "Изтрий"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "" msgstr "Изтрий пръстов отпечатък"
#: src/components/systems-table/systems-table.tsx #. Context: Battery state
#: src/lib/i18n.ts
msgid "Discharging"
msgstr "Разреждане"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk" msgid "Disk"
msgstr "Диск" msgstr "Диск"
@@ -368,11 +390,11 @@ msgstr "Диск I/O"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Disk unit" msgid "Disk unit"
msgstr "" msgstr "Единица за диск"
#: src/lib/utils.ts
#: src/components/routes/system.tsx
#: src/components/charts/disk-chart.tsx #: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Disk Usage" msgid "Disk Usage"
msgstr "Използване на диск" msgstr "Използване на диск"
@@ -397,24 +419,24 @@ msgid "Documentation"
msgstr "Документация" msgstr "Документация"
#. Context: System is down #. Context: System is down
#: src/lib/utils.ts
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "Down" msgid "Down"
msgstr "" msgstr "Офлайн"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "" msgstr "Продължителност"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Edit" msgid "Edit"
msgstr "" msgstr "Редактирай"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Email" msgid "Email"
msgstr "Имейл" msgstr "Имейл"
@@ -422,6 +444,11 @@ msgstr "Имейл"
msgid "Email notifications" msgid "Email notifications"
msgstr "Имейл нотификации" msgstr "Имейл нотификации"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Empty"
msgstr "Празна"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Enter email address to reset password" msgid "Enter email address to reset password"
msgstr "Въведи имейл адрес за да нулираш паролата" msgstr "Въведи имейл адрес за да нулираш паролата"
@@ -430,11 +457,11 @@ msgstr "Въведи имейл адрес за да нулираш парола
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Въведи имейл адрес..." msgstr "Въведи имейл адрес..."
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Error" msgid "Error"
msgstr "Грешка" msgstr "Грешка"
@@ -451,7 +478,7 @@ msgstr "Съществуващи системи които не са дефин
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Export" msgid "Export"
msgstr "" msgstr "Експортиране"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Export configuration" msgid "Export configuration"
@@ -463,14 +490,14 @@ msgstr "Експортирай конфигурацията на системи
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "" msgstr "Фаренхайт (°F)"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Неуспешно удостоверяване" msgstr "Неуспешно удостоверяване"
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Failed to save settings" msgid "Failed to save settings"
msgstr "Неуспешно запазване на настройки" msgstr "Неуспешно запазване на настройки"
@@ -478,21 +505,21 @@ msgstr "Неуспешно запазване на настройки"
msgid "Failed to send test notification" msgid "Failed to send test notification"
msgstr "Неуспешно изпрати тестова нотификация" msgstr "Неуспешно изпрати тестова нотификация"
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Failed to update alert" msgid "Failed to update alert"
msgstr "Неуспешно обнови тревога" msgstr "Неуспешно обнови тревога"
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Филтрирай..." msgstr "Филтрирай..."
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Fingerprint" msgid "Fingerprint"
msgstr "" msgstr "Пръстов отпечатък"
#: src/components/alerts/alerts-system.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}}"
msgstr "За <0>{min}</0> {min, plural, one {минута} other {минути}}" msgstr "За <0>{min}</0> {min, plural, one {минута} other {минути}}"
@@ -500,9 +527,14 @@ msgstr "За <0>{min}</0> {min, plural, one {минута} other {минути}}
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Забравена парола?" msgstr "Забравена парола?"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
msgstr "Пълна"
#. Context: General settings #. Context: General settings
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/layout.tsx
msgid "General" msgid "General"
msgstr "Общо" msgstr "Общо"
@@ -524,6 +556,11 @@ msgstr "Команда Homebrew"
msgid "Host / IP" msgid "Host / IP"
msgstr "Хост / IP" msgstr "Хост / IP"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Idle"
msgstr "Неактивна"
#: 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."
msgstr "Ако си загубил паролата до администраторския акаунт, можеш да я нулираш със следващата команда." msgstr "Ако си загубил паролата до администраторския акаунт, можеш да я нулираш със следващата команда."
@@ -545,31 +582,26 @@ msgstr "Език"
msgid "Layout" msgid "Layout"
msgstr "Подреждане" msgstr "Подреждане"
#. Light theme
#: src/components/mode-toggle.tsx
msgid "Light"
msgstr "Светъл"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "" msgstr "Средно натоварване"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 15m" msgid "Load Average 15m"
msgstr "" msgstr "Средно натоварване 15 минути"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 1m" msgid "Load Average 1m"
msgstr "" msgstr "Средно натоварване 1 минута"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 5m" msgid "Load Average 5m"
msgstr "" msgstr "Средно натоварване 5 минути"
#. Short label for load average #. Short label for load average
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Load Avg" msgid "Load Avg"
msgstr "" msgstr "Средно натоварване"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
@@ -579,13 +611,13 @@ msgstr "Изход"
msgid "Login" msgid "Login"
msgstr "Вход" msgstr "Вход"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Login attempt failed" msgid "Login attempt failed"
msgstr "Неуспешен опит за вход" msgstr "Неуспешен опит за вход"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Logs" msgid "Logs"
msgstr "Логове" msgstr "Логове"
@@ -599,19 +631,19 @@ msgstr "Управление на предпочитанията за показ
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "" msgstr "Инструкции за ръчна настройка"
#. Chart select field. Please try to keep this short. #. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Max 1 min" msgid "Max 1 min"
msgstr "Максимум 1 минута" msgstr "Максимум 1 минута"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Memory" msgid "Memory"
msgstr "Памет" msgstr "Памет"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
msgstr "Употреба на паметта" msgstr "Употреба на паметта"
@@ -619,12 +651,12 @@ msgstr "Употреба на паметта"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Използването на памет от docker контейнерите" msgstr "Използването на памет от docker контейнерите"
#: src/components/alerts-history-columns.tsx
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
msgid "Name" msgid "Name"
msgstr "Име" msgstr "Име"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Net" msgid "Net"
msgstr "Мрежа" msgstr "Мрежа"
@@ -639,7 +671,7 @@ msgstr "Мрежов трафик на публични интерфейси"
#. 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 "Единица за измерване на скорост"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "No results found." msgid "No results found."
@@ -647,7 +679,7 @@ msgstr "Няма намерени резултати."
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "No results." msgid "No results."
msgstr "" msgstr "Няма резултати."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
@@ -655,8 +687,8 @@ msgid "No systems found."
msgstr "Няма намерени системи." msgstr "Няма намерени системи."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Notifications" msgid "Notifications"
msgstr "Нотификации" msgstr "Нотификации"
@@ -668,9 +700,9 @@ msgstr "Поддръжка на OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "На всеки рестарт, системите в датабазата ще бъдат обновени да съвпадат със системите зададени във файла." msgstr "На всеки рестарт, системите в датабазата ще бъдат обновени да съвпадат със системите зададени във файла."
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu" msgid "Open menu"
msgstr "Отвори менюто" msgstr "Отвори менюто"
@@ -678,7 +710,7 @@ msgstr "Отвори менюто"
msgid "Or continue with" msgid "Or continue with"
msgstr "Или продължи с" msgstr "Или продължи с"
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Презапиши съществуващи тревоги" msgstr "Презапиши съществуващи тревоги"
@@ -690,7 +722,7 @@ msgstr "Страница"
#. 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 "Страница {0} от {1}"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Pages / Settings" msgid "Pages / Settings"
@@ -707,17 +739,17 @@ msgstr "Паролата трябва да е поне 8 символа."
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Password must be less than 72 bytes." msgid "Password must be less than 72 bytes."
msgstr "" msgstr "Паролата трябва да е по-малка от 72 байта."
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Password reset request received" msgid "Password reset request received"
msgstr "Получено е искането за нулиране на паролата" msgstr "Получено е искането за нулиране на паролата"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Pause" msgid "Pause"
msgstr "Пауза" msgstr "Пауза"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Paused" msgid "Paused"
msgstr "На пауза" msgstr "На пауза"
@@ -725,12 +757,12 @@ msgstr "На пауза"
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> за да се подсигуриш, че тревогите са доставени."
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Please check logs for more details." msgid "Please check logs for more details."
msgstr "Моля провери log-овете за повече информация." msgstr "Моля провери log-овете за повече информация."
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Please check your credentials and try again" msgid "Please check your credentials and try again"
msgstr "Моля провери дадената информация и опитай отново" msgstr "Моля провери дадената информация и опитай отново"
@@ -790,32 +822,32 @@ msgstr "Нулиране на парола"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Resolved" msgid "Resolved"
msgstr "" msgstr "Решен"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
msgstr "Възобнови" msgstr "Възобнови"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token" msgid "Rotate token"
msgstr "" msgstr "Пресъздаване на идентификатора"
#: 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 "Редове на страница"
#: 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."
msgstr "Запази адреса с enter или запетая. Остави празно за да изключиш нотификациите чрез имейл." msgstr "Запази адреса с enter или запетая. Остави празно за да изключиш нотификациите чрез имейл."
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Save Settings" msgid "Save Settings"
msgstr "Запази настройките" msgstr "Запази настройките"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Save system" msgid "Save system"
msgstr "" msgstr "Запази система"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Search" msgid "Search"
@@ -825,7 +857,7 @@ msgstr "Търси"
msgid "Search for systems or settings..." msgid "Search for systems or settings..."
msgstr "Търси за системи или настройки..." msgstr "Търси за системи или настройки..."
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "See <0>notification settings</0> to configure how you receive alerts." msgid "See <0>notification settings</0> to configure how you receive alerts."
msgstr "Виж <0>настройките за нотификациите</0> за да конфигурираш как получаваш тревоги." msgstr "Виж <0>настройките за нотификациите</0> за да конфигурираш как получаваш тревоги."
@@ -833,6 +865,10 @@ msgstr "Виж <0>настройките за нотификациите</0> з
msgid "Sent" msgid "Sent"
msgstr "Изпратени" msgstr "Изпратени"
#: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors."
msgstr "Задайте процентни прагове за цветовете на измервателните уреди."
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed." msgid "Sets the default time range for charts when a system is viewed."
msgstr "Задава диапазона за време за диаграмите, когато се разглежда система." msgstr "Задава диапазона за време за диаграмите, когато се разглежда система."
@@ -863,9 +899,9 @@ msgstr "Сортиране по"
#. 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 "Състояние"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "Статус" msgstr "Статус"
@@ -877,18 +913,16 @@ msgstr "Изполван swap от системата"
msgid "Swap Usage" msgid "Swap Usage"
msgstr "Използване на swap" msgstr "Използване на swap"
#. System theme
#: src/lib/utils.ts
#: src/components/mode-toggle.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "System" msgid "System"
msgstr "Система" msgstr "Система"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "System load averages over time" msgid "System load averages over time"
msgstr "" msgstr "Средно натоварване на системата във времето"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Systems" msgid "Systems"
@@ -903,18 +937,18 @@ msgid "Table"
msgstr "Таблица" msgstr "Таблица"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "" msgstr "Температура"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Temperature" msgid "Temperature"
msgstr "Температура" msgstr "Температура"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Temperature unit" msgid "Temperature unit"
msgstr "" msgstr "Единица за температура"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Temperatures of system sensors" msgid "Temperatures of system sensors"
@@ -932,13 +966,13 @@ msgstr "Тестова нотификация изпратена"
msgid "Then log into the backend and reset your user account password in the users table." msgid "Then log into the backend and reset your user account password in the users table."
msgstr "След това влез в backend-а и нулирай паролата за потребителския акаунт в таблицата за потребители." msgstr "След това влез в backend-а и нулирай паролата за потребителския акаунт в таблицата за потребители."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database." msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database."
msgstr "Това действие не може да бъде отменено. Това ще изтрие всички записи за {name} от датабазата." msgstr "Това действие не може да бъде отменено. Това ще изтрие всички записи за {name} от датабазата."
#: 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 "Това ще доведе до трайно изтриване на всички избрани записи от базата данни."
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Throughput of {extraFsName}" msgid "Throughput of {extraFsName}"
@@ -964,72 +998,77 @@ msgstr "Включи тема"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token" msgid "Token"
msgstr "" msgstr "Токен"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens & Fingerprints" msgid "Tokens & Fingerprints"
msgstr "" msgstr "Токен & Пръстов отпечатък"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens allow agents to connect and register. Fingerprints are stable identifiers unique to each system, set on first connection." msgid "Tokens allow agents to connect and register. Fingerprints are stable identifiers unique to each system, set on first connection."
msgstr "" msgstr "Токените позволяват на агентите да се свързват и регистрират. Отпечатъците са стабилни идентификатори, уникални за всяка система, които се задават при първото свързване."
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr "Токените и пръстовите отпечатъци се използват за удостоверяване на WebSocket връзките към концентратора."
#: src/lib/utils.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 "Задейства се, когато употребата на паметта за 1 минута надвиши зададен праг"
#: src/lib/utils.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 "Задейства се, когато употребата на паметта за 15 минута надвиши зададен праг"
#: src/lib/utils.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 "Задейства се, когато употребата на паметта за 5 минута надвиши зададен праг"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Задейства се, когато някой даден сензор надвиши зададен праг" msgstr "Задейства се, когато някой даден сензор надвиши зададен праг"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when combined up/down exceeds a threshold" msgid "Triggers when combined up/down exceeds a threshold"
msgstr "Задейства се, когато комбинираното качване/сваляне надвиши зададен праг" msgstr "Задейства се, когато комбинираното качване/сваляне надвиши зададен праг"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
msgstr "Задейства се, когато употребата на процесора надвиши зададен праг" msgstr "Задейства се, когато употребата на процесора надвиши зададен праг"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
msgstr "Задейства се, когато употребата на паметта надвиши зададен праг" msgstr "Задейства се, когато употребата на паметта надвиши зададен праг"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when status switches between up and down" msgid "Triggers when status switches between up and down"
msgstr "Задейства се, когато статуса превключва между долу и горе" msgstr "Задейства се, когато статуса превключва между долу и горе"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Задейства се, когато употребата на някой диск надивши зададен праг" msgstr "Задейства се, когато употребата на някой диск надивши зададен праг"
#. 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 "Предпочитания на единицата"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Universal token" msgid "Universal token"
msgstr "" msgstr "Универсален тоукън"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Неизвестна"
#. Context: System is up #. Context: System is up
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Up" 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 "Updated in real time. Click on a system to view information."
@@ -1050,19 +1089,19 @@ msgstr "Употреба"
msgid "Usage of root partition" msgid "Usage of root partition"
msgstr "Употреба на root partition-а" msgstr "Употреба на root partition-а"
#: src/components/charts/swap-chart.tsx
#: src/components/charts/mem-chart.tsx #: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
msgid "Used" msgid "Used"
msgstr "Използвани" msgstr "Използвани"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Users" msgid "Users"
msgstr "Потребители" msgstr "Потребители"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Value" msgid "Value"
msgstr "" msgstr "Стойност"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "View" msgid "View"
@@ -1070,7 +1109,7 @@ msgstr "Изглед"
#: 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 "Прегледайте последните си 200 сигнала."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Visible Fields" msgid "Visible Fields"
@@ -1084,13 +1123,21 @@ msgstr "Изчаква се за достатъчно записи за пока
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "Искаш да помогнеш да направиш преводите още по-добри? Провери нашия <0>Crowdin</0> за повече детайли." msgstr "Искаш да помогнеш да направиш преводите още по-добри? Провери нашия <0>Crowdin</0> за повече детайли."
#: src/components/routes/settings/general.tsx
msgid "Warning (%)"
msgstr "Предупреждение (%)"
#: src/components/routes/settings/general.tsx
msgid "Warning thresholds"
msgstr "Прагове за предупреждение"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Webhook / Push notifications" msgid "Webhook / Push notifications"
msgstr "Webhook / Пуш нотификации" msgstr "Webhook / Пуш нотификации"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "When enabled, this token allows agents to self-register without prior system creation. Expires after one hour or on hub restart." msgid "When enabled, this token allows agents to self-register without prior system creation. Expires after one hour or on hub restart."
msgstr "" msgstr "Когато е активиран, този символ позволява на агентите да се регистрират сами без предварително създаване на система. Изтича след един час или при рестартиране на хъба."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx

View File

@@ -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-07-25 22:44\n" "PO-Revision-Date: 2025-08-25 01:16\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"
@@ -27,7 +27,7 @@ msgstr "{0, plural, one {# den} few {# dny} other {# dní}}"
#. 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} z {1} vybraných řádků."
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "{hours, plural, one {# hour} other {# hours}}" msgid "{hours, plural, one {# hour} other {# hours}}"
@@ -40,7 +40,7 @@ msgstr "1 hodina"
#. 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 +53,7 @@ msgstr "12 hodin"
#. 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,18 +66,18 @@ msgstr "30 dní"
#. 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/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions" msgid "Actions"
msgstr "Akce" msgstr "Akce"
#: 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 "Active" msgid "Active"
msgstr "" msgstr "Aktivní"
#: src/components/routes/home.tsx #: src/components/routes/home.tsx
msgid "Active Alerts" msgid "Active Alerts"
@@ -108,33 +108,33 @@ msgstr "Upravit možnosti zobrazení pro grafy."
msgid "Admin" msgid "Admin"
msgstr "Admin" msgstr "Admin"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "Agent" msgstr "Agent"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/layout.tsx
msgid "Alert History" msgid "Alert History"
msgstr "" msgstr "Historie upozornění"
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alert-button.tsx
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Alerts" msgid "Alerts"
msgstr "Výstrahy" msgstr "Výstrahy"
#: src/components/alerts/alerts-sheet.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/alerts/alert-button.tsx
msgid "All Systems" msgid "All Systems"
msgstr "Všechny systémy" msgstr "Všechny systémy"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Are you sure you want to delete {name}?" msgid "Are you sure you want to delete {name}?"
msgstr "Opravdu chcete odstranit {name}?" msgstr "Opravdu chcete odstranit {name}?"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Are you sure?" msgid "Are you sure?"
msgstr "" msgstr "Jste si jistý?"
#: src/components/copy-to-clipboard.tsx #: src/components/copy-to-clipboard.tsx
msgid "Automatic copy requires a secure context." msgid "Automatic copy requires a secure context."
@@ -148,8 +148,8 @@ msgstr "Průměr"
msgid "Average CPU utilization of containers" msgid "Average CPU utilization of containers"
msgstr "Průměrné využití CPU kontejnerů" msgstr "Průměrné využití CPU kontejnerů"
#. placeholder {0}: data.alert.unit #. placeholder {0}: alertData.unit
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Average exceeds <0>{value}{0}</0>" msgid "Average exceeds <0>{value}{0}</0>"
msgstr "Průměr je vyšší než <0>{value}{0}</0>" msgstr "Průměr je vyšší než <0>{value}{0}</0>"
@@ -166,16 +166,20 @@ msgstr "Průměrné využití CPU v celém systému"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Průměrné využití {0}" msgstr "Průměrné využití {0}"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
msgstr "Zálohy" msgstr "Zálohy"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Bandwidth" msgid "Bandwidth"
msgstr "Přenos" msgstr "Přenos"
#: src/components/routes/system.tsx
msgid "Battery"
msgstr "Baterie"
#: 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."
msgstr "Beszel podporuje OpenID Connect a mnoho poskytovatelů OAuth2 ověřování." msgstr "Beszel podporuje OpenID Connect a mnoho poskytovatelů OAuth2 ověřování."
@@ -191,19 +195,19 @@ msgstr "Binary"
#: 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"
msgstr "Cache / vyrovnávací paměť" msgstr "Cache / vyrovnávací paměť"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Cancel" msgid "Cancel"
msgstr "Zrušit" msgstr "Zrušit"
@@ -213,16 +217,25 @@ msgstr "Upozornění - možná ztráta dat"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Celsius (°C)" msgid "Celsius (°C)"
msgstr "" msgstr "Celsia (°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 "Změnit jednotky zobrazení metrik."
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Change general application options." msgid "Change general application options."
msgstr "Změnit obecné nastavení aplikace." msgstr "Změnit obecné nastavení aplikace."
#: src/components/routes/system.tsx
msgid "Charge"
msgstr "Nabíjení"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Charging"
msgstr "Nabíjení"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Chart options" msgid "Chart options"
msgstr "Možnosti grafu" msgstr "Možnosti grafu"
@@ -259,10 +272,10 @@ msgstr "Potvrdit heslo"
#: src/components/routes/home.tsx #: src/components/routes/home.tsx
msgid "Connection is down" msgid "Connection is down"
msgstr "" msgstr "Připojení je nedostupné"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Continue" msgid "Continue"
msgstr "Pokračovat" msgstr "Pokračovat"
@@ -285,9 +298,9 @@ msgstr "Zkopírovat příkaz na spuštění dockeru"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Environment variables" msgctxt "Environment variables"
msgid "Copy env" msgid "Copy env"
msgstr "" msgstr "Kopírovat env"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy host" msgid "Copy host"
msgstr "Kopírovat hostitele" msgstr "Kopírovat hostitele"
@@ -296,7 +309,7 @@ msgstr "Kopírovat hostitele"
msgid "Copy Linux command" msgid "Copy Linux command"
msgstr "Kopírovat příkaz Linux" msgstr "Kopírovat příkaz Linux"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy name" msgid "Copy name"
msgstr "Kopírovat název" msgstr "Kopírovat název"
@@ -306,23 +319,23 @@ msgstr "Kopírovat text"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Copy the installation command for the agent below, or register agents automatically with a <0>universal token</0>." msgid "Copy the installation command for the agent below, or register agents automatically with a <0>universal token</0>."
msgstr "" msgstr "Zkopírujte instalační příkaz pro agenta níže nebo automaticky registrujte agenty s <0>univerzálním token</0>."
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Copy the<0>docker-compose.yml</0> content for the agent below, or register agents automatically with a <1>universal token</1>." msgid "Copy the<0>docker-compose.yml</0> content for the agent below, or register agents automatically with a <1>universal token</1>."
msgstr "" msgstr "Zkopírujte obsah <0>docker-compose.yml</0> pro agenta níže nebo automaticky registrujte agenty s <1>univerzálním token</1>."
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy YAML" msgid "Copy YAML"
msgstr "" msgstr "Kopírovat YAML"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "Procesor" msgstr "Procesor"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "Využití procesoru" msgstr "Využití procesoru"
@@ -333,12 +346,16 @@ msgstr "Vytvořit účet"
#. Context: date created #. Context: date created
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Created" msgid "Created"
msgstr "" msgstr "Vytvořeno"
#. Dark theme #: src/components/routes/settings/general.tsx
#: src/components/mode-toggle.tsx msgid "Critical (%)"
msgid "Dark" msgstr "Kritické (%)"
msgstr "Tmavý"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
msgstr "Aktuální stav"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/home.tsx #: src/components/routes/home.tsx
@@ -349,16 +366,21 @@ msgstr "Přehled"
msgid "Default time period" msgid "Default time period"
msgstr "Výchozí doba" msgstr "Výchozí doba"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Delete" msgid "Delete"
msgstr "Odstranit" msgstr "Odstranit"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "" msgstr "Smazat identifikátor"
#: src/components/systems-table/systems-table.tsx #. Context: Battery state
#: src/lib/i18n.ts
msgid "Discharging"
msgstr "Vybíjení"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk" msgid "Disk"
msgstr "Disk" msgstr "Disk"
@@ -368,11 +390,11 @@ msgstr "Disk I/O"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Disk unit" msgid "Disk unit"
msgstr "" msgstr "Disková jednotka"
#: src/lib/utils.ts
#: src/components/routes/system.tsx
#: src/components/charts/disk-chart.tsx #: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Disk Usage" msgid "Disk Usage"
msgstr "Využití disku" msgstr "Využití disku"
@@ -397,24 +419,24 @@ msgid "Documentation"
msgstr "Dokumentace" msgstr "Dokumentace"
#. Context: System is down #. Context: System is down
#: src/lib/utils.ts
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "Down" msgid "Down"
msgstr "Nefunkční" msgstr "Nefunkční"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "" msgstr "Doba trvání"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Edit" msgid "Edit"
msgstr "Upravit" msgstr "Upravit"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Email" msgid "Email"
msgstr "Email" msgstr "Email"
@@ -422,6 +444,11 @@ msgstr "Email"
msgid "Email notifications" msgid "Email notifications"
msgstr "Emailová upozornění" msgstr "Emailová upozornění"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Empty"
msgstr "Prázdná"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Enter email address to reset password" msgid "Enter email address to reset password"
msgstr "Zadejte e-mailovou adresu pro obnovu hesla" msgstr "Zadejte e-mailovou adresu pro obnovu hesla"
@@ -430,11 +457,11 @@ msgstr "Zadejte e-mailovou adresu pro obnovu hesla"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Zadejte e-mailovou adresu..." msgstr "Zadejte e-mailovou adresu..."
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Error" msgid "Error"
msgstr "Chyba" msgstr "Chyba"
@@ -451,7 +478,7 @@ msgstr "Stávající systémy, které nejsou definovány v <0>config.yml</0>, bu
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Export" msgid "Export"
msgstr "" msgstr "Export"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Export configuration" msgid "Export configuration"
@@ -463,14 +490,14 @@ msgstr "Exportovat aktuální konfiguraci systémů."
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "" msgstr "Fahrenheita (°F)"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Ověření se nezdařilo" msgstr "Ověření se nezdařilo"
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Failed to save settings" msgid "Failed to save settings"
msgstr "Nepodařilo se uložit nastavení" msgstr "Nepodařilo se uložit nastavení"
@@ -478,21 +505,21 @@ msgstr "Nepodařilo se uložit nastavení"
msgid "Failed to send test notification" msgid "Failed to send test notification"
msgstr "Nepodařilo se odeslat testovací oznámení" msgstr "Nepodařilo se odeslat testovací oznámení"
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Failed to update alert" msgid "Failed to update alert"
msgstr "Nepodařilo se aktualizovat upozornění" msgstr "Nepodařilo se aktualizovat upozornění"
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filtr..." msgstr "Filtr..."
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Fingerprint" msgid "Fingerprint"
msgstr "" msgstr "Otisk"
#: src/components/alerts/alerts-system.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}}"
msgstr "Za <0>{min}</0> {min, plural, one {minutu} few {minuty} other {minut}}" msgstr "Za <0>{min}</0> {min, plural, one {minutu} few {minuty} other {minut}}"
@@ -500,9 +527,14 @@ msgstr "Za <0>{min}</0> {min, plural, one {minutu} few {minuty} other {minut}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Zapomněli jste heslo?" msgstr "Zapomněli jste heslo?"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
msgstr "Plná"
#. Context: General settings #. Context: General settings
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/layout.tsx
msgid "General" msgid "General"
msgstr "Obecné" msgstr "Obecné"
@@ -524,6 +556,11 @@ msgstr "Homebrew příkaz"
msgid "Host / IP" msgid "Host / IP"
msgstr "Hostitel / IP" msgstr "Hostitel / IP"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Idle"
msgstr "Neaktivní"
#: 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."
msgstr "Pokud jste ztratili heslo k vašemu účtu správce, můžete jej obnovit pomocí následujícího příkazu." msgstr "Pokud jste ztratili heslo k vašemu účtu správce, můžete jej obnovit pomocí následujícího příkazu."
@@ -545,31 +582,26 @@ msgstr "Jazyk"
msgid "Layout" msgid "Layout"
msgstr "Rozvržení" msgstr "Rozvržení"
#. Light theme
#: src/components/mode-toggle.tsx
msgid "Light"
msgstr "Světlý"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "" msgstr "Průměrné vytížení"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 15m" msgid "Load Average 15m"
msgstr "" msgstr "Průměrná zátěž 15m"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 1m" msgid "Load Average 1m"
msgstr "" msgstr "Průměrná zátěž 1m"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 5m" msgid "Load Average 5m"
msgstr "" msgstr "Průměrná zátěž 5m"
#. Short label for load average #. Short label for load average
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Load Avg" msgid "Load Avg"
msgstr "" msgstr "Prům. zatížení"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
@@ -579,13 +611,13 @@ msgstr "Odhlásit"
msgid "Login" msgid "Login"
msgstr "Přihlásit" msgstr "Přihlásit"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Login attempt failed" msgid "Login attempt failed"
msgstr "Pokus o přihlášení selhal" msgstr "Pokus o přihlášení selhal"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Logs" msgid "Logs"
msgstr "Logy" msgstr "Logy"
@@ -606,12 +638,12 @@ msgstr "Pokyny k manuálnímu nastavení"
msgid "Max 1 min" msgid "Max 1 min"
msgstr "Max. 1 min" msgstr "Max. 1 min"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Memory" msgid "Memory"
msgstr "Paměť" msgstr "Paměť"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
msgstr "Využití paměti" msgstr "Využití paměti"
@@ -619,12 +651,12 @@ msgstr "Využití paměti"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Využití paměti docker kontejnerů" msgstr "Využití paměti docker kontejnerů"
#: src/components/alerts-history-columns.tsx
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
msgid "Name" msgid "Name"
msgstr "Název" msgstr "Název"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Net" msgid "Net"
msgstr "Síť" msgstr "Síť"
@@ -639,7 +671,7 @@ msgstr "Síťový provoz veřejných rozhraní"
#. 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 "Síťová jednotka"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "No results found." msgid "No results found."
@@ -647,7 +679,7 @@ msgstr "Nenalezeny žádné výskyty."
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "No results." msgid "No results."
msgstr "" msgstr "Žádné výsledky."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
@@ -655,8 +687,8 @@ msgid "No systems found."
msgstr "Nenalezeny žádné systémy." msgstr "Nenalezeny žádné systémy."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Notifications" msgid "Notifications"
msgstr "Upozornění" msgstr "Upozornění"
@@ -668,9 +700,9 @@ msgstr "Podpora OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Při každém restartu budou systémy v databázi aktualizovány tak, aby odpovídaly systémům definovaným v souboru." msgstr "Při každém restartu budou systémy v databázi aktualizovány tak, aby odpovídaly systémům definovaným v souboru."
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu" msgid "Open menu"
msgstr "Otevřít menu" msgstr "Otevřít menu"
@@ -678,7 +710,7 @@ msgstr "Otevřít menu"
msgid "Or continue with" msgid "Or continue with"
msgstr "Nebo pokračujte s" msgstr "Nebo pokračujte s"
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Přepsat existující upozornění" msgstr "Přepsat existující upozornění"
@@ -690,7 +722,7 @@ msgstr "Stránka"
#. 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 "Stránka {0} z {1}"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Pages / Settings" msgid "Pages / Settings"
@@ -713,11 +745,11 @@ msgstr "Heslo musí být menší než 72 bytů."
msgid "Password reset request received" msgid "Password reset request received"
msgstr "Žádost o obnovu hesla byla přijata" msgstr "Žádost o obnovu hesla byla přijata"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Pause" msgid "Pause"
msgstr "Pozastavit" msgstr "Pozastavit"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Paused" msgid "Paused"
msgstr "Pozastaveno" msgstr "Pozastaveno"
@@ -725,12 +757,12 @@ msgstr "Pozastaveno"
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."
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Please check logs for more details." msgid "Please check logs for more details."
msgstr "Pro více informací zkontrolujte logy." msgstr "Pro více informací zkontrolujte logy."
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Please check your credentials and try again" msgid "Please check your credentials and try again"
msgstr "Zkontrolujte prosím Vaše přihlašovací údaje a zkuste to znovu" msgstr "Zkontrolujte prosím Vaše přihlašovací údaje a zkuste to znovu"
@@ -790,26 +822,26 @@ msgstr "Obnovit heslo"
#: 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 "Vyřešeno"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
msgstr "Pokračovat" msgstr "Pokračovat"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token" msgid "Rotate token"
msgstr "" msgstr "Změnit 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 "Řádků na stránku"
#: 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."
msgstr "Adresu uložte pomocí klávesy enter nebo čárky. Pro deaktivaci e-mailových oznámení ponechte prázdné pole." msgstr "Adresu uložte pomocí klávesy enter nebo čárky. Pro deaktivaci e-mailových oznámení ponechte prázdné pole."
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Save Settings" msgid "Save Settings"
msgstr "Uložit nastavení" msgstr "Uložit nastavení"
@@ -825,7 +857,7 @@ msgstr "Hledat"
msgid "Search for systems or settings..." msgid "Search for systems or settings..."
msgstr "Hledat systémy nebo nastavení..." msgstr "Hledat systémy nebo nastavení..."
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "See <0>notification settings</0> to configure how you receive alerts." msgid "See <0>notification settings</0> to configure how you receive alerts."
msgstr "Podívejte se na <0>nastavení upozornění</0> pro nastavení toho, jak přijímáte upozornění." msgstr "Podívejte se na <0>nastavení upozornění</0> pro nastavení toho, jak přijímáte upozornění."
@@ -833,6 +865,10 @@ msgstr "Podívejte se na <0>nastavení upozornění</0> pro nastavení toho, jak
msgid "Sent" msgid "Sent"
msgstr "Odeslat" msgstr "Odeslat"
#: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors."
msgstr "Nastavte procentuální prahové hodnoty pro barvy měřičů."
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed." msgid "Sets the default time range for charts when a system is viewed."
msgstr "Nastaví výchozí časový rozsah grafů, když je systém zobrazen." msgstr "Nastaví výchozí časový rozsah grafů, když je systém zobrazen."
@@ -863,9 +899,9 @@ msgstr "Seřadit podle"
#. 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 "Stav"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "Stav" msgstr "Stav"
@@ -877,18 +913,16 @@ msgstr "Swap prostor využívaný systémem"
msgid "Swap Usage" msgid "Swap Usage"
msgstr "Swap využití" msgstr "Swap využití"
#. System theme
#: src/lib/utils.ts
#: src/components/mode-toggle.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "System" msgid "System"
msgstr "Systém" msgstr "Systém"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "System load averages over time" msgid "System load averages over time"
msgstr "" msgstr "Průměry zatížení systému v průběhu času"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Systems" msgid "Systems"
@@ -903,18 +937,18 @@ msgid "Table"
msgstr "Tabulka" msgstr "Tabulka"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "Teplota" msgstr "Teplota"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Temperature" msgid "Temperature"
msgstr "Teplota" msgstr "Teplota"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Temperature unit" msgid "Temperature unit"
msgstr "" msgstr "Jednotky teploty"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Temperatures of system sensors" msgid "Temperatures of system sensors"
@@ -932,13 +966,13 @@ msgstr "Testovací oznámení odesláno"
msgid "Then log into the backend and reset your user account password in the users table." msgid "Then log into the backend and reset your user account password in the users table."
msgstr "Poté se přihlaste do backendu a obnovte heslo k uživatelskému účtu v tabulce uživatelů." msgstr "Poté se přihlaste do backendu a obnovte heslo k uživatelskému účtu v tabulce uživatelů."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database." msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database."
msgstr "Tuto akci nelze vzít zpět. Tím se z databáze trvale odstraní všechny aktuální záznamy pro {name}." msgstr "Tuto akci nelze vzít zpět. Tím se z databáze trvale odstraní všechny aktuální záznamy pro {name}."
#: 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 "Tímto trvale odstraníte všechny vybrané záznamy z databáze."
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Throughput of {extraFsName}" msgid "Throughput of {extraFsName}"
@@ -964,70 +998,75 @@ msgstr "Přepnout motiv"
#: 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/tokens-fingerprints.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens & Fingerprints" msgid "Tokens & Fingerprints"
msgstr "" msgstr "Tokeny & Otisky"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens allow agents to connect and register. Fingerprints are stable identifiers unique to each system, set on first connection." msgid "Tokens allow agents to connect and register. Fingerprints are stable identifiers unique to each system, set on first connection."
msgstr "" msgstr "Tokeny umožňují agentům připojení a registraci. Otisky jsou stabilní identifikátory jedinečné pro každý systém, nastavené na první připojení."
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr "Tokeny a otisky slouží k ověření připojení WebSocket k uzlu."
#: src/lib/utils.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 "Spustí se, když využití paměti během 1 minuty překročí prahovou hodnotu"
#: src/lib/utils.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 "Spustí se, když využití paměti během 15 minut překročí prahovou hodnotu"
#: src/lib/utils.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 "Spustí se, když využití paměti během 5 minut překročí prahovou hodnotu"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Spustí se, když některý senzor překročí prahovou hodnotu" msgstr "Spustí se, když některý senzor překročí prahovou hodnotu"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when combined up/down exceeds a threshold" msgid "Triggers when combined up/down exceeds a threshold"
msgstr "Spustí se, když kombinace up/down překročí prahovou hodnotu" msgstr "Spustí se, když kombinace up/down překročí prahovou hodnotu"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
msgstr "Spustí se, když využití procesoru překročí prahovou hodnotu" msgstr "Spustí se, když využití procesoru překročí prahovou hodnotu"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
msgstr "Spustí se, když využití paměti překročí prahovou hodnotu" msgstr "Spustí se, když využití paměti překročí prahovou hodnotu"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when status switches between up and down" msgid "Triggers when status switches between up and down"
msgstr "Spouští se, když se změní dostupnost" msgstr "Spouští se, když se změní dostupnost"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Spustí se, když využití disku překročí prahovou hodnotu" msgstr "Spustí se, když využití disku překročí prahovou hodnotu"
#. 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 "Předvolby jednotek"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Universal token" msgid "Universal token"
msgstr "" msgstr "Univerzální token"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Neznámá"
#. Context: System is up #. Context: System is up
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Up" msgid "Up"
msgstr "Funkční" msgstr "Funkční"
@@ -1050,19 +1089,19 @@ msgstr "Využití"
msgid "Usage of root partition" msgid "Usage of root partition"
msgstr "Využití kořenového oddílu" msgstr "Využití kořenového oddílu"
#: src/components/charts/swap-chart.tsx
#: src/components/charts/mem-chart.tsx #: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
msgid "Used" msgid "Used"
msgstr "Využito" msgstr "Využito"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Users" msgid "Users"
msgstr "Uživatelé" msgstr "Uživatelé"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Value" msgid "Value"
msgstr "" msgstr "Hodnota"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "View" msgid "View"
@@ -1070,7 +1109,7 @@ msgstr "Zobrazení"
#: 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 "Zobrazit vašich 200 nejnovějších upozornění."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Visible Fields" msgid "Visible Fields"
@@ -1084,13 +1123,21 @@ msgstr "Čeká se na dostatek záznamů k zobrazení"
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "Chcete nám pomoci s našimi překlady ještě lépe? Podívejte se na <0>Crowdin</0> pro více informací." msgstr "Chcete nám pomoci s našimi překlady ještě lépe? Podívejte se na <0>Crowdin</0> pro více informací."
#: src/components/routes/settings/general.tsx
msgid "Warning (%)"
msgstr "Varování (%)"
#: src/components/routes/settings/general.tsx
msgid "Warning thresholds"
msgstr "Prahové hodnoty pro varování"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Webhook / Push notifications" msgid "Webhook / Push notifications"
msgstr "Webhook / Push oznámení" msgstr "Webhook / Push oznámení"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "When enabled, this token allows agents to self-register without prior system creation. Expires after one hour or on hub restart." msgid "When enabled, this token allows agents to self-register without prior system creation. Expires after one hour or on hub restart."
msgstr "" msgstr "Pokud je povoleno, tento token umožňuje agentům, aby se sami zaregistrovali bez předchozího vytvoření systému. Vyprší po jedné hodině nebo po restartu uzlu."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx

View File

@@ -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-07-25 22:44\n" "PO-Revision-Date: 2025-08-25 01:16\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"
@@ -69,8 +69,8 @@ msgid "5 min"
msgstr "" msgstr ""
#. Table column #. Table column
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions" msgid "Actions"
msgstr "Handlinger" msgstr "Handlinger"
@@ -108,27 +108,27 @@ msgstr "Juster visningsindstillinger for diagrammer."
msgid "Admin" msgid "Admin"
msgstr "Admin" msgstr "Admin"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "Agent" msgstr "Agent"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/layout.tsx
msgid "Alert History" msgid "Alert History"
msgstr "" msgstr ""
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alert-button.tsx
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Alerts" msgid "Alerts"
msgstr "Alarmer" msgstr "Alarmer"
#: src/components/alerts/alerts-sheet.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/alerts/alert-button.tsx
msgid "All Systems" msgid "All Systems"
msgstr "Alle systemer" msgstr "Alle systemer"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Are you sure you want to delete {name}?" msgid "Are you sure you want to delete {name}?"
msgstr "Er du sikker på, at du vil slette {name}?" msgstr "Er du sikker på, at du vil slette {name}?"
@@ -148,8 +148,8 @@ msgstr "Gennemsnitlig"
msgid "Average CPU utilization of containers" msgid "Average CPU utilization of containers"
msgstr "Gennemsnitlig CPU udnyttelse af containere" msgstr "Gennemsnitlig CPU udnyttelse af containere"
#. placeholder {0}: data.alert.unit #. placeholder {0}: alertData.unit
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Average exceeds <0>{value}{0}</0>" msgid "Average exceeds <0>{value}{0}</0>"
msgstr "Gennemsnit overstiger <0>{value}{0}</0>" msgstr "Gennemsnit overstiger <0>{value}{0}</0>"
@@ -166,16 +166,20 @@ msgstr "Gennemsnitlig systembaseret CPU-udnyttelse"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Gennemsnitlig udnyttelse af {0}" msgstr "Gennemsnitlig udnyttelse af {0}"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
msgstr "Sikkerhedskopier" msgstr "Sikkerhedskopier"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Bandwidth" msgid "Bandwidth"
msgstr "Båndbredde" msgstr "Båndbredde"
#: src/components/routes/system.tsx
msgid "Battery"
msgstr "Batteri"
#: 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."
msgstr "Beszel understøtter OpenID Connect og mange OAuth2 godkendelsesudbydere." msgstr "Beszel understøtter OpenID Connect og mange OAuth2 godkendelsesudbydere."
@@ -202,8 +206,8 @@ msgstr ""
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "Cache / Buffere" msgstr "Cache / Buffere"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Cancel" msgid "Cancel"
msgstr "Fortryd" msgstr "Fortryd"
@@ -223,6 +227,15 @@ msgstr ""
msgid "Change general application options." msgid "Change general application options."
msgstr "Skift generelle applikationsindstillinger." msgstr "Skift generelle applikationsindstillinger."
#: src/components/routes/system.tsx
msgid "Charge"
msgstr "Opladning"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Charging"
msgstr "Oplader"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Chart options" msgid "Chart options"
msgstr "Diagrammuligheder" msgstr "Diagrammuligheder"
@@ -261,8 +274,8 @@ msgstr "Bekræft adgangskode"
msgid "Connection is down" msgid "Connection is down"
msgstr "" msgstr ""
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Continue" msgid "Continue"
msgstr "Forsæt" msgstr "Forsæt"
@@ -287,7 +300,7 @@ msgctxt "Environment variables"
msgid "Copy env" msgid "Copy env"
msgstr "" msgstr ""
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy host" msgid "Copy host"
msgstr "Kopier host" msgstr "Kopier host"
@@ -296,7 +309,7 @@ msgstr "Kopier host"
msgid "Copy Linux command" msgid "Copy Linux command"
msgstr "Kopier Linux kommando" msgstr "Kopier Linux kommando"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy name" msgid "Copy name"
msgstr "Kopier navn" msgstr "Kopier navn"
@@ -316,13 +329,13 @@ msgstr ""
msgid "Copy YAML" msgid "Copy YAML"
msgstr "" msgstr ""
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "CPU forbrug" msgstr "CPU forbrug"
@@ -335,10 +348,14 @@ msgstr "Opret konto"
msgid "Created" msgid "Created"
msgstr "" msgstr ""
#. Dark theme #: src/components/routes/settings/general.tsx
#: src/components/mode-toggle.tsx msgid "Critical (%)"
msgid "Dark" msgstr "Kritisk (%)"
msgstr "Mørk"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
msgstr "Nuværende tilstand"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/home.tsx #: src/components/routes/home.tsx
@@ -349,8 +366,8 @@ msgstr "Oversigtspanel"
msgid "Default time period" msgid "Default time period"
msgstr "Standard tidsperiode" msgstr "Standard tidsperiode"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Delete" msgid "Delete"
msgstr "Slet" msgstr "Slet"
@@ -358,7 +375,12 @@ msgstr "Slet"
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "" msgstr ""
#: src/components/systems-table/systems-table.tsx #. Context: Battery state
#: src/lib/i18n.ts
msgid "Discharging"
msgstr "Aflader"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk" msgid "Disk"
msgstr "Disk" msgstr "Disk"
@@ -370,9 +392,9 @@ msgstr "Disk I/O"
msgid "Disk unit" msgid "Disk unit"
msgstr "" msgstr ""
#: src/lib/utils.ts
#: src/components/routes/system.tsx
#: src/components/charts/disk-chart.tsx #: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Disk Usage" msgid "Disk Usage"
msgstr "Diskforbrug" msgstr "Diskforbrug"
@@ -397,10 +419,10 @@ msgid "Documentation"
msgstr "Dokumentation" msgstr "Dokumentation"
#. Context: System is down #. Context: System is down
#: src/lib/utils.ts
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "Down" msgid "Down"
msgstr "Nede" msgstr "Nede"
@@ -409,12 +431,12 @@ msgid "Duration"
msgstr "" msgstr ""
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Edit" msgid "Edit"
msgstr "Rediger" msgstr "Rediger"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Email" msgid "Email"
msgstr "E-mail" msgstr "E-mail"
@@ -422,6 +444,11 @@ msgstr "E-mail"
msgid "Email notifications" msgid "Email notifications"
msgstr "Email-notifikationer" msgstr "Email-notifikationer"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Empty"
msgstr "Tom"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Enter email address to reset password" msgid "Enter email address to reset password"
msgstr "Indtast e-mailadresse for at nulstille adgangskoden" msgstr "Indtast e-mailadresse for at nulstille adgangskoden"
@@ -430,11 +457,11 @@ msgstr "Indtast e-mailadresse for at nulstille adgangskoden"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Indtast e-mailadresse..." msgstr "Indtast e-mailadresse..."
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Error" msgid "Error"
msgstr "Fejl" msgstr "Fejl"
@@ -469,8 +496,8 @@ msgstr ""
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Kunne ikke godkende" msgstr "Kunne ikke godkende"
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Failed to save settings" msgid "Failed to save settings"
msgstr "Kunne ikke gemme indstillinger" msgstr "Kunne ikke gemme indstillinger"
@@ -478,13 +505,13 @@ msgstr "Kunne ikke gemme indstillinger"
msgid "Failed to send test notification" msgid "Failed to send test notification"
msgstr "Afsendelse af testnotifikation mislykkedes" msgstr "Afsendelse af testnotifikation mislykkedes"
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Failed to update alert" msgid "Failed to update alert"
msgstr "Kunne ikke opdatere alarm" msgstr "Kunne ikke opdatere alarm"
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filter..." msgstr "Filter..."
@@ -492,7 +519,7 @@ msgstr "Filter..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "" msgstr ""
#: src/components/alerts/alerts-system.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}}"
msgstr "For <0>{min}</0> {min, plural, one {minut} other {minutter}}" msgstr "For <0>{min}</0> {min, plural, one {minut} other {minutter}}"
@@ -500,9 +527,14 @@ msgstr "For <0>{min}</0> {min, plural, one {minut} other {minutter}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Glemt adgangskode?" msgstr "Glemt adgangskode?"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
msgstr "Fuldt opladt"
#. Context: General settings #. Context: General settings
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/layout.tsx
msgid "General" msgid "General"
msgstr "Generelt" msgstr "Generelt"
@@ -524,6 +556,11 @@ msgstr "Homebrew-kommando"
msgid "Host / IP" msgid "Host / IP"
msgstr "Vært / IP" msgstr "Vært / IP"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Idle"
msgstr "Inaktiv"
#: 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."
msgstr "Hvis du har mistet adgangskoden til din administratorkonto, kan du nulstille den ved hjælp af følgende kommando." msgstr "Hvis du har mistet adgangskoden til din administratorkonto, kan du nulstille den ved hjælp af følgende kommando."
@@ -545,29 +582,24 @@ msgstr "Sprog"
msgid "Layout" msgid "Layout"
msgstr "Layout" msgstr "Layout"
#. Light theme
#: src/components/mode-toggle.tsx
msgid "Light"
msgstr "Lys"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "" msgstr ""
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 15m" msgid "Load Average 15m"
msgstr "" msgstr ""
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 1m" msgid "Load Average 1m"
msgstr "" msgstr ""
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 5m" msgid "Load Average 5m"
msgstr "" msgstr ""
#. Short label for load average #. Short label for load average
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Load Avg" msgid "Load Avg"
msgstr "" msgstr ""
@@ -579,13 +611,13 @@ msgstr "Log ud"
msgid "Login" msgid "Login"
msgstr "Log ind" msgstr "Log ind"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Login attempt failed" msgid "Login attempt failed"
msgstr "Loginforsøg mislykkedes" msgstr "Loginforsøg mislykkedes"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Logs" msgid "Logs"
msgstr "Logs" msgstr "Logs"
@@ -606,12 +638,12 @@ msgstr "Manuel opsætningsvejledning"
msgid "Max 1 min" msgid "Max 1 min"
msgstr "Maks. 1 min" msgstr "Maks. 1 min"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Memory" msgid "Memory"
msgstr "Hukommelse" msgstr "Hukommelse"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
msgstr "Hukommelsesforbrug" msgstr "Hukommelsesforbrug"
@@ -619,12 +651,12 @@ msgstr "Hukommelsesforbrug"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Hukommelsesforbrug af dockercontainere" msgstr "Hukommelsesforbrug af dockercontainere"
#: src/components/alerts-history-columns.tsx
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
msgid "Name" msgid "Name"
msgstr "Navn" msgstr "Navn"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Net" msgid "Net"
msgstr "Net" msgstr "Net"
@@ -655,8 +687,8 @@ msgid "No systems found."
msgstr "Ingen systemer fundet." msgstr "Ingen systemer fundet."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Notifications" msgid "Notifications"
msgstr "Notifikationer" msgstr "Notifikationer"
@@ -668,9 +700,9 @@ msgstr "OAuth 2 / OIDC understøttelse"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Ved hver genstart vil systemer i databasen blive opdateret til at matche de systemer, der er defineret i filen." msgstr "Ved hver genstart vil systemer i databasen blive opdateret til at matche de systemer, der er defineret i filen."
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu" msgid "Open menu"
msgstr "Åbn menu" msgstr "Åbn menu"
@@ -678,7 +710,7 @@ msgstr "Åbn menu"
msgid "Or continue with" msgid "Or continue with"
msgstr "Eller fortsæt med" msgstr "Eller fortsæt med"
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Overskriv eksisterende alarmer" msgstr "Overskriv eksisterende alarmer"
@@ -713,11 +745,11 @@ msgstr "Adgangskoden skal være mindre end 72 bytes."
msgid "Password reset request received" msgid "Password reset request received"
msgstr "Anmodning om nulstilling af adgangskode modtaget" msgstr "Anmodning om nulstilling af adgangskode modtaget"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Pause" msgid "Pause"
msgstr "Pause" msgstr "Pause"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Paused" msgid "Paused"
msgstr "Sat på pause" msgstr "Sat på pause"
@@ -725,12 +757,12 @@ msgstr "Sat på pause"
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."
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Please check logs for more details." msgid "Please check logs for more details."
msgstr "Tjek logfiler for flere detaljer." msgstr "Tjek logfiler for flere detaljer."
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Please check your credentials and try again" msgid "Please check your credentials and try again"
msgstr "Tjek dine legitimationsoplysninger og prøv igen" msgstr "Tjek dine legitimationsoplysninger og prøv igen"
@@ -792,7 +824,7 @@ msgstr "Nulstil adgangskode"
msgid "Resolved" msgid "Resolved"
msgstr "" msgstr ""
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
msgstr "Genoptag" msgstr "Genoptag"
@@ -808,8 +840,8 @@ msgstr ""
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."
msgstr "Gem adresse ved hjælp af enter eller komma. Lad feltet stå tomt for at deaktivere e-mail-meddelelser." msgstr "Gem adresse ved hjælp af enter eller komma. Lad feltet stå tomt for at deaktivere e-mail-meddelelser."
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Save Settings" msgid "Save Settings"
msgstr "Gem indstillinger" msgstr "Gem indstillinger"
@@ -825,7 +857,7 @@ msgstr "Søg"
msgid "Search for systems or settings..." msgid "Search for systems or settings..."
msgstr "Søg efter systemer eller indstillinger..." msgstr "Søg efter systemer eller indstillinger..."
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "See <0>notification settings</0> to configure how you receive alerts." msgid "See <0>notification settings</0> to configure how you receive alerts."
msgstr "Se <0>meddelelsesindstillinger</0> for at konfigurere, hvordan du modtager alarmer." msgstr "Se <0>meddelelsesindstillinger</0> for at konfigurere, hvordan du modtager alarmer."
@@ -833,6 +865,10 @@ msgstr "Se <0>meddelelsesindstillinger</0> for at konfigurere, hvordan du modtag
msgid "Sent" msgid "Sent"
msgstr "Sendt" msgstr "Sendt"
#: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors."
msgstr "Indstil procentvise tærskler for målerfarver."
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed." msgid "Sets the default time range for charts when a system is viewed."
msgstr "Sætter standardtidsintervallet for diagrammer når et system vises." msgstr "Sætter standardtidsintervallet for diagrammer når et system vises."
@@ -865,7 +901,7 @@ msgstr "Sorter efter"
msgid "State" msgid "State"
msgstr "" msgstr ""
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "Status" msgstr "Status"
@@ -877,12 +913,10 @@ msgstr "Swap plads brugt af systemet"
msgid "Swap Usage" msgid "Swap Usage"
msgstr "Swap forbrug" msgstr "Swap forbrug"
#. System theme
#: src/lib/utils.ts
#: src/components/mode-toggle.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "System" msgid "System"
msgstr "System" msgstr "System"
@@ -903,12 +937,12 @@ msgid "Table"
msgstr "Tabel" msgstr "Tabel"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "Temperatur" msgstr "Temperatur"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Temperature" msgid "Temperature"
msgstr "Temperatur" msgstr "Temperatur"
@@ -932,7 +966,7 @@ msgstr "Test notifikation sendt"
msgid "Then log into the backend and reset your user account password in the users table." msgid "Then log into the backend and reset your user account password in the users table."
msgstr "Log derefter ind på backend og nulstil adgangskoden til din brugerkonto i tabellen brugere." msgstr "Log derefter ind på backend og nulstil adgangskoden til din brugerkonto i tabellen brugere."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database." msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database."
msgstr "Denne handling kan ikke fortrydes. Dette vil permanent slette alle aktuelle elementer for {name} fra databasen." msgstr "Denne handling kan ikke fortrydes. Dette vil permanent slette alle aktuelle elementer for {name} fra databasen."
@@ -967,8 +1001,8 @@ msgid "Token"
msgstr "" msgstr ""
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens & Fingerprints" msgid "Tokens & Fingerprints"
msgstr "" msgstr ""
@@ -980,39 +1014,39 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/lib/utils.ts #: 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 ""
#: src/lib/utils.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 ""
#: src/lib/utils.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 ""
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Udløser når en sensor overstiger en tærskel" msgstr "Udløser når en sensor overstiger en tærskel"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when combined up/down exceeds a threshold" msgid "Triggers when combined up/down exceeds a threshold"
msgstr "Udløses når de kombinerede op/ned overstiger en tærskel" msgstr "Udløses når de kombinerede op/ned overstiger en tærskel"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
msgstr "Udløser når CPU-forbrug overstiger en tærskel" msgstr "Udløser når CPU-forbrug overstiger en tærskel"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
msgstr "Udløser når hukommelsesforbruget overstiger en tærskel" msgstr "Udløser når hukommelsesforbruget overstiger en tærskel"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when status switches between up and down" msgid "Triggers when status switches between up and down"
msgstr "Udløser når status skifter mellem op og ned" msgstr "Udløser når status skifter mellem op og ned"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Udløser når brugen af en disk overstiger en tærskel" msgstr "Udløser når brugen af en disk overstiger en tærskel"
@@ -1025,9 +1059,14 @@ msgstr ""
msgid "Universal token" msgid "Universal token"
msgstr "" msgstr ""
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Ukendt"
#. Context: System is up #. Context: System is up
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Up" msgid "Up"
msgstr "Oppe" msgstr "Oppe"
@@ -1050,13 +1089,13 @@ msgstr "Forbrug"
msgid "Usage of root partition" msgid "Usage of root partition"
msgstr "Brug af rodpartition" msgstr "Brug af rodpartition"
#: src/components/charts/swap-chart.tsx
#: src/components/charts/mem-chart.tsx #: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
msgid "Used" msgid "Used"
msgstr "Brugt" msgstr "Brugt"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Users" msgid "Users"
msgstr "Brugere" msgstr "Brugere"
@@ -1084,6 +1123,14 @@ msgstr "Venter på nok posteringer til at vise"
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "Vil du hjælpe os med at gøre vores oversættelser endnu bedre? Tjek <0>Crowdin</0> for flere detaljer." msgstr "Vil du hjælpe os med at gøre vores oversættelser endnu bedre? Tjek <0>Crowdin</0> for flere detaljer."
#: src/components/routes/settings/general.tsx
msgid "Warning (%)"
msgstr "Advarsel (%)"
#: src/components/routes/settings/general.tsx
msgid "Warning thresholds"
msgstr "Advarselstærskler"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Webhook / Push notifications" msgid "Webhook / Push notifications"
msgstr "Webhook / Push notifikationer" msgstr "Webhook / Push notifikationer"

View File

@@ -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-07-25 22:44\n" "PO-Revision-Date: 2025-08-25 01:15\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"
@@ -69,8 +69,8 @@ msgid "5 min"
msgstr "5 Min" msgstr "5 Min"
#. Table column #. Table column
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions" msgid "Actions"
msgstr "Aktionen" msgstr "Aktionen"
@@ -108,27 +108,27 @@ msgstr "Anzeigeoptionen für Diagramme anpassen."
msgid "Admin" msgid "Admin"
msgstr "Admin" msgstr "Admin"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "Agent" msgstr "Agent"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/layout.tsx
msgid "Alert History" msgid "Alert History"
msgstr "Alarm-Verlauf" msgstr "Alarm-Verlauf"
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alert-button.tsx
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Alerts" msgid "Alerts"
msgstr "Warnungen" msgstr "Warnungen"
#: src/components/alerts/alerts-sheet.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/alerts/alert-button.tsx
msgid "All Systems" msgid "All Systems"
msgstr "Alle Systeme" msgstr "Alle Systeme"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Are you sure you want to delete {name}?" msgid "Are you sure you want to delete {name}?"
msgstr "Möchtest du {name} wirklich löschen?" msgstr "Möchtest du {name} wirklich löschen?"
@@ -148,8 +148,8 @@ msgstr "Durchschnitt"
msgid "Average CPU utilization of containers" msgid "Average CPU utilization of containers"
msgstr "Durchschnittliche CPU-Auslastung der Container" msgstr "Durchschnittliche CPU-Auslastung der Container"
#. placeholder {0}: data.alert.unit #. placeholder {0}: alertData.unit
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Average exceeds <0>{value}{0}</0>" msgid "Average exceeds <0>{value}{0}</0>"
msgstr "Durchschnitt überschreitet <0>{value}{0}</0>" msgstr "Durchschnitt überschreitet <0>{value}{0}</0>"
@@ -166,16 +166,20 @@ msgstr "Durchschnittliche systemweite CPU-Auslastung"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Durchschnittliche Auslastung von {0}" msgstr "Durchschnittliche Auslastung von {0}"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
msgstr "Backups" msgstr "Backups"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Bandwidth" msgid "Bandwidth"
msgstr "Bandbreite" msgstr "Bandbreite"
#: src/components/routes/system.tsx
msgid "Battery"
msgstr "Batterie"
#: 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."
msgstr "Beszel unterstützt OpenID Connect und viele OAuth2-Authentifizierungsanbieter." msgstr "Beszel unterstützt OpenID Connect und viele OAuth2-Authentifizierungsanbieter."
@@ -202,8 +206,8 @@ msgstr "Bytes (KB/s, MB/s, GB/s)"
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "Cache / Puffer" msgstr "Cache / Puffer"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Cancel" msgid "Cancel"
msgstr "Abbrechen" msgstr "Abbrechen"
@@ -223,6 +227,15 @@ msgstr "Anzeigeeinheiten der Werte ändern."
msgid "Change general application options." msgid "Change general application options."
msgstr "Allgemeine Anwendungsoptionen ändern." msgstr "Allgemeine Anwendungsoptionen ändern."
#: src/components/routes/system.tsx
msgid "Charge"
msgstr "Ladung"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Charging"
msgstr "Wird geladen"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Chart options" msgid "Chart options"
msgstr "Diagrammoptionen" msgstr "Diagrammoptionen"
@@ -261,8 +274,8 @@ msgstr "Passwort bestätigen"
msgid "Connection is down" msgid "Connection is down"
msgstr "Verbindung unterbrochen" msgstr "Verbindung unterbrochen"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Continue" msgid "Continue"
msgstr "Fortfahren" msgstr "Fortfahren"
@@ -287,7 +300,7 @@ msgctxt "Environment variables"
msgid "Copy env" msgid "Copy env"
msgstr "Umgebungsvariablen kopieren" msgstr "Umgebungsvariablen kopieren"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy host" msgid "Copy host"
msgstr "Host kopieren" msgstr "Host kopieren"
@@ -296,7 +309,7 @@ msgstr "Host kopieren"
msgid "Copy Linux command" msgid "Copy Linux command"
msgstr "Linux-Befehl kopieren" msgstr "Linux-Befehl kopieren"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy name" msgid "Copy name"
msgstr "Name kopieren" msgstr "Name kopieren"
@@ -316,13 +329,13 @@ msgstr "Kopieren Sie den<0>docker-compose.yml</0> Inhalt für den Agent unten od
msgid "Copy YAML" msgid "Copy YAML"
msgstr "YAML kopieren" msgstr "YAML kopieren"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "CPU-Auslastung" msgstr "CPU-Auslastung"
@@ -335,10 +348,14 @@ msgstr "Konto erstellen"
msgid "Created" msgid "Created"
msgstr "Erstellt" msgstr "Erstellt"
#. Dark theme #: src/components/routes/settings/general.tsx
#: src/components/mode-toggle.tsx msgid "Critical (%)"
msgid "Dark" msgstr "Kritisch (%)"
msgstr "Dunkel"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
msgstr "Aktueller Zustand"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/home.tsx #: src/components/routes/home.tsx
@@ -349,8 +366,8 @@ msgstr "Dashboard"
msgid "Default time period" msgid "Default time period"
msgstr "Standardzeitraum" msgstr "Standardzeitraum"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Delete" msgid "Delete"
msgstr "Löschen" msgstr "Löschen"
@@ -358,7 +375,12 @@ msgstr "Löschen"
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "Fingerabdruck löschen" msgstr "Fingerabdruck löschen"
#: src/components/systems-table/systems-table.tsx #. Context: Battery state
#: src/lib/i18n.ts
msgid "Discharging"
msgstr "Wird entladen"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk" msgid "Disk"
msgstr "Festplatte" msgstr "Festplatte"
@@ -370,9 +392,9 @@ msgstr "Festplatten-I/O"
msgid "Disk unit" msgid "Disk unit"
msgstr "Festplatteneinheit" msgstr "Festplatteneinheit"
#: src/lib/utils.ts
#: src/components/routes/system.tsx
#: src/components/charts/disk-chart.tsx #: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Disk Usage" msgid "Disk Usage"
msgstr "Festplattennutzung" msgstr "Festplattennutzung"
@@ -397,10 +419,10 @@ msgid "Documentation"
msgstr "Dokumentation" msgstr "Dokumentation"
#. Context: System is down #. Context: System is down
#: src/lib/utils.ts
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "Down" msgid "Down"
msgstr "Offline" msgstr "Offline"
@@ -409,12 +431,12 @@ msgid "Duration"
msgstr "Dauer" msgstr "Dauer"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Edit" msgid "Edit"
msgstr "Bearbeiten" msgstr "Bearbeiten"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Email" msgid "Email"
msgstr "E-Mail" msgstr "E-Mail"
@@ -422,6 +444,11 @@ msgstr "E-Mail"
msgid "Email notifications" msgid "Email notifications"
msgstr "E-Mail-Benachrichtigungen" msgstr "E-Mail-Benachrichtigungen"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Empty"
msgstr "Leer"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Enter email address to reset password" msgid "Enter email address to reset password"
msgstr "E-Mail-Adresse eingeben, um das Passwort zurückzusetzen" msgstr "E-Mail-Adresse eingeben, um das Passwort zurückzusetzen"
@@ -430,11 +457,11 @@ msgstr "E-Mail-Adresse eingeben, um das Passwort zurückzusetzen"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "E-Mail-Adresse eingeben..." msgstr "E-Mail-Adresse eingeben..."
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Error" msgid "Error"
msgstr "Fehler" msgstr "Fehler"
@@ -469,8 +496,8 @@ msgstr "Fahrenheit (°F)"
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Authentifizierung fehlgeschlagen" msgstr "Authentifizierung fehlgeschlagen"
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Failed to save settings" msgid "Failed to save settings"
msgstr "Einstellungen konnten nicht gespeichert werden" msgstr "Einstellungen konnten nicht gespeichert werden"
@@ -478,13 +505,13 @@ msgstr "Einstellungen konnten nicht gespeichert werden"
msgid "Failed to send test notification" msgid "Failed to send test notification"
msgstr "Testbenachrichtigung konnte nicht gesendet werden" msgstr "Testbenachrichtigung konnte nicht gesendet werden"
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Failed to update alert" msgid "Failed to update alert"
msgstr "Warnung konnte nicht aktualisiert werden" msgstr "Warnung konnte nicht aktualisiert werden"
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filter..." msgstr "Filter..."
@@ -492,7 +519,7 @@ msgstr "Filter..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "Fingerabdruck" msgstr "Fingerabdruck"
#: src/components/alerts/alerts-system.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}}"
msgstr "Für <0>{min}</0> {min, plural, one {Minute} other {Minuten}}" msgstr "Für <0>{min}</0> {min, plural, one {Minute} other {Minuten}}"
@@ -500,9 +527,14 @@ msgstr "Für <0>{min}</0> {min, plural, one {Minute} other {Minuten}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Passwort vergessen?" msgstr "Passwort vergessen?"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
msgstr "Voll"
#. Context: General settings #. Context: General settings
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/layout.tsx
msgid "General" msgid "General"
msgstr "Allgemein" msgstr "Allgemein"
@@ -524,6 +556,11 @@ msgstr "Homebrew-Befehl"
msgid "Host / IP" msgid "Host / IP"
msgstr "Host / IP" msgstr "Host / IP"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Idle"
msgstr "Untätig"
#: 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."
msgstr "Wenn du das Passwort für dein Administratorkonto verloren hast, kannst du es mit dem folgenden Befehl zurücksetzen." msgstr "Wenn du das Passwort für dein Administratorkonto verloren hast, kannst du es mit dem folgenden Befehl zurücksetzen."
@@ -545,31 +582,26 @@ msgstr "Sprache"
msgid "Layout" msgid "Layout"
msgstr "Anordnung" msgstr "Anordnung"
#. Light theme
#: src/components/mode-toggle.tsx
msgid "Light"
msgstr "Hell"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "Durchschnittliche Systemlast" msgstr "Durchschnittliche Systemlast"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 15m" msgid "Load Average 15m"
msgstr "Durchschnittliche Systemlast 15 Min" msgstr "Durchschnittliche Systemlast 15 Min"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 1m" msgid "Load Average 1m"
msgstr "Durchschnittliche Systemlast 1 Min" msgstr "Durchschnittliche Systemlast 1 Min"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 5m" msgid "Load Average 5m"
msgstr "Durchschnittliche Systemlast 5 Min" msgstr "Durchschnittliche Systemlast 5 Min"
#. Short label for load average #. Short label for load average
#: src/components/systems-table/systems-table.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"
@@ -579,13 +611,13 @@ msgstr "Abmelden"
msgid "Login" msgid "Login"
msgstr "Anmelden" msgstr "Anmelden"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Login attempt failed" msgid "Login attempt failed"
msgstr "Anmeldeversuch fehlgeschlagen" msgstr "Anmeldeversuch fehlgeschlagen"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Logs" msgid "Logs"
msgstr "Protokolle" msgstr "Protokolle"
@@ -606,12 +638,12 @@ msgstr "Anleitung zur manuellen Einrichtung"
msgid "Max 1 min" msgid "Max 1 min"
msgstr "Max 1 Min" msgstr "Max 1 Min"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Memory" msgid "Memory"
msgstr "Arbeitsspeicher" msgstr "Arbeitsspeicher"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
msgstr "Arbeitsspeichernutzung" msgstr "Arbeitsspeichernutzung"
@@ -619,12 +651,12 @@ msgstr "Arbeitsspeichernutzung"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Arbeitsspeichernutzung der Docker-Container" msgstr "Arbeitsspeichernutzung der Docker-Container"
#: src/components/alerts-history-columns.tsx
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
msgid "Name" msgid "Name"
msgstr "Name" msgstr "Name"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Net" msgid "Net"
msgstr "Netz" msgstr "Netz"
@@ -655,8 +687,8 @@ msgid "No systems found."
msgstr "Keine Systeme gefunden." msgstr "Keine Systeme gefunden."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Notifications" msgid "Notifications"
msgstr "Benachrichtigungen" msgstr "Benachrichtigungen"
@@ -668,9 +700,9 @@ msgstr "OAuth 2 / OIDC-Unterstützung"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Bei jedem Neustart werden die Systeme in der Datenbank aktualisiert, um den in der Datei definierten Systemen zu entsprechen." msgstr "Bei jedem Neustart werden die Systeme in der Datenbank aktualisiert, um den in der Datei definierten Systemen zu entsprechen."
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu" msgid "Open menu"
msgstr "Menü öffnen" msgstr "Menü öffnen"
@@ -678,7 +710,7 @@ msgstr "Menü öffnen"
msgid "Or continue with" msgid "Or continue with"
msgstr "Oder fortfahren mit" msgstr "Oder fortfahren mit"
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Bestehende Warnungen überschreiben" msgstr "Bestehende Warnungen überschreiben"
@@ -713,11 +745,11 @@ msgstr "Das Passwort muss weniger als 72 Bytes lang sein."
msgid "Password reset request received" msgid "Password reset request received"
msgstr "Anfrage zum Zurücksetzen des Passworts erhalten" msgstr "Anfrage zum Zurücksetzen des Passworts erhalten"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Pause" msgid "Pause"
msgstr "Pause" msgstr "Pause"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Paused" msgid "Paused"
msgstr "Pausiert" msgstr "Pausiert"
@@ -725,12 +757,12 @@ msgstr "Pausiert"
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."
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Please check logs for more details." msgid "Please check logs for more details."
msgstr "Bitte überprüfe die Protokolle für weitere Details." msgstr "Bitte überprüfe die Protokolle für weitere Details."
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Please check your credentials and try again" msgid "Please check your credentials and try again"
msgstr "Bitte überprüfe deine Anmeldedaten und versuche es erneut" msgstr "Bitte überprüfe deine Anmeldedaten und versuche es erneut"
@@ -752,7 +784,7 @@ msgstr "In der <0>Dokumentation</0> findest du weitere Anweisungen."
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Please sign in to your account" msgid "Please sign in to your account"
msgstr "Bitte melde dich bei beinem Konto an" msgstr "Bitte melde dich bei deinem Konto an"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Port" msgid "Port"
@@ -792,7 +824,7 @@ msgstr "Passwort zurücksetzen"
msgid "Resolved" msgid "Resolved"
msgstr "Gelöst" msgstr "Gelöst"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
msgstr "Fortsetzen" msgstr "Fortsetzen"
@@ -808,8 +840,8 @@ msgstr "Zeilen pro Seite"
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."
msgstr "Adresse mit der Enter-Taste oder Komma speichern. Leer lassen, um E-Mail-Benachrichtigungen zu deaktivieren." msgstr "Adresse mit der Enter-Taste oder Komma speichern. Leer lassen, um E-Mail-Benachrichtigungen zu deaktivieren."
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Save Settings" msgid "Save Settings"
msgstr "Einstellungen speichern" msgstr "Einstellungen speichern"
@@ -825,7 +857,7 @@ msgstr "Suche"
msgid "Search for systems or settings..." msgid "Search for systems or settings..."
msgstr "Nach Systemen oder Einstellungen suchen..." msgstr "Nach Systemen oder Einstellungen suchen..."
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "See <0>notification settings</0> to configure how you receive alerts." msgid "See <0>notification settings</0> to configure how you receive alerts."
msgstr "Siehe <0>Benachrichtigungseinstellungen</0>, um zu konfigurieren, wie du Warnungen erhältst." msgstr "Siehe <0>Benachrichtigungseinstellungen</0>, um zu konfigurieren, wie du Warnungen erhältst."
@@ -833,6 +865,10 @@ msgstr "Siehe <0>Benachrichtigungseinstellungen</0>, um zu konfigurieren, wie du
msgid "Sent" msgid "Sent"
msgstr "Gesendet" msgstr "Gesendet"
#: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors."
msgstr "Prozentuale Schwellenwerte für Zählerfarben festlegen."
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed." msgid "Sets the default time range for charts when a system is viewed."
msgstr "Legt den Standardzeitraum für Diagramme fest, wenn ein System angezeigt wird." msgstr "Legt den Standardzeitraum für Diagramme fest, wenn ein System angezeigt wird."
@@ -865,7 +901,7 @@ msgstr "Sortieren nach"
msgid "State" msgid "State"
msgstr "Status" msgstr "Status"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "Status" msgstr "Status"
@@ -877,12 +913,10 @@ msgstr "Vom System genutzter Swap-Speicher"
msgid "Swap Usage" msgid "Swap Usage"
msgstr "Swap-Nutzung" msgstr "Swap-Nutzung"
#. System theme
#: src/lib/utils.ts
#: src/components/mode-toggle.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "System" msgid "System"
msgstr "System" msgstr "System"
@@ -903,12 +937,12 @@ msgid "Table"
msgstr "Tabelle" msgstr "Tabelle"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "Temperatur" msgstr "Temperatur"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Temperature" msgid "Temperature"
msgstr "Temperatur" msgstr "Temperatur"
@@ -932,7 +966,7 @@ msgstr "Testbenachrichtigung gesendet"
msgid "Then log into the backend and reset your user account password in the users table." msgid "Then log into the backend and reset your user account password in the users table."
msgstr "Melde dich dann im Backend an und setze dein Benutzerkontopasswort in der Benutzertabelle zurück." msgstr "Melde dich dann im Backend an und setze dein Benutzerkontopasswort in der Benutzertabelle zurück."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database." msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database."
msgstr "Diese Aktion kann nicht rückgängig gemacht werden. Dadurch werden alle aktuellen Datensätze für {name} dauerhaft aus der Datenbank gelöscht." msgstr "Diese Aktion kann nicht rückgängig gemacht werden. Dadurch werden alle aktuellen Datensätze für {name} dauerhaft aus der Datenbank gelöscht."
@@ -967,8 +1001,8 @@ msgid "Token"
msgstr "Token" msgstr "Token"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens & Fingerprints" msgid "Tokens & Fingerprints"
msgstr "Tokens & Fingerabdrücke" msgstr "Tokens & Fingerabdrücke"
@@ -980,39 +1014,39 @@ msgstr "Tokens ermöglichen es Agents, sich zu verbinden und zu registrieren. Fi
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokens und Fingerabdrücke werden verwendet, um WebSocket-Verbindungen zum Hub zu authentifizieren." msgstr "Tokens und Fingerabdrücke werden verwendet, um WebSocket-Verbindungen zum Hub zu authentifizieren."
#: src/lib/utils.ts #: 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 "Löst aus, wenn der Lastdurchschnitt der letzten Minute einen Schwellenwert überschreitet" msgstr "Löst aus, wenn der Lastdurchschnitt der letzten Minute einen Schwellenwert überschreitet"
#: src/lib/utils.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 "Löst aus, wenn der Lastdurchschnitt der letzten 15 Minuten einen Schwellenwert überschreitet" msgstr "Löst aus, wenn der Lastdurchschnitt der letzten 15 Minuten einen Schwellenwert überschreitet"
#: src/lib/utils.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 "Löst aus, wenn der Lastdurchschnitt der letzten 5 Minuten einen Schwellenwert überschreitet" msgstr "Löst aus, wenn der Lastdurchschnitt der letzten 5 Minuten einen Schwellenwert überschreitet"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Löst aus, wenn ein Sensor einen Schwellenwert überschreitet" msgstr "Löst aus, wenn ein Sensor einen Schwellenwert überschreitet"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when combined up/down exceeds a threshold" msgid "Triggers when combined up/down exceeds a threshold"
msgstr "Löst aus, wenn die kombinierte Auf-/Abwärtsbewegung einen Schwellenwert überschreitet" msgstr "Löst aus, wenn die kombinierte Auf-/Abwärtsbewegung einen Schwellenwert überschreitet"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
msgstr "Löst aus, wenn die CPU-Auslastung einen Schwellenwert überschreitet" msgstr "Löst aus, wenn die CPU-Auslastung einen Schwellenwert überschreitet"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
msgstr "Löst aus, wenn die Arbeitsspeichernutzung einen Schwellenwert überschreitet" msgstr "Löst aus, wenn die Arbeitsspeichernutzung einen Schwellenwert überschreitet"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when status switches between up and down" msgid "Triggers when status switches between up and down"
msgstr "Löst aus, wenn der Status zwischen online und offline wechselt" msgstr "Löst aus, wenn der Status zwischen online und offline wechselt"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Löst aus, wenn die Nutzung einer Festplatte einen Schwellenwert überschreitet" msgstr "Löst aus, wenn die Nutzung einer Festplatte einen Schwellenwert überschreitet"
@@ -1025,9 +1059,14 @@ msgstr "Einheiten"
msgid "Universal token" msgid "Universal token"
msgstr "Universeller Token" msgstr "Universeller Token"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Unbekannt"
#. Context: System is up #. Context: System is up
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Up" msgid "Up"
msgstr "aktiv" msgstr "aktiv"
@@ -1050,13 +1089,13 @@ msgstr "Nutzung"
msgid "Usage of root partition" msgid "Usage of root partition"
msgstr "Nutzung der Root-Partition" msgstr "Nutzung der Root-Partition"
#: src/components/charts/swap-chart.tsx
#: src/components/charts/mem-chart.tsx #: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
msgid "Used" msgid "Used"
msgstr "Verwendet" msgstr "Verwendet"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Users" msgid "Users"
msgstr "Benutzer" msgstr "Benutzer"
@@ -1084,6 +1123,14 @@ msgstr "Warten auf genügend Datensätze zur Anzeige"
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "Möchtest du uns helfen, unsere Übersetzungen noch besser zu machen? Schau dir <0>Crowdin</0> für weitere Details an." msgstr "Möchtest du uns helfen, unsere Übersetzungen noch besser zu machen? Schau dir <0>Crowdin</0> für weitere Details an."
#: src/components/routes/settings/general.tsx
msgid "Warning (%)"
msgstr "Warnung (%)"
#: src/components/routes/settings/general.tsx
msgid "Warning thresholds"
msgstr "Warnschwellen"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Webhook / Push notifications" msgid "Webhook / Push notifications"
msgstr "Webhook / Push-Benachrichtigungen" msgstr "Webhook / Push-Benachrichtigungen"

View File

@@ -64,8 +64,8 @@ msgid "5 min"
msgstr "5 min" msgstr "5 min"
#. Table column #. Table column
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions" msgid "Actions"
msgstr "Actions" msgstr "Actions"
@@ -103,27 +103,27 @@ msgstr "Adjust display options for charts."
msgid "Admin" msgid "Admin"
msgstr "Admin" msgstr "Admin"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "Agent" msgstr "Agent"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/layout.tsx
msgid "Alert History" msgid "Alert History"
msgstr "Alert History" msgstr "Alert History"
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alert-button.tsx
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Alerts" msgid "Alerts"
msgstr "Alerts" msgstr "Alerts"
#: src/components/alerts/alerts-sheet.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/alerts/alert-button.tsx
msgid "All Systems" msgid "All Systems"
msgstr "All Systems" msgstr "All Systems"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Are you sure you want to delete {name}?" msgid "Are you sure you want to delete {name}?"
msgstr "Are you sure you want to delete {name}?" msgstr "Are you sure you want to delete {name}?"
@@ -143,8 +143,8 @@ msgstr "Average"
msgid "Average CPU utilization of containers" msgid "Average CPU utilization of containers"
msgstr "Average CPU utilization of containers" msgstr "Average CPU utilization of containers"
#. placeholder {0}: data.alert.unit #. placeholder {0}: alertData.unit
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Average exceeds <0>{value}{0}</0>" msgid "Average exceeds <0>{value}{0}</0>"
msgstr "Average exceeds <0>{value}{0}</0>" msgstr "Average exceeds <0>{value}{0}</0>"
@@ -161,16 +161,20 @@ msgstr "Average system-wide CPU utilization"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Average utilization of {0}" msgstr "Average utilization of {0}"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
msgstr "Backups" msgstr "Backups"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Bandwidth" msgid "Bandwidth"
msgstr "Bandwidth" msgstr "Bandwidth"
#: src/components/routes/system.tsx
msgid "Battery"
msgstr "Battery"
#: 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."
msgstr "Beszel supports OpenID Connect and many OAuth2 authentication providers." msgstr "Beszel supports OpenID Connect and many OAuth2 authentication providers."
@@ -197,8 +201,8 @@ msgstr "Bytes (KB/s, MB/s, GB/s)"
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "Cache / Buffers" msgstr "Cache / Buffers"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Cancel" msgid "Cancel"
msgstr "Cancel" msgstr "Cancel"
@@ -218,6 +222,15 @@ msgstr "Change display units for metrics."
msgid "Change general application options." msgid "Change general application options."
msgstr "Change general application options." msgstr "Change general application options."
#: src/components/routes/system.tsx
msgid "Charge"
msgstr "Charge"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Charging"
msgstr "Charging"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Chart options" msgid "Chart options"
msgstr "Chart options" msgstr "Chart options"
@@ -256,8 +269,8 @@ msgstr "Confirm password"
msgid "Connection is down" msgid "Connection is down"
msgstr "Connection is down" msgstr "Connection is down"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Continue" msgid "Continue"
msgstr "Continue" msgstr "Continue"
@@ -282,7 +295,7 @@ msgctxt "Environment variables"
msgid "Copy env" msgid "Copy env"
msgstr "Copy env" msgstr "Copy env"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy host" msgid "Copy host"
msgstr "Copy host" msgstr "Copy host"
@@ -291,7 +304,7 @@ msgstr "Copy host"
msgid "Copy Linux command" msgid "Copy Linux command"
msgstr "Copy Linux command" msgstr "Copy Linux command"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy name" msgid "Copy name"
msgstr "Copy name" msgstr "Copy name"
@@ -311,13 +324,13 @@ msgstr "Copy the<0>docker-compose.yml</0> content for the agent below, or regist
msgid "Copy YAML" msgid "Copy YAML"
msgstr "Copy YAML" msgstr "Copy YAML"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "CPU Usage" msgstr "CPU Usage"
@@ -330,10 +343,14 @@ msgstr "Create account"
msgid "Created" msgid "Created"
msgstr "Created" msgstr "Created"
#. Dark theme #: src/components/routes/settings/general.tsx
#: src/components/mode-toggle.tsx msgid "Critical (%)"
msgid "Dark" msgstr "Critical (%)"
msgstr "Dark"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
msgstr "Current state"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/home.tsx #: src/components/routes/home.tsx
@@ -344,8 +361,8 @@ msgstr "Dashboard"
msgid "Default time period" msgid "Default time period"
msgstr "Default time period" msgstr "Default time period"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Delete" msgid "Delete"
msgstr "Delete" msgstr "Delete"
@@ -353,7 +370,12 @@ msgstr "Delete"
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "Delete fingerprint" msgstr "Delete fingerprint"
#: src/components/systems-table/systems-table.tsx #. Context: Battery state
#: src/lib/i18n.ts
msgid "Discharging"
msgstr "Discharging"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk" msgid "Disk"
msgstr "Disk" msgstr "Disk"
@@ -365,9 +387,9 @@ msgstr "Disk I/O"
msgid "Disk unit" msgid "Disk unit"
msgstr "Disk unit" msgstr "Disk unit"
#: src/lib/utils.ts
#: src/components/routes/system.tsx
#: src/components/charts/disk-chart.tsx #: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Disk Usage" msgid "Disk Usage"
msgstr "Disk Usage" msgstr "Disk Usage"
@@ -392,10 +414,10 @@ msgid "Documentation"
msgstr "Documentation" msgstr "Documentation"
#. Context: System is down #. Context: System is down
#: src/lib/utils.ts
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "Down" msgid "Down"
msgstr "Down" msgstr "Down"
@@ -404,12 +426,12 @@ msgid "Duration"
msgstr "Duration" msgstr "Duration"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Edit" msgid "Edit"
msgstr "Edit" msgstr "Edit"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Email" msgid "Email"
msgstr "Email" msgstr "Email"
@@ -417,6 +439,11 @@ msgstr "Email"
msgid "Email notifications" msgid "Email notifications"
msgstr "Email notifications" msgstr "Email notifications"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Empty"
msgstr "Empty"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Enter email address to reset password" msgid "Enter email address to reset password"
msgstr "Enter email address to reset password" msgstr "Enter email address to reset password"
@@ -425,11 +452,11 @@ msgstr "Enter email address to reset password"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Enter email address..." msgstr "Enter email address..."
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Error" msgid "Error"
msgstr "Error" msgstr "Error"
@@ -464,8 +491,8 @@ msgstr "Fahrenheit (°F)"
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Failed to authenticate" msgstr "Failed to authenticate"
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Failed to save settings" msgid "Failed to save settings"
msgstr "Failed to save settings" msgstr "Failed to save settings"
@@ -473,13 +500,13 @@ msgstr "Failed to save settings"
msgid "Failed to send test notification" msgid "Failed to send test notification"
msgstr "Failed to send test notification" msgstr "Failed to send test notification"
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Failed to update alert" msgid "Failed to update alert"
msgstr "Failed to update alert" msgstr "Failed to update alert"
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filter..." msgstr "Filter..."
@@ -487,7 +514,7 @@ msgstr "Filter..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "Fingerprint" msgstr "Fingerprint"
#: src/components/alerts/alerts-system.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}}"
msgstr "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgstr "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
@@ -495,9 +522,14 @@ msgstr "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Forgot password?" msgstr "Forgot password?"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
msgstr "Full"
#. Context: General settings #. Context: General settings
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/layout.tsx
msgid "General" msgid "General"
msgstr "General" msgstr "General"
@@ -519,6 +551,11 @@ msgstr "Homebrew command"
msgid "Host / IP" msgid "Host / IP"
msgstr "Host / IP" msgstr "Host / IP"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Idle"
msgstr "Idle"
#: 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."
msgstr "If you've lost the password to your admin account, you may reset it using the following command." msgstr "If you've lost the password to your admin account, you may reset it using the following command."
@@ -540,29 +577,24 @@ msgstr "Language"
msgid "Layout" msgid "Layout"
msgstr "Layout" msgstr "Layout"
#. Light theme
#: src/components/mode-toggle.tsx
msgid "Light"
msgstr "Light"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "Load Average" msgstr "Load Average"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 15m" msgid "Load Average 15m"
msgstr "Load Average 15m" msgstr "Load Average 15m"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 1m" msgid "Load Average 1m"
msgstr "Load Average 1m" msgstr "Load Average 1m"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 5m" msgid "Load Average 5m"
msgstr "Load Average 5m" msgstr "Load Average 5m"
#. Short label for load average #. Short label for load average
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Load Avg" msgid "Load Avg"
msgstr "Load Avg" msgstr "Load Avg"
@@ -574,13 +606,13 @@ msgstr "Log Out"
msgid "Login" msgid "Login"
msgstr "Login" msgstr "Login"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Login attempt failed" msgid "Login attempt failed"
msgstr "Login attempt failed" msgstr "Login attempt failed"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Logs" msgid "Logs"
msgstr "Logs" msgstr "Logs"
@@ -601,12 +633,12 @@ msgstr "Manual setup instructions"
msgid "Max 1 min" msgid "Max 1 min"
msgstr "Max 1 min" msgstr "Max 1 min"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Memory" msgid "Memory"
msgstr "Memory" msgstr "Memory"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
msgstr "Memory Usage" msgstr "Memory Usage"
@@ -614,12 +646,12 @@ msgstr "Memory Usage"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Memory usage of docker containers" msgstr "Memory usage of docker containers"
#: src/components/alerts-history-columns.tsx
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
msgid "Name" msgid "Name"
msgstr "Name" msgstr "Name"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Net" msgid "Net"
msgstr "Net" msgstr "Net"
@@ -650,8 +682,8 @@ msgid "No systems found."
msgstr "No systems found." msgstr "No systems found."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Notifications" msgid "Notifications"
msgstr "Notifications" msgstr "Notifications"
@@ -663,9 +695,9 @@ msgstr "OAuth 2 / OIDC support"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "On each restart, systems in the database will be updated to match the systems defined in the file." msgstr "On each restart, systems in the database will be updated to match the systems defined in the file."
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu" msgid "Open menu"
msgstr "Open menu" msgstr "Open menu"
@@ -673,7 +705,7 @@ msgstr "Open menu"
msgid "Or continue with" msgid "Or continue with"
msgstr "Or continue with" msgstr "Or continue with"
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Overwrite existing alerts" msgstr "Overwrite existing alerts"
@@ -708,11 +740,11 @@ msgstr "Password must be less than 72 bytes."
msgid "Password reset request received" msgid "Password reset request received"
msgstr "Password reset request received" msgstr "Password reset request received"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Pause" msgid "Pause"
msgstr "Pause" msgstr "Pause"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Paused" msgid "Paused"
msgstr "Paused" msgstr "Paused"
@@ -720,12 +752,12 @@ msgstr "Paused"
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."
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Please check logs for more details." msgid "Please check logs for more details."
msgstr "Please check logs for more details." msgstr "Please check logs for more details."
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Please check your credentials and try again" msgid "Please check your credentials and try again"
msgstr "Please check your credentials and try again" msgstr "Please check your credentials and try again"
@@ -787,7 +819,7 @@ msgstr "Reset Password"
msgid "Resolved" msgid "Resolved"
msgstr "Resolved" msgstr "Resolved"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
msgstr "Resume" msgstr "Resume"
@@ -803,8 +835,8 @@ msgstr "Rows per page"
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."
msgstr "Save address using enter key or comma. Leave blank to disable email notifications." msgstr "Save address using enter key or comma. Leave blank to disable email notifications."
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Save Settings" msgid "Save Settings"
msgstr "Save Settings" msgstr "Save Settings"
@@ -820,7 +852,7 @@ msgstr "Search"
msgid "Search for systems or settings..." msgid "Search for systems or settings..."
msgstr "Search for systems or settings..." msgstr "Search for systems or settings..."
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "See <0>notification settings</0> to configure how you receive alerts." msgid "See <0>notification settings</0> to configure how you receive alerts."
msgstr "See <0>notification settings</0> to configure how you receive alerts." msgstr "See <0>notification settings</0> to configure how you receive alerts."
@@ -828,6 +860,10 @@ msgstr "See <0>notification settings</0> to configure how you receive alerts."
msgid "Sent" msgid "Sent"
msgstr "Sent" msgstr "Sent"
#: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors."
msgstr "Set percentage thresholds for meter colors."
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed." msgid "Sets the default time range for charts when a system is viewed."
msgstr "Sets the default time range for charts when a system is viewed." msgstr "Sets the default time range for charts when a system is viewed."
@@ -860,7 +896,7 @@ msgstr "Sort By"
msgid "State" msgid "State"
msgstr "State" msgstr "State"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "Status" msgstr "Status"
@@ -872,12 +908,10 @@ msgstr "Swap space used by the system"
msgid "Swap Usage" msgid "Swap Usage"
msgstr "Swap Usage" msgstr "Swap Usage"
#. System theme
#: src/lib/utils.ts
#: src/components/mode-toggle.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "System" msgid "System"
msgstr "System" msgstr "System"
@@ -898,12 +932,12 @@ msgid "Table"
msgstr "Table" msgstr "Table"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "Temp" msgstr "Temp"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Temperature" msgid "Temperature"
msgstr "Temperature" msgstr "Temperature"
@@ -927,7 +961,7 @@ msgstr "Test notification sent"
msgid "Then log into the backend and reset your user account password in the users table." msgid "Then log into the backend and reset your user account password in the users table."
msgstr "Then log into the backend and reset your user account password in the users table." msgstr "Then log into the backend and reset your user account password in the users table."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database." msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database."
msgstr "This action cannot be undone. This will permanently delete all current records for {name} from the database." msgstr "This action cannot be undone. This will permanently delete all current records for {name} from the database."
@@ -962,8 +996,8 @@ msgid "Token"
msgstr "Token" msgstr "Token"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens & Fingerprints" msgid "Tokens & Fingerprints"
msgstr "Tokens & Fingerprints" msgstr "Tokens & Fingerprints"
@@ -975,39 +1009,39 @@ msgstr "Tokens allow agents to connect and register. Fingerprints are stable ide
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgstr "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
#: src/lib/utils.ts #: 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 "Triggers when 1 minute load average exceeds a threshold" msgstr "Triggers when 1 minute load average exceeds a threshold"
#: src/lib/utils.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 "Triggers when 15 minute load average exceeds a threshold" msgstr "Triggers when 15 minute load average exceeds a threshold"
#: src/lib/utils.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 "Triggers when 5 minute load average exceeds a threshold" msgstr "Triggers when 5 minute load average exceeds a threshold"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Triggers when any sensor exceeds a threshold" msgstr "Triggers when any sensor exceeds a threshold"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when combined up/down exceeds a threshold" msgid "Triggers when combined up/down exceeds a threshold"
msgstr "Triggers when combined up/down exceeds a threshold" msgstr "Triggers when combined up/down exceeds a threshold"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
msgstr "Triggers when CPU usage exceeds a threshold" msgstr "Triggers when CPU usage exceeds a threshold"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
msgstr "Triggers when memory usage exceeds a threshold" msgstr "Triggers when memory usage exceeds a threshold"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when status switches between up and down" msgid "Triggers when status switches between up and down"
msgstr "Triggers when status switches between up and down" msgstr "Triggers when status switches between up and down"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Triggers when usage of any disk exceeds a threshold" msgstr "Triggers when usage of any disk exceeds a threshold"
@@ -1020,9 +1054,14 @@ msgstr "Unit preferences"
msgid "Universal token" msgid "Universal token"
msgstr "Universal token" msgstr "Universal token"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Unknown"
#. Context: System is up #. Context: System is up
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Up" msgid "Up"
msgstr "Up" msgstr "Up"
@@ -1045,13 +1084,13 @@ msgstr "Usage"
msgid "Usage of root partition" msgid "Usage of root partition"
msgstr "Usage of root partition" msgstr "Usage of root partition"
#: src/components/charts/swap-chart.tsx
#: src/components/charts/mem-chart.tsx #: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
msgid "Used" msgid "Used"
msgstr "Used" msgstr "Used"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Users" msgid "Users"
msgstr "Users" msgstr "Users"
@@ -1079,6 +1118,14 @@ msgstr "Waiting for enough records to display"
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "Want to help improve our translations? Check <0>Crowdin</0> for details." msgstr "Want to help improve our translations? Check <0>Crowdin</0> for details."
#: src/components/routes/settings/general.tsx
msgid "Warning (%)"
msgstr "Warning (%)"
#: src/components/routes/settings/general.tsx
msgid "Warning thresholds"
msgstr "Warning thresholds"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Webhook / Push notifications" msgid "Webhook / Push notifications"
msgstr "Webhook / Push notifications" msgstr "Webhook / Push notifications"

View File

@@ -69,8 +69,8 @@ msgid "5 min"
msgstr "" msgstr ""
#. Table column #. Table column
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions" msgid "Actions"
msgstr "Acciones" msgstr "Acciones"
@@ -108,27 +108,27 @@ msgstr "Ajustar las opciones de visualización para los gráficos."
msgid "Admin" msgid "Admin"
msgstr "Administrador" msgstr "Administrador"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "Agente" msgstr "Agente"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/layout.tsx
msgid "Alert History" msgid "Alert History"
msgstr "Historial de Alertas" msgstr "Historial de Alertas"
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alert-button.tsx
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Alerts" msgid "Alerts"
msgstr "Alertas" msgstr "Alertas"
#: src/components/alerts/alerts-sheet.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/alerts/alert-button.tsx
msgid "All Systems" msgid "All Systems"
msgstr "Todos los Sistemas" msgstr "Todos los Sistemas"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Are you sure you want to delete {name}?" msgid "Are you sure you want to delete {name}?"
msgstr "¿Está seguro de que desea eliminar {name}?" msgstr "¿Está seguro de que desea eliminar {name}?"
@@ -148,8 +148,8 @@ msgstr "Promedio"
msgid "Average CPU utilization of containers" msgid "Average CPU utilization of containers"
msgstr "Utilización promedio de CPU de los contenedores" msgstr "Utilización promedio de CPU de los contenedores"
#. placeholder {0}: data.alert.unit #. placeholder {0}: alertData.unit
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Average exceeds <0>{value}{0}</0>" msgid "Average exceeds <0>{value}{0}</0>"
msgstr "El promedio excede <0>{value}{0}</0>" msgstr "El promedio excede <0>{value}{0}</0>"
@@ -166,16 +166,20 @@ msgstr "Utilización promedio de CPU del sistema"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Uso promedio de {0}" msgstr "Uso promedio de {0}"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
msgstr "Copias de Seguridad" msgstr "Copias de Seguridad"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Bandwidth" msgid "Bandwidth"
msgstr "Ancho de banda" msgstr "Ancho de banda"
#: src/components/routes/system.tsx
msgid "Battery"
msgstr "Batería"
#: 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."
msgstr "Beszel admite OpenID Connect y muchos proveedores de autenticación OAuth2." msgstr "Beszel admite OpenID Connect y muchos proveedores de autenticación OAuth2."
@@ -202,8 +206,8 @@ msgstr ""
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "Caché / Buffers" msgstr "Caché / Buffers"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Cancel" msgid "Cancel"
msgstr "Cancelar" msgstr "Cancelar"
@@ -223,6 +227,15 @@ msgstr "Cambiar las unidades de visualización de las métricas."
msgid "Change general application options." msgid "Change general application options."
msgstr "Cambiar las opciones generales de la aplicación." msgstr "Cambiar las opciones generales de la aplicación."
#: src/components/routes/system.tsx
msgid "Charge"
msgstr "Carga"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Charging"
msgstr "Cargando"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Chart options" msgid "Chart options"
msgstr "Opciones de Gráficos" msgstr "Opciones de Gráficos"
@@ -261,8 +274,8 @@ msgstr "Confirmar contraseña"
msgid "Connection is down" msgid "Connection is down"
msgstr "La conexión está caída" msgstr "La conexión está caída"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Continue" msgid "Continue"
msgstr "Continuar" msgstr "Continuar"
@@ -287,7 +300,7 @@ msgctxt "Environment variables"
msgid "Copy env" msgid "Copy env"
msgstr "Copiar env" msgstr "Copiar env"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy host" msgid "Copy host"
msgstr "Copiar host" msgstr "Copiar host"
@@ -296,7 +309,7 @@ msgstr "Copiar host"
msgid "Copy Linux command" msgid "Copy Linux command"
msgstr "Copiar comando de Linux" msgstr "Copiar comando de Linux"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy name" msgid "Copy name"
msgstr "Copiar nombre" msgstr "Copiar nombre"
@@ -316,13 +329,13 @@ msgstr "Copia el contenido del<0>docker-compose.yml</0> para el agente a continu
msgid "Copy YAML" msgid "Copy YAML"
msgstr "Copiar YAML" msgstr "Copiar YAML"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "Uso de CPU" msgstr "Uso de CPU"
@@ -335,10 +348,14 @@ msgstr "Crear cuenta"
msgid "Created" msgid "Created"
msgstr "Creado" msgstr "Creado"
#. Dark theme #: src/components/routes/settings/general.tsx
#: src/components/mode-toggle.tsx msgid "Critical (%)"
msgid "Dark" msgstr "Crítico (%)"
msgstr "Oscuro"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
msgstr "Estado actual"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/home.tsx #: src/components/routes/home.tsx
@@ -349,8 +366,8 @@ msgstr "Tablero"
msgid "Default time period" msgid "Default time period"
msgstr "Período de tiempo predeterminado" msgstr "Período de tiempo predeterminado"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Delete" msgid "Delete"
msgstr "Eliminar" msgstr "Eliminar"
@@ -358,7 +375,12 @@ msgstr "Eliminar"
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "Eliminar huella digital" msgstr "Eliminar huella digital"
#: src/components/systems-table/systems-table.tsx #. Context: Battery state
#: src/lib/i18n.ts
msgid "Discharging"
msgstr "Descargando"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk" msgid "Disk"
msgstr "Disco" msgstr "Disco"
@@ -370,9 +392,9 @@ msgstr "E/S de Disco"
msgid "Disk unit" msgid "Disk unit"
msgstr "Unidad de disco" msgstr "Unidad de disco"
#: src/lib/utils.ts
#: src/components/routes/system.tsx
#: src/components/charts/disk-chart.tsx #: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Disk Usage" msgid "Disk Usage"
msgstr "Uso de Disco" msgstr "Uso de Disco"
@@ -397,10 +419,10 @@ msgid "Documentation"
msgstr "Documentación" msgstr "Documentación"
#. Context: System is down #. Context: System is down
#: src/lib/utils.ts
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "Down" msgid "Down"
msgstr "Abajo" msgstr "Abajo"
@@ -409,12 +431,12 @@ msgid "Duration"
msgstr "Duración" msgstr "Duración"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Edit" msgid "Edit"
msgstr "Editar" msgstr "Editar"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Email" msgid "Email"
msgstr "Correo electrónico" msgstr "Correo electrónico"
@@ -422,6 +444,11 @@ msgstr "Correo electrónico"
msgid "Email notifications" msgid "Email notifications"
msgstr "Notificaciones por correo" msgstr "Notificaciones por correo"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Empty"
msgstr "Vacía"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Enter email address to reset password" msgid "Enter email address to reset password"
msgstr "Ingrese la dirección de correo electrónico para restablecer la contraseña" msgstr "Ingrese la dirección de correo electrónico para restablecer la contraseña"
@@ -430,11 +457,11 @@ msgstr "Ingrese la dirección de correo electrónico para restablecer la contras
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Ingrese dirección de correo..." msgstr "Ingrese dirección de correo..."
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Error" msgid "Error"
msgstr "Error" msgstr "Error"
@@ -469,8 +496,8 @@ msgstr ""
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Error al autenticar" msgstr "Error al autenticar"
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Failed to save settings" msgid "Failed to save settings"
msgstr "Error al guardar la configuración" msgstr "Error al guardar la configuración"
@@ -478,13 +505,13 @@ msgstr "Error al guardar la configuración"
msgid "Failed to send test notification" msgid "Failed to send test notification"
msgstr "Error al enviar la notificación de prueba" msgstr "Error al enviar la notificación de prueba"
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Failed to update alert" msgid "Failed to update alert"
msgstr "Error al actualizar la alerta" msgstr "Error al actualizar la alerta"
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filtrar..." msgstr "Filtrar..."
@@ -492,7 +519,7 @@ msgstr "Filtrar..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "Huella dactilar" msgstr "Huella dactilar"
#: src/components/alerts/alerts-system.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}}"
msgstr "Por <0>{min}</0> {min, plural, one {minuto} other {minutos}}" msgstr "Por <0>{min}</0> {min, plural, one {minuto} other {minutos}}"
@@ -500,9 +527,14 @@ msgstr "Por <0>{min}</0> {min, plural, one {minuto} other {minutos}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "¿Olvidó su contraseña?" msgstr "¿Olvidó su contraseña?"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
msgstr "Llena"
#. Context: General settings #. Context: General settings
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/layout.tsx
msgid "General" msgid "General"
msgstr "General" msgstr "General"
@@ -524,6 +556,11 @@ msgstr "Comando Homebrew"
msgid "Host / IP" msgid "Host / IP"
msgstr "Host / IP" msgstr "Host / IP"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Idle"
msgstr "Inactiva"
#: 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."
msgstr "Si ha perdido la contraseña de su cuenta de administrador, puede restablecerla usando el siguiente comando." msgstr "Si ha perdido la contraseña de su cuenta de administrador, puede restablecerla usando el siguiente comando."
@@ -545,29 +582,24 @@ msgstr "Idioma"
msgid "Layout" msgid "Layout"
msgstr "Diseño" msgstr "Diseño"
#. Light theme
#: src/components/mode-toggle.tsx
msgid "Light"
msgstr "Claro"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "Carga Media" msgstr "Carga Media"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 15m" msgid "Load Average 15m"
msgstr "Carga media 15m" msgstr "Carga media 15m"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 1m" msgid "Load Average 1m"
msgstr "Carga media 1m" msgstr "Carga media 1m"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 5m" msgid "Load Average 5m"
msgstr "Carga media 5m" msgstr "Carga media 5m"
#. Short label for load average #. Short label for load average
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Load Avg" msgid "Load Avg"
msgstr "Carga media" msgstr "Carga media"
@@ -579,13 +611,13 @@ msgstr "Cerrar Sesión"
msgid "Login" msgid "Login"
msgstr "Iniciar sesión" msgstr "Iniciar sesión"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Login attempt failed" msgid "Login attempt failed"
msgstr "Intento de inicio de sesión fallido" msgstr "Intento de inicio de sesión fallido"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Logs" msgid "Logs"
msgstr "Registros" msgstr "Registros"
@@ -606,12 +638,12 @@ msgstr "Instrucciones manuales de configuración"
msgid "Max 1 min" msgid "Max 1 min"
msgstr "Máx 1 min" msgstr "Máx 1 min"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Memory" msgid "Memory"
msgstr "Memoria" msgstr "Memoria"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
msgstr "Uso de Memoria" msgstr "Uso de Memoria"
@@ -619,12 +651,12 @@ msgstr "Uso de Memoria"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Uso de memoria de los contenedores de Docker" msgstr "Uso de memoria de los contenedores de Docker"
#: src/components/alerts-history-columns.tsx
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
msgid "Name" msgid "Name"
msgstr "Nombre" msgstr "Nombre"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Net" msgid "Net"
msgstr "Red" msgstr "Red"
@@ -655,8 +687,8 @@ msgid "No systems found."
msgstr "No se encontraron sistemas." msgstr "No se encontraron sistemas."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Notifications" msgid "Notifications"
msgstr "Notificaciones" msgstr "Notificaciones"
@@ -668,9 +700,9 @@ msgstr "Soporte para OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "En cada reinicio, los sistemas en la base de datos se actualizarán para coincidir con los sistemas definidos en el archivo." msgstr "En cada reinicio, los sistemas en la base de datos se actualizarán para coincidir con los sistemas definidos en el archivo."
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu" msgid "Open menu"
msgstr "Abrir menú" msgstr "Abrir menú"
@@ -678,7 +710,7 @@ msgstr "Abrir menú"
msgid "Or continue with" msgid "Or continue with"
msgstr "O continuar con" msgstr "O continuar con"
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Sobrescribir alertas existentes" msgstr "Sobrescribir alertas existentes"
@@ -713,11 +745,11 @@ msgstr "La contraseña debe ser menor de 72 bytes."
msgid "Password reset request received" msgid "Password reset request received"
msgstr "Solicitud de restablecimiento de contraseña recibida" msgstr "Solicitud de restablecimiento de contraseña recibida"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Pause" msgid "Pause"
msgstr "Pausar" msgstr "Pausar"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Paused" msgid "Paused"
msgstr "Pausado" msgstr "Pausado"
@@ -725,12 +757,12 @@ msgstr "Pausado"
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."
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Please check logs for more details." msgid "Please check logs for more details."
msgstr "Por favor, revise los registros para más detalles." msgstr "Por favor, revise los registros para más detalles."
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Please check your credentials and try again" msgid "Please check your credentials and try again"
msgstr "Por favor, verifique sus credenciales e intente de nuevo" msgstr "Por favor, verifique sus credenciales e intente de nuevo"
@@ -792,7 +824,7 @@ msgstr "Restablecer Contraseña"
msgid "Resolved" msgid "Resolved"
msgstr "Resuelto" msgstr "Resuelto"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
msgstr "Reanudar" msgstr "Reanudar"
@@ -808,8 +840,8 @@ msgstr "Filas por página"
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."
msgstr "Guarde la dirección usando la tecla enter o coma. Deje en blanco para desactivar las notificaciones por correo." msgstr "Guarde la dirección usando la tecla enter o coma. Deje en blanco para desactivar las notificaciones por correo."
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Save Settings" msgid "Save Settings"
msgstr "Guardar Configuración" msgstr "Guardar Configuración"
@@ -825,7 +857,7 @@ msgstr "Buscar"
msgid "Search for systems or settings..." msgid "Search for systems or settings..."
msgstr "Buscar sistemas o configuraciones..." msgstr "Buscar sistemas o configuraciones..."
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "See <0>notification settings</0> to configure how you receive alerts." msgid "See <0>notification settings</0> to configure how you receive alerts."
msgstr "Consulte <0>configuración de notificaciones</0> para configurar cómo recibe alertas." msgstr "Consulte <0>configuración de notificaciones</0> para configurar cómo recibe alertas."
@@ -833,6 +865,10 @@ msgstr "Consulte <0>configuración de notificaciones</0> para configurar cómo r
msgid "Sent" msgid "Sent"
msgstr "Enviado" msgstr "Enviado"
#: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors."
msgstr "Establecer umbrales de porcentaje para los colores de los medidores."
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed." msgid "Sets the default time range for charts when a system is viewed."
msgstr "Establece el rango de tiempo predeterminado para los gráficos cuando se visualiza un sistema." msgstr "Establece el rango de tiempo predeterminado para los gráficos cuando se visualiza un sistema."
@@ -865,7 +901,7 @@ msgstr "Ordenar por"
msgid "State" msgid "State"
msgstr "Estado" msgstr "Estado"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "Estado" msgstr "Estado"
@@ -877,12 +913,10 @@ msgstr "Espacio de swap utilizado por el sistema"
msgid "Swap Usage" msgid "Swap Usage"
msgstr "Uso de Swap" msgstr "Uso de Swap"
#. System theme
#: src/lib/utils.ts
#: src/components/mode-toggle.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "System" msgid "System"
msgstr "Sistema" msgstr "Sistema"
@@ -903,12 +937,12 @@ msgid "Table"
msgstr "Tabla" msgstr "Tabla"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "Temperatura" msgstr "Temperatura"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Temperature" msgid "Temperature"
msgstr "Temperatura" msgstr "Temperatura"
@@ -932,7 +966,7 @@ msgstr "Notificación de prueba enviada"
msgid "Then log into the backend and reset your user account password in the users table." msgid "Then log into the backend and reset your user account password in the users table."
msgstr "Luego inicie sesión en el backend y restablezca la contraseña de su cuenta de usuario en la tabla de usuarios." msgstr "Luego inicie sesión en el backend y restablezca la contraseña de su cuenta de usuario en la tabla de usuarios."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database." msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database."
msgstr "Esta acción no se puede deshacer. Esto eliminará permanentemente todos los registros actuales de {name} de la base de datos." msgstr "Esta acción no se puede deshacer. Esto eliminará permanentemente todos los registros actuales de {name} de la base de datos."
@@ -967,8 +1001,8 @@ msgid "Token"
msgstr "" msgstr ""
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens & Fingerprints" msgid "Tokens & Fingerprints"
msgstr "Tokens y Huellas Digitales" msgstr "Tokens y Huellas Digitales"
@@ -980,39 +1014,39 @@ msgstr "Los tokens permiten que los agentes se conecten y registren. Las huellas
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Los tokens y las huellas digitales se utilizan para autenticar las conexiones WebSocket al hub." msgstr "Los tokens y las huellas digitales se utilizan para autenticar las conexiones WebSocket al hub."
#: src/lib/utils.ts #: 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 "Se activa cuando la carga media de 1 minuto supera un umbral" msgstr "Se activa cuando la carga media de 1 minuto supera un umbral"
#: src/lib/utils.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 "Se activa cuando la carga media de 15 minutos supera un umbral" msgstr "Se activa cuando la carga media de 15 minutos supera un umbral"
#: src/lib/utils.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 "Se activa cuando la carga media de 5 minutos supera un umbral" msgstr "Se activa cuando la carga media de 5 minutos supera un umbral"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Se activa cuando cualquier sensor supera un umbral" msgstr "Se activa cuando cualquier sensor supera un umbral"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when combined up/down exceeds a threshold" msgid "Triggers when combined up/down exceeds a threshold"
msgstr "Se activa cuando la suma de subida/bajada supera un umbral" msgstr "Se activa cuando la suma de subida/bajada supera un umbral"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
msgstr "Se activa cuando el uso de CPU supera un umbral" msgstr "Se activa cuando el uso de CPU supera un umbral"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
msgstr "Se activa cuando el uso de memoria supera un umbral" msgstr "Se activa cuando el uso de memoria supera un umbral"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when status switches between up and down" msgid "Triggers when status switches between up and down"
msgstr "Se activa cuando el estado cambia entre activo e inactivo" msgstr "Se activa cuando el estado cambia entre activo e inactivo"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Se activa cuando el uso de cualquier disco supera un umbral" msgstr "Se activa cuando el uso de cualquier disco supera un umbral"
@@ -1025,9 +1059,14 @@ msgstr "Preferencias de unidad"
msgid "Universal token" msgid "Universal token"
msgstr "Token universal" msgstr "Token universal"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Desconocida"
#. Context: System is up #. Context: System is up
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Up" msgid "Up"
msgstr "Activo" msgstr "Activo"
@@ -1050,13 +1089,13 @@ msgstr "Uso"
msgid "Usage of root partition" msgid "Usage of root partition"
msgstr "Uso de la partición raíz" msgstr "Uso de la partición raíz"
#: src/components/charts/swap-chart.tsx
#: src/components/charts/mem-chart.tsx #: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
msgid "Used" msgid "Used"
msgstr "Usado" msgstr "Usado"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Users" msgid "Users"
msgstr "Usuarios" msgstr "Usuarios"
@@ -1084,6 +1123,14 @@ msgstr "Esperando suficientes registros para mostrar"
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "¿Quieres ayudarnos a mejorar nuestras traducciones? Consulta <0>Crowdin</0> para más detalles." msgstr "¿Quieres ayudarnos a mejorar nuestras traducciones? Consulta <0>Crowdin</0> para más detalles."
#: src/components/routes/settings/general.tsx
msgid "Warning (%)"
msgstr "Advertencia (%)"
#: src/components/routes/settings/general.tsx
msgid "Warning thresholds"
msgstr "Umbrales de advertencia"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Webhook / Push notifications" msgid "Webhook / Push notifications"
msgstr "Notificaciones Webhook / Push" msgstr "Notificaciones Webhook / Push"

View File

@@ -69,8 +69,8 @@ msgid "5 min"
msgstr "۵ دقیقه" msgstr "۵ دقیقه"
#. Table column #. Table column
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions" msgid "Actions"
msgstr "عملیات" msgstr "عملیات"
@@ -108,27 +108,27 @@ msgstr "تنظیم گزینه‌های نمایش برای نمودارها."
msgid "Admin" msgid "Admin"
msgstr "مدیر" msgstr "مدیر"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "عامل" msgstr "عامل"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/layout.tsx
msgid "Alert History" msgid "Alert History"
msgstr "تاریخچه هشدارها" msgstr "تاریخچه هشدارها"
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alert-button.tsx
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Alerts" msgid "Alerts"
msgstr "هشدارها" msgstr "هشدارها"
#: src/components/alerts/alerts-sheet.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/alerts/alert-button.tsx
msgid "All Systems" msgid "All Systems"
msgstr "همه سیستم‌ها" msgstr "همه سیستم‌ها"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Are you sure you want to delete {name}?" msgid "Are you sure you want to delete {name}?"
msgstr "آیا مطمئن هستید که می‌خواهید {name} را حذف کنید؟" msgstr "آیا مطمئن هستید که می‌خواهید {name} را حذف کنید؟"
@@ -148,8 +148,8 @@ msgstr "میانگین"
msgid "Average CPU utilization of containers" msgid "Average CPU utilization of containers"
msgstr "میانگین استفاده از CPU کانتینرها" msgstr "میانگین استفاده از CPU کانتینرها"
#. placeholder {0}: data.alert.unit #. placeholder {0}: alertData.unit
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Average exceeds <0>{value}{0}</0>" msgid "Average exceeds <0>{value}{0}</0>"
msgstr "میانگین از <0>{value}{0}</0> فراتر رفته است" msgstr "میانگین از <0>{value}{0}</0> فراتر رفته است"
@@ -166,16 +166,20 @@ msgstr "میانگین استفاده از CPU در کل سیستم"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "میانگین استفاده از {0}" msgstr "میانگین استفاده از {0}"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
msgstr "پشتیبان‌گیری‌ها" msgstr "پشتیبان‌گیری‌ها"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Bandwidth" msgid "Bandwidth"
msgstr "پهنای باند" msgstr "پهنای باند"
#: src/components/routes/system.tsx
msgid "Battery"
msgstr "باتری"
#: 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."
msgstr "بِزل از OpenID Connect و بسیاری از ارائه‌دهندگان احراز هویت OAuth2 پشتیبانی می‌کند." msgstr "بِزل از OpenID Connect و بسیاری از ارائه‌دهندگان احراز هویت OAuth2 پشتیبانی می‌کند."
@@ -202,8 +206,8 @@ msgstr "بایت (کیلوبایت بر ثانیه، مگابایت بر ثان
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "حافظه پنهان / بافرها" msgstr "حافظه پنهان / بافرها"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Cancel" msgid "Cancel"
msgstr "لغو" msgstr "لغو"
@@ -223,6 +227,15 @@ msgstr "تغییر واحدهای نمایش برای معیارها."
msgid "Change general application options." msgid "Change general application options."
msgstr "تغییر گزینه‌های کلی برنامه." msgstr "تغییر گزینه‌های کلی برنامه."
#: src/components/routes/system.tsx
msgid "Charge"
msgstr "شارژ"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Charging"
msgstr "در حال شارژ"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Chart options" msgid "Chart options"
msgstr "گزینه‌های نمودار" msgstr "گزینه‌های نمودار"
@@ -261,8 +274,8 @@ msgstr "تأیید رمز عبور"
msgid "Connection is down" msgid "Connection is down"
msgstr "اتصال قطع است" msgstr "اتصال قطع است"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Continue" msgid "Continue"
msgstr "ادامه" msgstr "ادامه"
@@ -287,7 +300,7 @@ msgctxt "Environment variables"
msgid "Copy env" msgid "Copy env"
msgstr "کپی متغیرهای محیط" msgstr "کپی متغیرهای محیط"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy host" msgid "Copy host"
msgstr "کپی میزبان" msgstr "کپی میزبان"
@@ -296,7 +309,7 @@ msgstr "کپی میزبان"
msgid "Copy Linux command" msgid "Copy Linux command"
msgstr "کپی دستور لینوکس" msgstr "کپی دستور لینوکس"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy name" msgid "Copy name"
msgstr "کپی نام" msgstr "کپی نام"
@@ -316,13 +329,13 @@ msgstr "محتوای <0>docker-compose.yml</0> عامل زیر را کپی کن
msgid "Copy YAML" msgid "Copy YAML"
msgstr "کپی YAML" msgstr "کپی YAML"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "پردازنده" msgstr "پردازنده"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "میزان استفاده از پردازنده" msgstr "میزان استفاده از پردازنده"
@@ -335,10 +348,14 @@ msgstr "ایجاد حساب کاربری"
msgid "Created" msgid "Created"
msgstr "ایجاد شده" msgstr "ایجاد شده"
#. Dark theme #: src/components/routes/settings/general.tsx
#: src/components/mode-toggle.tsx msgid "Critical (%)"
msgid "Dark" msgstr "بحرانی (%)"
msgstr "تیره"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
msgstr "وضعیت فعلی"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/home.tsx #: src/components/routes/home.tsx
@@ -349,8 +366,8 @@ msgstr "داشبورد"
msgid "Default time period" msgid "Default time period"
msgstr "بازه زمانی پیش‌فرض" msgstr "بازه زمانی پیش‌فرض"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Delete" msgid "Delete"
msgstr "حذف" msgstr "حذف"
@@ -358,7 +375,12 @@ msgstr "حذف"
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "حذف اثر انگشت" msgstr "حذف اثر انگشت"
#: src/components/systems-table/systems-table.tsx #. Context: Battery state
#: src/lib/i18n.ts
msgid "Discharging"
msgstr "در حال تخلیه"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk" msgid "Disk"
msgstr "دیسک" msgstr "دیسک"
@@ -370,9 +392,9 @@ msgstr "ورودی/خروجی دیسک"
msgid "Disk unit" msgid "Disk unit"
msgstr "واحد دیسک" msgstr "واحد دیسک"
#: src/lib/utils.ts
#: src/components/routes/system.tsx
#: src/components/charts/disk-chart.tsx #: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Disk Usage" msgid "Disk Usage"
msgstr "میزان استفاده از دیسک" msgstr "میزان استفاده از دیسک"
@@ -397,10 +419,10 @@ msgid "Documentation"
msgstr "مستندات" msgstr "مستندات"
#. Context: System is down #. Context: System is down
#: src/lib/utils.ts
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "Down" msgid "Down"
msgstr "قطع" msgstr "قطع"
@@ -409,12 +431,12 @@ msgid "Duration"
msgstr "مدت زمان" msgstr "مدت زمان"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Edit" msgid "Edit"
msgstr "ویرایش" msgstr "ویرایش"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Email" msgid "Email"
msgstr "ایمیل" msgstr "ایمیل"
@@ -422,6 +444,11 @@ msgstr "ایمیل"
msgid "Email notifications" msgid "Email notifications"
msgstr "اعلان‌های ایمیلی" msgstr "اعلان‌های ایمیلی"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Empty"
msgstr "خالی"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Enter email address to reset password" msgid "Enter email address to reset password"
msgstr "آدرس ایمیل را برای بازنشانی رمز عبور وارد کنید" msgstr "آدرس ایمیل را برای بازنشانی رمز عبور وارد کنید"
@@ -430,11 +457,11 @@ msgstr "آدرس ایمیل را برای بازنشانی رمز عبور وا
msgid "Enter email address..." msgid "Enter email address..."
msgstr "آدرس ایمیل را وارد کنید..." msgstr "آدرس ایمیل را وارد کنید..."
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Error" msgid "Error"
msgstr "خطا" msgstr "خطا"
@@ -469,8 +496,8 @@ msgstr "فارنهایت (°F)"
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "احراز هویت ناموفق بود" msgstr "احراز هویت ناموفق بود"
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Failed to save settings" msgid "Failed to save settings"
msgstr "ذخیره تنظیمات ناموفق بود" msgstr "ذخیره تنظیمات ناموفق بود"
@@ -478,13 +505,13 @@ msgstr "ذخیره تنظیمات ناموفق بود"
msgid "Failed to send test notification" msgid "Failed to send test notification"
msgstr "ارسال اعلان آزمایشی ناموفق بود" msgstr "ارسال اعلان آزمایشی ناموفق بود"
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Failed to update alert" msgid "Failed to update alert"
msgstr "به‌روزرسانی هشدار ناموفق بود" msgstr "به‌روزرسانی هشدار ناموفق بود"
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "فیلتر..." msgstr "فیلتر..."
@@ -492,7 +519,7 @@ msgstr "فیلتر..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "اثر انگشت" msgstr "اثر انگشت"
#: src/components/alerts/alerts-system.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}}"
msgstr "برای <0>{min}</0> {min, plural, one {دقیقه} other {دقیقه}}" msgstr "برای <0>{min}</0> {min, plural, one {دقیقه} other {دقیقه}}"
@@ -500,9 +527,14 @@ msgstr "برای <0>{min}</0> {min, plural, one {دقیقه} other {دقیقه}}
msgid "Forgot password?" msgid "Forgot password?"
msgstr "رمز عبور را فراموش کرده‌اید؟" msgstr "رمز عبور را فراموش کرده‌اید؟"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
msgstr "پر"
#. Context: General settings #. Context: General settings
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/layout.tsx
msgid "General" msgid "General"
msgstr "عمومی" msgstr "عمومی"
@@ -524,6 +556,11 @@ msgstr "دستور Homebrew"
msgid "Host / IP" msgid "Host / IP"
msgstr "میزبان / IP" msgstr "میزبان / IP"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Idle"
msgstr "بیکار"
#: 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."
msgstr "اگر رمز عبور حساب مدیر خود را گم کرده‌اید، می‌توانید آن را با استفاده از دستور زیر بازنشانی کنید." msgstr "اگر رمز عبور حساب مدیر خود را گم کرده‌اید، می‌توانید آن را با استفاده از دستور زیر بازنشانی کنید."
@@ -545,29 +582,24 @@ msgstr "زبان"
msgid "Layout" msgid "Layout"
msgstr "طرح‌بندی" msgstr "طرح‌بندی"
#. Light theme
#: src/components/mode-toggle.tsx
msgid "Light"
msgstr "روشن"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "میانگین بار" msgstr "میانگین بار"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 15m" msgid "Load Average 15m"
msgstr "میانگین بار ۱۵ دقیقه" msgstr "میانگین بار ۱۵ دقیقه"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 1m" msgid "Load Average 1m"
msgstr "میانگین بار ۱ دقیقه" msgstr "میانگین بار ۱ دقیقه"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 5m" msgid "Load Average 5m"
msgstr "میانگین بار ۵ دقیقه" msgstr "میانگین بار ۵ دقیقه"
#. Short label for load average #. Short label for load average
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Load Avg" msgid "Load Avg"
msgstr "میانگین بار" msgstr "میانگین بار"
@@ -579,13 +611,13 @@ msgstr "خروج"
msgid "Login" msgid "Login"
msgstr "ورود" msgstr "ورود"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Login attempt failed" msgid "Login attempt failed"
msgstr "تلاش برای ورود ناموفق بود" msgstr "تلاش برای ورود ناموفق بود"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Logs" msgid "Logs"
msgstr "لاگ‌ها" msgstr "لاگ‌ها"
@@ -606,12 +638,12 @@ msgstr "دستورالعمل‌های راه‌اندازی دستی"
msgid "Max 1 min" msgid "Max 1 min"
msgstr "حداکثر ۱ دقیقه" msgstr "حداکثر ۱ دقیقه"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Memory" msgid "Memory"
msgstr "حافظه" msgstr "حافظه"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
msgstr "میزان استفاده از حافظه" msgstr "میزان استفاده از حافظه"
@@ -619,12 +651,12 @@ msgstr "میزان استفاده از حافظه"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "میزان استفاده از حافظه کانتینرهای داکر" msgstr "میزان استفاده از حافظه کانتینرهای داکر"
#: src/components/alerts-history-columns.tsx
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
msgid "Name" msgid "Name"
msgstr "نام" msgstr "نام"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Net" msgid "Net"
msgstr "شبکه" msgstr "شبکه"
@@ -655,8 +687,8 @@ msgid "No systems found."
msgstr "هیچ سیستمی یافت نشد." msgstr "هیچ سیستمی یافت نشد."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Notifications" msgid "Notifications"
msgstr "اعلان‌ها" msgstr "اعلان‌ها"
@@ -668,9 +700,9 @@ msgstr "پشتیبانی از OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "در هر بار راه‌اندازی مجدد، سیستم‌های موجود در پایگاه داده با سیستم‌های تعریف شده در فایل مطابقت داده می‌شوند." msgstr "در هر بار راه‌اندازی مجدد، سیستم‌های موجود در پایگاه داده با سیستم‌های تعریف شده در فایل مطابقت داده می‌شوند."
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu" msgid "Open menu"
msgstr "باز کردن منو" msgstr "باز کردن منو"
@@ -678,7 +710,7 @@ msgstr "باز کردن منو"
msgid "Or continue with" msgid "Or continue with"
msgstr "یا ادامه با" msgstr "یا ادامه با"
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "بازنویسی هشدارهای موجود" msgstr "بازنویسی هشدارهای موجود"
@@ -713,11 +745,11 @@ msgstr "رمز عبور باید کمتر از ۷۲ بایت باشد."
msgid "Password reset request received" msgid "Password reset request received"
msgstr "درخواست بازنشانی رمز عبور دریافت شد" msgstr "درخواست بازنشانی رمز عبور دریافت شد"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Pause" msgid "Pause"
msgstr "توقف" msgstr "توقف"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Paused" msgid "Paused"
msgstr "مکث شده" msgstr "مکث شده"
@@ -725,12 +757,12 @@ msgstr "مکث شده"
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>."
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Please check logs for more details." msgid "Please check logs for more details."
msgstr "لطفاً برای جزئیات بیشتر، لاگ‌ها را بررسی کنید." msgstr "لطفاً برای جزئیات بیشتر، لاگ‌ها را بررسی کنید."
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Please check your credentials and try again" msgid "Please check your credentials and try again"
msgstr "لطفاً اعتبارنامه‌های خود را بررسی کرده و دوباره تلاش کنید." msgstr "لطفاً اعتبارنامه‌های خود را بررسی کرده و دوباره تلاش کنید."
@@ -792,7 +824,7 @@ msgstr "بازنشانی رمز عبور"
msgid "Resolved" msgid "Resolved"
msgstr "حل شده" msgstr "حل شده"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
msgstr "ادامه" msgstr "ادامه"
@@ -808,8 +840,8 @@ msgstr "ردیف در هر صفحه"
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."
msgstr "آدرس را با استفاده از کلید Enter یا کاما ذخیره کنید. برای غیرفعال کردن اعلان‌های ایمیلی، خالی بگذارید." msgstr "آدرس را با استفاده از کلید Enter یا کاما ذخیره کنید. برای غیرفعال کردن اعلان‌های ایمیلی، خالی بگذارید."
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Save Settings" msgid "Save Settings"
msgstr "ذخیره تنظیمات" msgstr "ذخیره تنظیمات"
@@ -825,7 +857,7 @@ msgstr "جستجو"
msgid "Search for systems or settings..." msgid "Search for systems or settings..."
msgstr "جستجو برای سیستم‌ها یا تنظیمات..." msgstr "جستجو برای سیستم‌ها یا تنظیمات..."
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "See <0>notification settings</0> to configure how you receive alerts." msgid "See <0>notification settings</0> to configure how you receive alerts."
msgstr "برای پیکربندی نحوه دریافت هشدارها، به <0>تنظیمات اعلان</0> مراجعه کنید." msgstr "برای پیکربندی نحوه دریافت هشدارها، به <0>تنظیمات اعلان</0> مراجعه کنید."
@@ -833,6 +865,10 @@ msgstr "برای پیکربندی نحوه دریافت هشدارها، به <0
msgid "Sent" msgid "Sent"
msgstr "ارسال شد" msgstr "ارسال شد"
#: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors."
msgstr "آستانه های درصدی را برای رنگ های متر تنظیم کنید."
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed." msgid "Sets the default time range for charts when a system is viewed."
msgstr "بازه زمانی پیش‌فرض برای نمودارها هنگام مشاهده یک سیستم را تعیین می‌کند." msgstr "بازه زمانی پیش‌فرض برای نمودارها هنگام مشاهده یک سیستم را تعیین می‌کند."
@@ -865,7 +901,7 @@ msgstr "مرتب‌سازی بر اساس"
msgid "State" msgid "State"
msgstr "وضعیت" msgstr "وضعیت"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "وضعیت" msgstr "وضعیت"
@@ -877,12 +913,10 @@ msgstr "فضای Swap استفاده شده توسط سیستم"
msgid "Swap Usage" msgid "Swap Usage"
msgstr "میزان استفاده از Swap" msgstr "میزان استفاده از Swap"
#. System theme
#: src/lib/utils.ts
#: src/components/mode-toggle.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "System" msgid "System"
msgstr "سیستم" msgstr "سیستم"
@@ -903,12 +937,12 @@ msgid "Table"
msgstr "جدول" msgstr "جدول"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "دما" msgstr "دما"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Temperature" msgid "Temperature"
msgstr "دما" msgstr "دما"
@@ -932,7 +966,7 @@ msgstr "اعلان آزمایشی ارسال شد"
msgid "Then log into the backend and reset your user account password in the users table." msgid "Then log into the backend and reset your user account password in the users table."
msgstr "سپس وارد بخش پشتیبان شوید و رمز عبور حساب کاربری خود را در جدول کاربران بازنشانی کنید." msgstr "سپس وارد بخش پشتیبان شوید و رمز عبور حساب کاربری خود را در جدول کاربران بازنشانی کنید."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database." msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database."
msgstr "این عمل قابل برگشت نیست. این کار تمام رکوردهای فعلی {name} را برای همیشه از پایگاه داده حذف خواهد کرد." msgstr "این عمل قابل برگشت نیست. این کار تمام رکوردهای فعلی {name} را برای همیشه از پایگاه داده حذف خواهد کرد."
@@ -967,8 +1001,8 @@ msgid "Token"
msgstr "توکن" msgstr "توکن"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens & Fingerprints" msgid "Tokens & Fingerprints"
msgstr "توکن‌ها و اثرات انگشت" msgstr "توکن‌ها و اثرات انگشت"
@@ -980,39 +1014,39 @@ msgstr "توکن‌ها به عامل‌ها اجازه اتصال و ثبت‌
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "توکن‌ها و اثرات انگشت برای احراز هویت اتصالات WebSocket به هاب استفاده می‌شوند." msgstr "توکن‌ها و اثرات انگشت برای احراز هویت اتصالات WebSocket به هاب استفاده می‌شوند."
#: src/lib/utils.ts #: 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 "هنگامی که میانگین بار ۱ دقیقه‌ای از یک آستانه فراتر رود، فعال می‌شود"
#: src/lib/utils.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 "هنگامی که میانگین بار ۱۵ دقیقه‌ای از یک آستانه فراتر رود، فعال می‌شود"
#: src/lib/utils.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 "هنگامی که میانگین بار ۵ دقیقه‌ای از یک آستانه فراتر رود، فعال می‌شود"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "هنگامی که هر حسگری از یک آستانه فراتر رود، فعال می‌شود" msgstr "هنگامی که هر حسگری از یک آستانه فراتر رود، فعال می‌شود"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when combined up/down exceeds a threshold" msgid "Triggers when combined up/down exceeds a threshold"
msgstr "هنگامی که مجموع بالا/پایین از یک آستانه فراتر رود، فعال می‌شود" msgstr "هنگامی که مجموع بالا/پایین از یک آستانه فراتر رود، فعال می‌شود"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
msgstr "هنگامی که میزان استفاده از CPU از یک آستانه فراتر رود، فعال می‌شود" msgstr "هنگامی که میزان استفاده از CPU از یک آستانه فراتر رود، فعال می‌شود"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
msgstr "هنگامی که میزان استفاده از حافظه از یک آستانه فراتر رود، فعال می‌شود" msgstr "هنگامی که میزان استفاده از حافظه از یک آستانه فراتر رود، فعال می‌شود"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when status switches between up and down" msgid "Triggers when status switches between up and down"
msgstr "هنگامی که وضعیت بین بالا و پایین تغییر می‌کند، فعال می‌شود" msgstr "هنگامی که وضعیت بین بالا و پایین تغییر می‌کند، فعال می‌شود"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "هنگامی که استفاده از هر دیسکی از یک آستانه فراتر رود، فعال می‌شود" msgstr "هنگامی که استفاده از هر دیسکی از یک آستانه فراتر رود، فعال می‌شود"
@@ -1025,9 +1059,14 @@ msgstr "تنظیمات واحدها"
msgid "Universal token" msgid "Universal token"
msgstr "توکن جهانی" msgstr "توکن جهانی"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "ناشناخته"
#. Context: System is up #. Context: System is up
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Up" msgid "Up"
msgstr "فعال" msgstr "فعال"
@@ -1050,13 +1089,13 @@ msgstr "میزان استفاده"
msgid "Usage of root partition" msgid "Usage of root partition"
msgstr "میزان استفاده از پارتیشن ریشه" msgstr "میزان استفاده از پارتیشن ریشه"
#: src/components/charts/swap-chart.tsx
#: src/components/charts/mem-chart.tsx #: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
msgid "Used" msgid "Used"
msgstr "استفاده شده" msgstr "استفاده شده"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Users" msgid "Users"
msgstr "کاربران" msgstr "کاربران"
@@ -1084,6 +1123,14 @@ msgstr "در انتظار رکوردهای کافی برای نمایش"
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "می‌خواهید به ما کمک کنید تا ترجمه‌های خود را بهتر کنیم؟ برای جزئیات بیشتر به <0>Crowdin</0> مراجعه کنید." msgstr "می‌خواهید به ما کمک کنید تا ترجمه‌های خود را بهتر کنیم؟ برای جزئیات بیشتر به <0>Crowdin</0> مراجعه کنید."
#: src/components/routes/settings/general.tsx
msgid "Warning (%)"
msgstr "هشدار (%)"
#: src/components/routes/settings/general.tsx
msgid "Warning thresholds"
msgstr "آستانه های هشدار"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Webhook / Push notifications" msgid "Webhook / Push notifications"
msgstr "اعلان‌های Webhook / Push" msgstr "اعلان‌های Webhook / Push"

View File

@@ -69,8 +69,8 @@ msgid "5 min"
msgstr "" msgstr ""
#. Table column #. Table column
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions" msgid "Actions"
msgstr "Actions" msgstr "Actions"
@@ -108,27 +108,27 @@ msgstr "Ajuster les options d'affichage pour les graphiques."
msgid "Admin" msgid "Admin"
msgstr "Admin" msgstr "Admin"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "Agent" msgstr "Agent"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/layout.tsx
msgid "Alert History" msgid "Alert History"
msgstr "" msgstr ""
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alert-button.tsx
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Alerts" msgid "Alerts"
msgstr "Alertes" msgstr "Alertes"
#: src/components/alerts/alerts-sheet.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/alerts/alert-button.tsx
msgid "All Systems" msgid "All Systems"
msgstr "Tous les systèmes" msgstr "Tous les systèmes"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Are you sure you want to delete {name}?" msgid "Are you sure you want to delete {name}?"
msgstr "Êtes-vous sûr de vouloir supprimer {name} ?" msgstr "Êtes-vous sûr de vouloir supprimer {name} ?"
@@ -148,8 +148,8 @@ msgstr "Moyenne"
msgid "Average CPU utilization of containers" msgid "Average CPU utilization of containers"
msgstr "Utilisation moyenne du CPU des conteneurs" msgstr "Utilisation moyenne du CPU des conteneurs"
#. placeholder {0}: data.alert.unit #. placeholder {0}: alertData.unit
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Average exceeds <0>{value}{0}</0>" msgid "Average exceeds <0>{value}{0}</0>"
msgstr "La moyenne dépasse <0>{value}{0}</0>" msgstr "La moyenne dépasse <0>{value}{0}</0>"
@@ -166,16 +166,20 @@ msgstr "Utilisation moyenne du CPU à l'échelle du système"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Utilisation moyenne de {0}" msgstr "Utilisation moyenne de {0}"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
msgstr "Sauvegardes" msgstr "Sauvegardes"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Bandwidth" msgid "Bandwidth"
msgstr "Bande passante" msgstr "Bande passante"
#: src/components/routes/system.tsx
msgid "Battery"
msgstr "Batterie"
#: 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."
msgstr "Beszel prend en charge OpenID Connect et de nombreux fournisseurs d'authentification OAuth2." msgstr "Beszel prend en charge OpenID Connect et de nombreux fournisseurs d'authentification OAuth2."
@@ -202,8 +206,8 @@ msgstr ""
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "Cache / Tampons" msgstr "Cache / Tampons"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Cancel" msgid "Cancel"
msgstr "Annuler" msgstr "Annuler"
@@ -223,6 +227,15 @@ msgstr ""
msgid "Change general application options." msgid "Change general application options."
msgstr "Modifier les options générales de l'application." msgstr "Modifier les options générales de l'application."
#: src/components/routes/system.tsx
msgid "Charge"
msgstr "Charge"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Charging"
msgstr "En charge"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Chart options" msgid "Chart options"
msgstr "Options de graphique" msgstr "Options de graphique"
@@ -261,8 +274,8 @@ msgstr "Confirmer le mot de passe"
msgid "Connection is down" msgid "Connection is down"
msgstr "" msgstr ""
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Continue" msgid "Continue"
msgstr "Continuer" msgstr "Continuer"
@@ -287,7 +300,7 @@ msgctxt "Environment variables"
msgid "Copy env" msgid "Copy env"
msgstr "Copier env" msgstr "Copier env"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy host" msgid "Copy host"
msgstr "Copier l'hôte" msgstr "Copier l'hôte"
@@ -296,7 +309,7 @@ msgstr "Copier l'hôte"
msgid "Copy Linux command" msgid "Copy Linux command"
msgstr "Copier la commande Linux" msgstr "Copier la commande Linux"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy name" msgid "Copy name"
msgstr "Copier le nom" msgstr "Copier le nom"
@@ -316,13 +329,13 @@ msgstr "Copiez le contenu du<0>docker-compose.yml</0> pour l'agent ci-dessous, o
msgid "Copy YAML" msgid "Copy YAML"
msgstr "Copier YAML" msgstr "Copier YAML"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "Utilisation du CPU" msgstr "Utilisation du CPU"
@@ -335,10 +348,14 @@ msgstr "Créer un compte"
msgid "Created" msgid "Created"
msgstr "" msgstr ""
#. Dark theme #: src/components/routes/settings/general.tsx
#: src/components/mode-toggle.tsx msgid "Critical (%)"
msgid "Dark" msgstr "Critique (%)"
msgstr "Sombre"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
msgstr "État actuel"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/home.tsx #: src/components/routes/home.tsx
@@ -349,8 +366,8 @@ msgstr "Tableau de bord"
msgid "Default time period" msgid "Default time period"
msgstr "Période par défaut" msgstr "Période par défaut"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Delete" msgid "Delete"
msgstr "Supprimer" msgstr "Supprimer"
@@ -358,7 +375,12 @@ msgstr "Supprimer"
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "Supprimer l'empreinte" msgstr "Supprimer l'empreinte"
#: src/components/systems-table/systems-table.tsx #. Context: Battery state
#: src/lib/i18n.ts
msgid "Discharging"
msgstr "En décharge"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk" msgid "Disk"
msgstr "Disque" msgstr "Disque"
@@ -370,9 +392,9 @@ msgstr "Entrée/Sortie disque"
msgid "Disk unit" msgid "Disk unit"
msgstr "" msgstr ""
#: src/lib/utils.ts
#: src/components/routes/system.tsx
#: src/components/charts/disk-chart.tsx #: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Disk Usage" msgid "Disk Usage"
msgstr "Utilisation du disque" msgstr "Utilisation du disque"
@@ -397,10 +419,10 @@ msgid "Documentation"
msgstr "Documentation" msgstr "Documentation"
#. Context: System is down #. Context: System is down
#: src/lib/utils.ts
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "Down" msgid "Down"
msgstr "Injoignable" msgstr "Injoignable"
@@ -409,12 +431,12 @@ msgid "Duration"
msgstr "" msgstr ""
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Edit" msgid "Edit"
msgstr "Éditer" msgstr "Éditer"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Email" msgid "Email"
msgstr "Email" msgstr "Email"
@@ -422,6 +444,11 @@ msgstr "Email"
msgid "Email notifications" msgid "Email notifications"
msgstr "Notifications par email" msgstr "Notifications par email"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Empty"
msgstr "Vide"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Enter email address to reset password" msgid "Enter email address to reset password"
msgstr "Entrez l'adresse email pour réinitialiser le mot de passe" msgstr "Entrez l'adresse email pour réinitialiser le mot de passe"
@@ -430,11 +457,11 @@ msgstr "Entrez l'adresse email pour réinitialiser le mot de passe"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Entrez l'adresse email..." msgstr "Entrez l'adresse email..."
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Error" msgid "Error"
msgstr "Erreur" msgstr "Erreur"
@@ -469,8 +496,8 @@ msgstr ""
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Échec de l'authentification" msgstr "Échec de l'authentification"
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Failed to save settings" msgid "Failed to save settings"
msgstr "Échec de l'enregistrement des paramètres" msgstr "Échec de l'enregistrement des paramètres"
@@ -478,13 +505,13 @@ msgstr "Échec de l'enregistrement des paramètres"
msgid "Failed to send test notification" msgid "Failed to send test notification"
msgstr "Échec de l'envoi de la notification de test" msgstr "Échec de l'envoi de la notification de test"
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Failed to update alert" msgid "Failed to update alert"
msgstr "Échec de la mise à jour de l'alerte" msgstr "Échec de la mise à jour de l'alerte"
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filtrer..." msgstr "Filtrer..."
@@ -492,7 +519,7 @@ msgstr "Filtrer..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "" msgstr ""
#: src/components/alerts/alerts-system.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}}"
msgstr "Pour <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgstr "Pour <0>{min}</0> {min, plural, one {minute} other {minutes}}"
@@ -500,9 +527,14 @@ msgstr "Pour <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Mot de passe oublié ?" msgstr "Mot de passe oublié ?"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
msgstr "Pleine"
#. Context: General settings #. Context: General settings
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/layout.tsx
msgid "General" msgid "General"
msgstr "Général" msgstr "Général"
@@ -524,6 +556,11 @@ msgstr "Commande Homebrew"
msgid "Host / IP" msgid "Host / IP"
msgstr "Hôte / IP" msgstr "Hôte / IP"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Idle"
msgstr "Inactive"
#: 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."
msgstr "Si vous avez perdu le mot de passe de votre compte administrateur, vous pouvez le réinitialiser en utilisant la commande suivante." msgstr "Si vous avez perdu le mot de passe de votre compte administrateur, vous pouvez le réinitialiser en utilisant la commande suivante."
@@ -545,29 +582,24 @@ msgstr "Langue"
msgid "Layout" msgid "Layout"
msgstr "Disposition" msgstr "Disposition"
#. Light theme
#: src/components/mode-toggle.tsx
msgid "Light"
msgstr "Clair"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "" msgstr ""
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 15m" msgid "Load Average 15m"
msgstr "" msgstr ""
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 1m" msgid "Load Average 1m"
msgstr "" msgstr ""
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 5m" msgid "Load Average 5m"
msgstr "" msgstr ""
#. Short label for load average #. Short label for load average
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Load Avg" msgid "Load Avg"
msgstr "" msgstr ""
@@ -579,13 +611,13 @@ msgstr "Déconnexion"
msgid "Login" msgid "Login"
msgstr "Connexion" msgstr "Connexion"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Login attempt failed" msgid "Login attempt failed"
msgstr "Échec de la tentative de connexion" msgstr "Échec de la tentative de connexion"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Logs" msgid "Logs"
msgstr "Journaux" msgstr "Journaux"
@@ -606,12 +638,12 @@ msgstr "Guide pour une installation manuelle"
msgid "Max 1 min" msgid "Max 1 min"
msgstr "Max 1 min" msgstr "Max 1 min"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Memory" msgid "Memory"
msgstr "Mémoire" msgstr "Mémoire"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
msgstr "Utilisation de la mémoire" msgstr "Utilisation de la mémoire"
@@ -619,12 +651,12 @@ msgstr "Utilisation de la mémoire"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Utilisation de la mémoire des conteneurs Docker" msgstr "Utilisation de la mémoire des conteneurs Docker"
#: src/components/alerts-history-columns.tsx
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
msgid "Name" msgid "Name"
msgstr "Nom" msgstr "Nom"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Net" msgid "Net"
msgstr "Net" msgstr "Net"
@@ -655,8 +687,8 @@ msgid "No systems found."
msgstr "Aucun système trouvé." msgstr "Aucun système trouvé."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Notifications" msgid "Notifications"
msgstr "Notifications" msgstr "Notifications"
@@ -668,9 +700,9 @@ msgstr "Support OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "À chaque redémarrage, les systèmes dans la base de données seront mis à jour pour correspondre aux systèmes définis dans le fichier." msgstr "À chaque redémarrage, les systèmes dans la base de données seront mis à jour pour correspondre aux systèmes définis dans le fichier."
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu" msgid "Open menu"
msgstr "Ouvrir le menu" msgstr "Ouvrir le menu"
@@ -678,7 +710,7 @@ msgstr "Ouvrir le menu"
msgid "Or continue with" msgid "Or continue with"
msgstr "Ou continuer avec" msgstr "Ou continuer avec"
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Écraser les alertes existantes" msgstr "Écraser les alertes existantes"
@@ -713,11 +745,11 @@ msgstr "Le mot de passe doit être inférieur à 72 Octets."
msgid "Password reset request received" msgid "Password reset request received"
msgstr "Demande de réinitialisation du mot de passe reçue" msgstr "Demande de réinitialisation du mot de passe reçue"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Pause" msgid "Pause"
msgstr "Pause" msgstr "Pause"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Paused" msgid "Paused"
msgstr "En pause" msgstr "En pause"
@@ -725,12 +757,12 @@ msgstr "En pause"
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."
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Please check logs for more details." msgid "Please check logs for more details."
msgstr "Veuillez vérifier les journaux pour plus de détails." msgstr "Veuillez vérifier les journaux pour plus de détails."
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Please check your credentials and try again" msgid "Please check your credentials and try again"
msgstr "Veuillez vérifier vos identifiants et réessayer" msgstr "Veuillez vérifier vos identifiants et réessayer"
@@ -792,7 +824,7 @@ msgstr "Réinitialiser le mot de passe"
msgid "Resolved" msgid "Resolved"
msgstr "" msgstr ""
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
msgstr "Reprendre" msgstr "Reprendre"
@@ -808,8 +840,8 @@ msgstr ""
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."
msgstr "Enregistrez l'adresse en utilisant la touche Entrée ou la virgule. Laissez vide pour désactiver les notifications par email." msgstr "Enregistrez l'adresse en utilisant la touche Entrée ou la virgule. Laissez vide pour désactiver les notifications par email."
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Save Settings" msgid "Save Settings"
msgstr "Enregistrer les paramètres" msgstr "Enregistrer les paramètres"
@@ -825,7 +857,7 @@ msgstr "Recherche"
msgid "Search for systems or settings..." msgid "Search for systems or settings..."
msgstr "Rechercher des systèmes ou des paramètres..." msgstr "Rechercher des systèmes ou des paramètres..."
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "See <0>notification settings</0> to configure how you receive alerts." msgid "See <0>notification settings</0> to configure how you receive alerts."
msgstr "Voir les <0>paramètres de notification</0> pour configurer comment vous recevez les alertes." msgstr "Voir les <0>paramètres de notification</0> pour configurer comment vous recevez les alertes."
@@ -833,6 +865,10 @@ msgstr "Voir les <0>paramètres de notification</0> pour configurer comment vous
msgid "Sent" msgid "Sent"
msgstr "Envoyé" msgstr "Envoyé"
#: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors."
msgstr "Définir des seuils de pourcentage pour les couleurs des compteurs."
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed." msgid "Sets the default time range for charts when a system is viewed."
msgstr "Définit la plage de temps par défaut pour les graphiques lorsqu'un système est consulté." msgstr "Définit la plage de temps par défaut pour les graphiques lorsqu'un système est consulté."
@@ -865,7 +901,7 @@ msgstr "Trier par"
msgid "State" msgid "State"
msgstr "" msgstr ""
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "Statut" msgstr "Statut"
@@ -877,12 +913,10 @@ msgstr "Espace Swap utilisé par le système"
msgid "Swap Usage" msgid "Swap Usage"
msgstr "Utilisation du swap" msgstr "Utilisation du swap"
#. System theme
#: src/lib/utils.ts
#: src/components/mode-toggle.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "System" msgid "System"
msgstr "Système" msgstr "Système"
@@ -903,12 +937,12 @@ msgid "Table"
msgstr "Tableau" msgstr "Tableau"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "Temp." msgstr "Temp."
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Temperature" msgid "Temperature"
msgstr "Température" msgstr "Température"
@@ -932,7 +966,7 @@ msgstr "Notification de test envoyée"
msgid "Then log into the backend and reset your user account password in the users table." msgid "Then log into the backend and reset your user account password in the users table."
msgstr "Ensuite, connectez-vous au backend et réinitialisez le mot de passe de votre compte utilisateur dans la table des utilisateurs." msgstr "Ensuite, connectez-vous au backend et réinitialisez le mot de passe de votre compte utilisateur dans la table des utilisateurs."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database." msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database."
msgstr "Cette action ne peut pas être annulée. Cela supprimera définitivement tous les enregistrements actuels pour {name} de la base de données." msgstr "Cette action ne peut pas être annulée. Cela supprimera définitivement tous les enregistrements actuels pour {name} de la base de données."
@@ -967,8 +1001,8 @@ msgid "Token"
msgstr "" msgstr ""
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens & Fingerprints" msgid "Tokens & Fingerprints"
msgstr "Tokens et Empreintes" msgstr "Tokens et Empreintes"
@@ -980,39 +1014,39 @@ msgstr "Les tokens permettent aux agents de se connecter et de s'enregistrer. Le
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Les tokens et les empreintes sont utilisés pour authentifier les connexions WebSocket vers le hub." msgstr "Les tokens et les empreintes sont utilisés pour authentifier les connexions WebSocket vers le hub."
#: src/lib/utils.ts #: 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 ""
#: src/lib/utils.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 ""
#: src/lib/utils.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 ""
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Déclenchement lorsque tout capteur dépasse un seuil" msgstr "Déclenchement lorsque tout capteur dépasse un seuil"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when combined up/down exceeds a threshold" msgid "Triggers when combined up/down exceeds a threshold"
msgstr "Déclenchement lorsque le montant/descendant combinée dépasse un seuil" msgstr "Déclenchement lorsque le montant/descendant combinée dépasse un seuil"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
msgstr "Déclenchement lorsque l'utilisation du CPU dépasse un seuil" msgstr "Déclenchement lorsque l'utilisation du CPU dépasse un seuil"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
msgstr "Déclenchement lorsque l'utilisation de la mémoire dépasse un seuil" msgstr "Déclenchement lorsque l'utilisation de la mémoire dépasse un seuil"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when status switches between up and down" msgid "Triggers when status switches between up and down"
msgstr "Se déclenche lorsque le statut passe de \"Joignable\" à \"Injoignable\"" msgstr "Se déclenche lorsque le statut passe de \"Joignable\" à \"Injoignable\""
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Déclenchement lorsque l'utilisation de tout disque dépasse un seuil" msgstr "Déclenchement lorsque l'utilisation de tout disque dépasse un seuil"
@@ -1025,9 +1059,14 @@ msgstr ""
msgid "Universal token" msgid "Universal token"
msgstr "Token universel" msgstr "Token universel"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Inconnue"
#. Context: System is up #. Context: System is up
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Up" msgid "Up"
msgstr "Joignable" msgstr "Joignable"
@@ -1050,13 +1089,13 @@ msgstr "Utilisation"
msgid "Usage of root partition" msgid "Usage of root partition"
msgstr "Utilisation de la partition racine" msgstr "Utilisation de la partition racine"
#: src/components/charts/swap-chart.tsx
#: src/components/charts/mem-chart.tsx #: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
msgid "Used" msgid "Used"
msgstr "Utilisé" msgstr "Utilisé"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Users" msgid "Users"
msgstr "Utilisateurs" msgstr "Utilisateurs"
@@ -1084,6 +1123,14 @@ msgstr "En attente de suffisamment d'enregistrements à afficher"
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "Vous voulez nous aider à améliorer nos traductions ? Consultez <0>Crowdin</0> pour plus de détails." msgstr "Vous voulez nous aider à améliorer nos traductions ? Consultez <0>Crowdin</0> pour plus de détails."
#: src/components/routes/settings/general.tsx
msgid "Warning (%)"
msgstr "Avertissement (%)"
#: src/components/routes/settings/general.tsx
msgid "Warning thresholds"
msgstr "Seuils d'avertissement"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Webhook / Push notifications" msgid "Webhook / Push notifications"
msgstr "Notifications Webhook / Push" msgstr "Notifications Webhook / Push"

View File

@@ -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-07-25 22:44\n" "PO-Revision-Date: 2025-08-25 01:16\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"
@@ -69,8 +69,8 @@ msgid "5 min"
msgstr "" msgstr ""
#. Table column #. Table column
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions" msgid "Actions"
msgstr "Akcije" msgstr "Akcije"
@@ -108,27 +108,27 @@ msgstr "Podesite opcije prikaza za grafikone."
msgid "Admin" msgid "Admin"
msgstr "Admin" msgstr "Admin"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "Agent" msgstr "Agent"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/layout.tsx
msgid "Alert History" msgid "Alert History"
msgstr "" msgstr ""
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alert-button.tsx
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Alerts" msgid "Alerts"
msgstr "Upozorenja" msgstr "Upozorenja"
#: src/components/alerts/alerts-sheet.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/alerts/alert-button.tsx
msgid "All Systems" msgid "All Systems"
msgstr "Svi Sistemi" msgstr "Svi Sistemi"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Are you sure you want to delete {name}?" msgid "Are you sure you want to delete {name}?"
msgstr "Jeste li sigurni da želite izbrisati {name}?" msgstr "Jeste li sigurni da želite izbrisati {name}?"
@@ -148,8 +148,8 @@ msgstr "Prosjek"
msgid "Average CPU utilization of containers" msgid "Average CPU utilization of containers"
msgstr "Prosječna iskorištenost procesora u spremnicima" msgstr "Prosječna iskorištenost procesora u spremnicima"
#. placeholder {0}: data.alert.unit #. placeholder {0}: alertData.unit
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Average exceeds <0>{value}{0}</0>" msgid "Average exceeds <0>{value}{0}</0>"
msgstr "Prosjek premašuje <0>{value}{0}</0>" msgstr "Prosjek premašuje <0>{value}{0}</0>"
@@ -166,16 +166,20 @@ msgstr "Prosječna iskorištenost procesora na cijelom sustavu"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "" msgstr ""
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
msgstr "Sigurnosne kopije" msgstr "Sigurnosne kopije"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Bandwidth" msgid "Bandwidth"
msgstr "Propusnost" msgstr "Propusnost"
#: src/components/routes/system.tsx
msgid "Battery"
msgstr "Baterija"
#: 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."
msgstr "Beszel podržava OpenID Connect i mnoge druge OAuth2 davatalje autentifikacije." msgstr "Beszel podržava OpenID Connect i mnoge druge OAuth2 davatalje autentifikacije."
@@ -202,8 +206,8 @@ msgstr ""
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "Predmemorija / Međuspremnici" msgstr "Predmemorija / Međuspremnici"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Cancel" msgid "Cancel"
msgstr "Otkaži" msgstr "Otkaži"
@@ -223,6 +227,15 @@ msgstr ""
msgid "Change general application options." msgid "Change general application options."
msgstr "Promijenite opće opcije aplikacije." msgstr "Promijenite opće opcije aplikacije."
#: src/components/routes/system.tsx
msgid "Charge"
msgstr "Punjenje"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Charging"
msgstr "Puni se"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Chart options" msgid "Chart options"
msgstr "Opcije grafikona" msgstr "Opcije grafikona"
@@ -261,8 +274,8 @@ msgstr "Potvrdite lozinku"
msgid "Connection is down" msgid "Connection is down"
msgstr "" msgstr ""
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Continue" msgid "Continue"
msgstr "Nastavite" msgstr "Nastavite"
@@ -287,7 +300,7 @@ msgctxt "Environment variables"
msgid "Copy env" msgid "Copy env"
msgstr "" msgstr ""
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy host" msgid "Copy host"
msgstr "Kopiraj hosta" msgstr "Kopiraj hosta"
@@ -296,7 +309,7 @@ msgstr "Kopiraj hosta"
msgid "Copy Linux command" msgid "Copy Linux command"
msgstr "Kopiraj Linux komandu" msgstr "Kopiraj Linux komandu"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy name" msgid "Copy name"
msgstr "Kopiraj naziv" msgstr "Kopiraj naziv"
@@ -316,13 +329,13 @@ msgstr ""
msgid "Copy YAML" msgid "Copy YAML"
msgstr "" msgstr ""
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "Procesor" msgstr "Procesor"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "Iskorištenost procesora" msgstr "Iskorištenost procesora"
@@ -335,10 +348,14 @@ msgstr "Napravite račun"
msgid "Created" msgid "Created"
msgstr "" msgstr ""
#. Dark theme #: src/components/routes/settings/general.tsx
#: src/components/mode-toggle.tsx msgid "Critical (%)"
msgid "Dark" msgstr "Kritično (%)"
msgstr "Tamno"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
msgstr "Trenutno stanje"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/home.tsx #: src/components/routes/home.tsx
@@ -349,8 +366,8 @@ msgstr "Nadzorna ploča"
msgid "Default time period" msgid "Default time period"
msgstr "Zadano vremensko razdoblje" msgstr "Zadano vremensko razdoblje"
#: src/components/systems-table/systems-table.tsx
#: 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
msgid "Delete" msgid "Delete"
msgstr "Izbriši" msgstr "Izbriši"
@@ -358,7 +375,12 @@ msgstr "Izbriši"
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "" msgstr ""
#: src/components/systems-table/systems-table.tsx #. Context: Battery state
#: src/lib/i18n.ts
msgid "Discharging"
msgstr "Prazni se"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk" msgid "Disk"
msgstr "Disk" msgstr "Disk"
@@ -370,9 +392,9 @@ msgstr "Disk I/O"
msgid "Disk unit" msgid "Disk unit"
msgstr "" msgstr ""
#: src/lib/utils.ts
#: src/components/routes/system.tsx
#: src/components/charts/disk-chart.tsx #: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Disk Usage" msgid "Disk Usage"
msgstr "Iskorištenost Diska" msgstr "Iskorištenost Diska"
@@ -397,10 +419,10 @@ msgid "Documentation"
msgstr "Dokumentacija" msgstr "Dokumentacija"
#. Context: System is down #. Context: System is down
#: src/lib/utils.ts
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "Down" msgid "Down"
msgstr "" msgstr ""
@@ -409,12 +431,12 @@ msgid "Duration"
msgstr "" msgstr ""
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Edit" msgid "Edit"
msgstr "" msgstr ""
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Email" msgid "Email"
msgstr "Email" msgstr "Email"
@@ -422,6 +444,11 @@ msgstr "Email"
msgid "Email notifications" msgid "Email notifications"
msgstr "Email notifikacije" msgstr "Email notifikacije"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Empty"
msgstr "Prazna"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Enter email address to reset password" msgid "Enter email address to reset password"
msgstr "Unesite email adresu za resetiranje lozinke" msgstr "Unesite email adresu za resetiranje lozinke"
@@ -430,11 +457,11 @@ msgstr "Unesite email adresu za resetiranje lozinke"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Unesite email adresu..." msgstr "Unesite email adresu..."
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Error" msgid "Error"
msgstr "Greška" msgstr "Greška"
@@ -469,8 +496,8 @@ msgstr ""
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Provjera autentičnosti nije uspjela" msgstr "Provjera autentičnosti nije uspjela"
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Failed to save settings" msgid "Failed to save settings"
msgstr "Neuspješno snimanje postavki" msgstr "Neuspješno snimanje postavki"
@@ -478,13 +505,13 @@ msgstr "Neuspješno snimanje postavki"
msgid "Failed to send test notification" msgid "Failed to send test notification"
msgstr "Neuspješno slanje testne notifikacije" msgstr "Neuspješno slanje testne notifikacije"
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Failed to update alert" msgid "Failed to update alert"
msgstr "Ažuriranje upozorenja nije uspjelo" msgstr "Ažuriranje upozorenja nije uspjelo"
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filter..." msgstr "Filter..."
@@ -492,7 +519,7 @@ msgstr "Filter..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "" msgstr ""
#: src/components/alerts/alerts-system.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}}"
msgstr "Za <0>{min}</0> {min, plural, one {minutu} other {minute}}" msgstr "Za <0>{min}</0> {min, plural, one {minutu} other {minute}}"
@@ -500,9 +527,14 @@ msgstr "Za <0>{min}</0> {min, plural, one {minutu} other {minute}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Zaboravljena lozinka?" msgstr "Zaboravljena lozinka?"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
msgstr "Puna"
#. Context: General settings #. Context: General settings
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/layout.tsx
msgid "General" msgid "General"
msgstr "Općenito" msgstr "Općenito"
@@ -524,6 +556,11 @@ msgstr "Homebrew naredba"
msgid "Host / IP" msgid "Host / IP"
msgstr "Host / IP" msgstr "Host / IP"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Idle"
msgstr "Neaktivna"
#: 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."
msgstr "Ako ste izgubili lozinku za svoj administratorski račun, možete ju resetirati pomoću sljedeće naredbe." msgstr "Ako ste izgubili lozinku za svoj administratorski račun, možete ju resetirati pomoću sljedeće naredbe."
@@ -545,29 +582,24 @@ msgstr "Jezik"
msgid "Layout" msgid "Layout"
msgstr "Izgled" msgstr "Izgled"
#. Light theme
#: src/components/mode-toggle.tsx
msgid "Light"
msgstr "Svijetlo"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "" msgstr ""
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 15m" msgid "Load Average 15m"
msgstr "" msgstr ""
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 1m" msgid "Load Average 1m"
msgstr "" msgstr ""
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Load Average 5m" msgid "Load Average 5m"
msgstr "" msgstr ""
#. Short label for load average #. Short label for load average
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Load Avg" msgid "Load Avg"
msgstr "" msgstr ""
@@ -579,13 +611,13 @@ msgstr "Odjava"
msgid "Login" msgid "Login"
msgstr "Prijava" msgstr "Prijava"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Login attempt failed" msgid "Login attempt failed"
msgstr "Pokušaj prijave nije uspio" msgstr "Pokušaj prijave nije uspio"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Logs" msgid "Logs"
msgstr "Logovi" msgstr "Logovi"
@@ -606,12 +638,12 @@ msgstr ""
msgid "Max 1 min" msgid "Max 1 min"
msgstr "Maksimalno 1 minuta" msgstr "Maksimalno 1 minuta"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Memory" msgid "Memory"
msgstr "Memorija" msgstr "Memorija"
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
msgstr "Upotreba memorije" msgstr "Upotreba memorije"
@@ -619,12 +651,12 @@ msgstr "Upotreba memorije"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Upotreba memorije Docker spremnika" msgstr "Upotreba memorije Docker spremnika"
#: src/components/alerts-history-columns.tsx
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
msgid "Name" msgid "Name"
msgstr "Ime" msgstr "Ime"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Net" msgid "Net"
msgstr "Mreža" msgstr "Mreža"
@@ -655,8 +687,8 @@ msgid "No systems found."
msgstr "Nije pronađen nijedan sustav." msgstr "Nije pronađen nijedan sustav."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Notifications" msgid "Notifications"
msgstr "Obavijesti" msgstr "Obavijesti"
@@ -668,9 +700,9 @@ msgstr "Podrška za OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Prilikom svakog ponovnog pokretanja, sustavi u bazi podataka biti će ažurirani kako bi odgovarali sustavima definiranim u datoteci." msgstr "Prilikom svakog ponovnog pokretanja, sustavi u bazi podataka biti će ažurirani kako bi odgovarali sustavima definiranim u datoteci."
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu" msgid "Open menu"
msgstr "Otvori menu" msgstr "Otvori menu"
@@ -678,7 +710,7 @@ msgstr "Otvori menu"
msgid "Or continue with" msgid "Or continue with"
msgstr "Ili nastavi sa" msgstr "Ili nastavi sa"
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Prebrišite postojeća upozorenja" msgstr "Prebrišite postojeća upozorenja"
@@ -713,11 +745,11 @@ msgstr ""
msgid "Password reset request received" msgid "Password reset request received"
msgstr "Zahtjev za ponovno postavljanje lozinke primljen" msgstr "Zahtjev za ponovno postavljanje lozinke primljen"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Pause" msgid "Pause"
msgstr "Pauza" msgstr "Pauza"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Paused" msgid "Paused"
msgstr "Pauzirano" msgstr "Pauzirano"
@@ -725,12 +757,12 @@ msgstr "Pauzirano"
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."
#: src/components/alerts/alerts-system.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Please check logs for more details." msgid "Please check logs for more details."
msgstr "Za više detalja provjerite logove." msgstr "Za više detalja provjerite logove."
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
msgid "Please check your credentials and try again" msgid "Please check your credentials and try again"
msgstr "Provjerite svoje podatke i pokušajte ponovno" msgstr "Provjerite svoje podatke i pokušajte ponovno"
@@ -792,7 +824,7 @@ msgstr "Resetiraj Lozinku"
msgid "Resolved" msgid "Resolved"
msgstr "" msgstr ""
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
msgstr "Nastavi" msgstr "Nastavi"
@@ -808,8 +840,8 @@ msgstr ""
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."
msgstr "Spremite adresu pomoću tipke enter ili zareza. Ostavite prazno kako biste onemogućili obavijesti e-poštom." msgstr "Spremite adresu pomoću tipke enter ili zareza. Ostavite prazno kako biste onemogućili obavijesti e-poštom."
#: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Save Settings" msgid "Save Settings"
msgstr "Spremi Postavke" msgstr "Spremi Postavke"
@@ -825,7 +857,7 @@ msgstr "Pretraži"
msgid "Search for systems or settings..." msgid "Search for systems or settings..."
msgstr "Pretraži za sisteme ili postavke..." msgstr "Pretraži za sisteme ili postavke..."
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "See <0>notification settings</0> to configure how you receive alerts." msgid "See <0>notification settings</0> to configure how you receive alerts."
msgstr "Pogledajte <0>postavke obavijesti</0> da biste konfigurirali način primanja upozorenja." msgstr "Pogledajte <0>postavke obavijesti</0> da biste konfigurirali način primanja upozorenja."
@@ -833,6 +865,10 @@ msgstr "Pogledajte <0>postavke obavijesti</0> da biste konfigurirali način prim
msgid "Sent" msgid "Sent"
msgstr "Poslano" msgstr "Poslano"
#: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors."
msgstr "Postavite pragove postotka za boje mjerača."
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed." msgid "Sets the default time range for charts when a system is viewed."
msgstr "Postavlja zadani vremenski raspon za grafikone kada se sustav gleda." msgstr "Postavlja zadani vremenski raspon za grafikone kada se sustav gleda."
@@ -865,7 +901,7 @@ msgstr "Sortiraj po"
msgid "State" msgid "State"
msgstr "" msgstr ""
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "Status" msgstr "Status"
@@ -877,12 +913,10 @@ msgstr "Swap prostor uzet od strane sistema"
msgid "Swap Usage" msgid "Swap Usage"
msgstr "Swap Iskorištenost" msgstr "Swap Iskorištenost"
#. System theme
#: src/lib/utils.ts
#: src/components/mode-toggle.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "System" msgid "System"
msgstr "Sistem" msgstr "Sistem"
@@ -903,12 +937,12 @@ msgid "Table"
msgstr "Tablica" msgstr "Tablica"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "" msgstr ""
#: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts
msgid "Temperature" msgid "Temperature"
msgstr "Temperatura" msgstr "Temperatura"
@@ -932,7 +966,7 @@ msgstr "Testna obavijest poslana"
msgid "Then log into the backend and reset your user account password in the users table." msgid "Then log into the backend and reset your user account password in the users table."
msgstr "Zatim se prijavite u backend i resetirajte lozinku korisničkog računa u tablici korisnika." msgstr "Zatim se prijavite u backend i resetirajte lozinku korisničkog računa u tablici korisnika."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database." msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database."
msgstr "Ova radnja se ne može poništiti. Ovo će trajno izbrisati sve trenutne zapise za {name} iz baze podataka." msgstr "Ova radnja se ne može poništiti. Ovo će trajno izbrisati sve trenutne zapise za {name} iz baze podataka."
@@ -967,8 +1001,8 @@ msgid "Token"
msgstr "" msgstr ""
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens & Fingerprints" msgid "Tokens & Fingerprints"
msgstr "" msgstr ""
@@ -980,39 +1014,39 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/lib/utils.ts #: 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 ""
#: src/lib/utils.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 ""
#: src/lib/utils.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 ""
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "Pokreće se kada bilo koji senzor prijeđe prag" msgstr "Pokreće se kada bilo koji senzor prijeđe prag"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when combined up/down exceeds a threshold" msgid "Triggers when combined up/down exceeds a threshold"
msgstr "Pokreće se kada kombinacija gore/dolje premaši prag" msgstr "Pokreće se kada kombinacija gore/dolje premaši prag"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
msgstr "Pokreće se kada iskorištenost procesora premaši prag" msgstr "Pokreće se kada iskorištenost procesora premaši prag"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
msgstr "Pokreće se kada iskorištenost memorije premaši prag" msgstr "Pokreće se kada iskorištenost memorije premaši prag"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when status switches between up and down" msgid "Triggers when status switches between up and down"
msgstr "Pokreće se kada se status sistema promijeni" msgstr "Pokreće se kada se status sistema promijeni"
#: src/lib/utils.ts #: src/lib/alerts.ts
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Pokreće se kada iskorištenost bilo kojeg diska premaši prag" msgstr "Pokreće se kada iskorištenost bilo kojeg diska premaši prag"
@@ -1025,9 +1059,14 @@ msgstr ""
msgid "Universal token" msgid "Universal token"
msgstr "" msgstr ""
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Nepoznata"
#. Context: System is up #. Context: System is up
#: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Up" msgid "Up"
msgstr "" msgstr ""
@@ -1050,13 +1089,13 @@ msgstr "Iskorištenost"
msgid "Usage of root partition" msgid "Usage of root partition"
msgstr "Iskorištenost root datotečnog sustava" msgstr "Iskorištenost root datotečnog sustava"
#: src/components/charts/swap-chart.tsx
#: src/components/charts/mem-chart.tsx #: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
msgid "Used" msgid "Used"
msgstr "Iskorišteno" msgstr "Iskorišteno"
#: src/components/navbar.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Users" msgid "Users"
msgstr "Korisnici" msgstr "Korisnici"
@@ -1084,6 +1123,14 @@ msgstr "Čeka se na više podataka prije prikaza"
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "Želite li nam pomoći da naše prijevode učinimo još boljim? Posjetite <0>Crowdin</0> za više detalja." msgstr "Želite li nam pomoći da naše prijevode učinimo još boljim? Posjetite <0>Crowdin</0> za više detalja."
#: src/components/routes/settings/general.tsx
msgid "Warning (%)"
msgstr "Upozorenje (%)"
#: src/components/routes/settings/general.tsx
msgid "Warning thresholds"
msgstr "Pragovi upozorenja"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Webhook / Push notifications" msgid "Webhook / Push notifications"
msgstr "Webhook / Push obavijest" msgstr "Webhook / Push obavijest"

Some files were not shown because too many files have changed in this diff Show More