mirror of
https://github.com/henrygd/beszel.git
synced 2026-03-23 14:06:18 +01:00
Compare commits
11 Commits
a19ccc9263
...
battery-fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e73399b87 | ||
|
|
e149366451 | ||
|
|
8da1ded73e | ||
|
|
efa37b2312 | ||
|
|
bcdb4c92b5 | ||
|
|
a7d07310b6 | ||
|
|
8db87e5497 | ||
|
|
e601a0d564 | ||
|
|
07491108cd | ||
|
|
42ab17de1f | ||
|
|
2d14174f61 |
@@ -20,9 +20,8 @@ func HasReadableBattery() bool {
|
||||
}
|
||||
haveCheckedBattery = true
|
||||
bat, err := battery.Get(0)
|
||||
if err == nil && bat != nil {
|
||||
systemHasBattery = true
|
||||
} else {
|
||||
systemHasBattery = err == nil && bat != nil && bat.Design != 0 && bat.Full != 0
|
||||
if !systemHasBattery {
|
||||
slog.Debug("No battery found", "err", err)
|
||||
}
|
||||
return systemHasBattery
|
||||
|
||||
@@ -85,7 +85,7 @@ func getToken() (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(tokenBytes), nil
|
||||
return strings.TrimSpace(string(tokenBytes)), nil
|
||||
}
|
||||
|
||||
// getOptions returns the WebSocket client options, creating them if necessary.
|
||||
|
||||
@@ -537,4 +537,25 @@ func TestGetToken(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "", token, "Empty file should return empty string")
|
||||
})
|
||||
|
||||
t.Run("strips whitespace from TOKEN_FILE", func(t *testing.T) {
|
||||
unsetEnvVars()
|
||||
|
||||
tokenWithWhitespace := " test-token-with-whitespace \n\t"
|
||||
expectedToken := "test-token-with-whitespace"
|
||||
tokenFile, err := os.CreateTemp("", "token-test-*.txt")
|
||||
require.NoError(t, err)
|
||||
defer os.Remove(tokenFile.Name())
|
||||
|
||||
_, err = tokenFile.WriteString(tokenWithWhitespace)
|
||||
require.NoError(t, err)
|
||||
tokenFile.Close()
|
||||
|
||||
os.Setenv("TOKEN_FILE", tokenFile.Name())
|
||||
defer os.Unsetenv("TOKEN_FILE")
|
||||
|
||||
token, err := getToken()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedToken, token, "Whitespace should be stripped from token file content")
|
||||
})
|
||||
}
|
||||
|
||||
4
i18n.yml
4
i18n.yml
@@ -1,3 +1,3 @@
|
||||
files:
|
||||
- source: /beszel/site/src/locales/en/en.po
|
||||
translation: /beszel/site/src/locales/%two_letters_code%/%two_letters_code%.po
|
||||
- source: /internal/site/src/locales/en/
|
||||
translation: /internal/site/src/locales/%two_letters_code%/%two_letters_code%.po
|
||||
|
||||
@@ -69,6 +69,8 @@ func (h *Hub) StartHub() error {
|
||||
if err := config.SyncSystems(e); err != nil {
|
||||
return err
|
||||
}
|
||||
// register middlewares
|
||||
h.registerMiddlewares(e)
|
||||
// register api routes
|
||||
if err := h.registerApiRoutes(e); err != nil {
|
||||
return err
|
||||
@@ -171,6 +173,37 @@ func (h *Hub) registerCronJobs(_ *core.ServeEvent) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// custom middlewares
|
||||
func (h *Hub) registerMiddlewares(se *core.ServeEvent) {
|
||||
// authorizes request with user matching the provided email
|
||||
authorizeRequestWithEmail := func(e *core.RequestEvent, email string) (err error) {
|
||||
if e.Auth != nil || email == "" {
|
||||
return e.Next()
|
||||
}
|
||||
isAuthRefresh := e.Request.URL.Path == "/api/collections/users/auth-refresh" && e.Request.Method == http.MethodPost
|
||||
e.Auth, err = e.App.FindFirstRecordByData("users", "email", email)
|
||||
if err != nil || !isAuthRefresh {
|
||||
return e.Next()
|
||||
}
|
||||
// auth refresh endpoint, make sure token is set in header
|
||||
token, _ := e.Auth.NewAuthToken()
|
||||
e.Request.Header.Set("Authorization", token)
|
||||
return e.Next()
|
||||
}
|
||||
// authenticate with trusted header
|
||||
if autoLogin, _ := GetEnv("AUTO_LOGIN"); autoLogin != "" {
|
||||
se.Router.BindFunc(func(e *core.RequestEvent) error {
|
||||
return authorizeRequestWithEmail(e, autoLogin)
|
||||
})
|
||||
}
|
||||
// authenticate with trusted header
|
||||
if trustedHeader, _ := GetEnv("TRUSTED_AUTH_HEADER"); trustedHeader != "" {
|
||||
se.Router.BindFunc(func(e *core.RequestEvent) error {
|
||||
return authorizeRequestWithEmail(e, e.Request.Header.Get(trustedHeader))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// custom api routes
|
||||
func (h *Hub) registerApiRoutes(se *core.ServeEvent) error {
|
||||
// auth protected routes
|
||||
|
||||
@@ -711,3 +711,117 @@ func TestCreateUserEndpointAvailability(t *testing.T) {
|
||||
scenario.Test(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAutoLoginMiddleware(t *testing.T) {
|
||||
var hubs []*beszelTests.TestHub
|
||||
|
||||
defer func() {
|
||||
defer os.Unsetenv("AUTO_LOGIN")
|
||||
for _, hub := range hubs {
|
||||
hub.Cleanup()
|
||||
}
|
||||
}()
|
||||
|
||||
os.Setenv("AUTO_LOGIN", "user@test.com")
|
||||
|
||||
testAppFactory := func(t testing.TB) *pbTests.TestApp {
|
||||
hub, _ := beszelTests.NewTestHub(t.TempDir())
|
||||
hubs = append(hubs, hub)
|
||||
hub.StartHub()
|
||||
return hub.TestApp
|
||||
}
|
||||
|
||||
scenarios := []beszelTests.ApiScenario{
|
||||
{
|
||||
Name: "GET /getkey - without auto login should fail",
|
||||
Method: http.MethodGet,
|
||||
URL: "/api/beszel/getkey",
|
||||
ExpectedStatus: 401,
|
||||
ExpectedContent: []string{"requires valid"},
|
||||
TestAppFactory: testAppFactory,
|
||||
},
|
||||
{
|
||||
Name: "GET /getkey - with auto login should fail if no matching user",
|
||||
Method: http.MethodGet,
|
||||
URL: "/api/beszel/getkey",
|
||||
ExpectedStatus: 401,
|
||||
ExpectedContent: []string{"requires valid"},
|
||||
TestAppFactory: testAppFactory,
|
||||
},
|
||||
{
|
||||
Name: "GET /getkey - with auto login should succeed",
|
||||
Method: http.MethodGet,
|
||||
URL: "/api/beszel/getkey",
|
||||
ExpectedStatus: 200,
|
||||
ExpectedContent: []string{"\"key\":", "\"v\":"},
|
||||
TestAppFactory: testAppFactory,
|
||||
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
|
||||
beszelTests.CreateUser(app, "user@test.com", "password123")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
scenario.Test(t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrustedHeaderMiddleware(t *testing.T) {
|
||||
var hubs []*beszelTests.TestHub
|
||||
|
||||
defer func() {
|
||||
defer os.Unsetenv("TRUSTED_AUTH_HEADER")
|
||||
for _, hub := range hubs {
|
||||
hub.Cleanup()
|
||||
}
|
||||
}()
|
||||
|
||||
os.Setenv("TRUSTED_AUTH_HEADER", "X-Beszel-Trusted")
|
||||
|
||||
testAppFactory := func(t testing.TB) *pbTests.TestApp {
|
||||
hub, _ := beszelTests.NewTestHub(t.TempDir())
|
||||
hubs = append(hubs, hub)
|
||||
hub.StartHub()
|
||||
return hub.TestApp
|
||||
}
|
||||
|
||||
scenarios := []beszelTests.ApiScenario{
|
||||
{
|
||||
Name: "GET /getkey - without trusted header should fail",
|
||||
Method: http.MethodGet,
|
||||
URL: "/api/beszel/getkey",
|
||||
ExpectedStatus: 401,
|
||||
ExpectedContent: []string{"requires valid"},
|
||||
TestAppFactory: testAppFactory,
|
||||
},
|
||||
{
|
||||
Name: "GET /getkey - with trusted header should fail if no matching user",
|
||||
Method: http.MethodGet,
|
||||
URL: "/api/beszel/getkey",
|
||||
Headers: map[string]string{
|
||||
"X-Beszel-Trusted": "user@test.com",
|
||||
},
|
||||
ExpectedStatus: 401,
|
||||
ExpectedContent: []string{"requires valid"},
|
||||
TestAppFactory: testAppFactory,
|
||||
},
|
||||
{
|
||||
Name: "GET /getkey - with trusted header should succeed",
|
||||
Method: http.MethodGet,
|
||||
URL: "/api/beszel/getkey",
|
||||
Headers: map[string]string{
|
||||
"X-Beszel-Trusted": "user@test.com",
|
||||
},
|
||||
ExpectedStatus: 200,
|
||||
ExpectedContent: []string{"\"key\":", "\"v\":"},
|
||||
TestAppFactory: testAppFactory,
|
||||
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
|
||||
beszelTests.CreateUser(app, "user@test.com", "password123")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
scenario.Test(t)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true
|
||||
"recommended": true,
|
||||
"correctness": {
|
||||
"useUniqueElementIds": "off"
|
||||
}
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
@@ -35,4 +38,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import { memo, useEffect, useRef, useState } from "react"
|
||||
import { $router, basePath, Link, navigate } from "./router"
|
||||
import { SystemRecord } from "@/types"
|
||||
import { SystemStatus } from "@/lib/enums"
|
||||
import { AppleIcon, DockerIcon, TuxIcon, WindowsIcon } from "./ui/icons"
|
||||
import { AppleIcon, DockerIcon, FreeBsdIcon, TuxIcon, WindowsIcon } from "./ui/icons"
|
||||
import { InputCopy } from "./ui/input-copy"
|
||||
import { getPagePath } from "@nanostores/router"
|
||||
import {
|
||||
@@ -253,6 +253,12 @@ export const SystemDialog = ({ setOpen, system }: { setOpen: (open: boolean) =>
|
||||
copyWindowsCommand(isUnixSocket ? hostValue : port.current?.value, publicKey, token),
|
||||
icons: [WindowsIcon],
|
||||
},
|
||||
{
|
||||
text: t({ message: "FreeBSD command", context: "Button to copy install command" }),
|
||||
onClick: async () =>
|
||||
copyLinuxCommand(isUnixSocket ? hostValue : port.current?.value, publicKey, token),
|
||||
icons: [FreeBsdIcon],
|
||||
},
|
||||
{
|
||||
text: t`Manual setup instructions`,
|
||||
url: "https://beszel.dev/guide/agent-installation#binary",
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
RotateCwIcon,
|
||||
ServerIcon,
|
||||
Trash2Icon,
|
||||
ExternalLinkIcon,
|
||||
} from "lucide-react"
|
||||
import { memo, useEffect, useMemo, useState } from "react"
|
||||
import {
|
||||
@@ -28,7 +29,7 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { AppleIcon, DockerIcon, TuxIcon, WindowsIcon } from "@/components/ui/icons"
|
||||
import { AppleIcon, DockerIcon, FreeBsdIcon, TuxIcon, WindowsIcon } from "@/components/ui/icons"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||
@@ -150,6 +151,7 @@ const SectionUniversalToken = memo(() => {
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: only on mount
|
||||
useEffect(() => {
|
||||
updateToken()
|
||||
}, [])
|
||||
@@ -221,6 +223,16 @@ const ActionsButtonUniversalToken = memo(({ token, checked }: { token: string; c
|
||||
onClick: () => copyWindowsCommand(port, publicKey, token),
|
||||
icons: [WindowsIcon],
|
||||
},
|
||||
{
|
||||
text: t({ message: "FreeBSD command", context: "Button to copy install command" }),
|
||||
onClick: () => copyLinuxCommand(port, publicKey, token),
|
||||
icons: [FreeBsdIcon],
|
||||
},
|
||||
{
|
||||
text: t`Manual setup instructions`,
|
||||
url: "https://beszel.dev/guide/agent-installation#binary",
|
||||
icons: [ExternalLinkIcon],
|
||||
},
|
||||
]
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -291,8 +303,8 @@ const SectionTable = memo(({ fingerprints = [] }: { fingerprints: FingerprintRec
|
||||
</tr>
|
||||
</TableHeader>
|
||||
<TableBody className="whitespace-pre">
|
||||
{fingerprints.map((fingerprint, i) => (
|
||||
<TableRow key={i}>
|
||||
{fingerprints.map((fingerprint) => (
|
||||
<TableRow key={fingerprint.id}>
|
||||
<TableCell className="font-medium ps-5 py-2 max-w-60 truncate">
|
||||
{fingerprint.expand.system.name}
|
||||
</TableCell>
|
||||
@@ -317,10 +329,10 @@ async function updateFingerprint(fingerprint: FingerprintRecord, rotateToken = f
|
||||
fingerprint: "",
|
||||
token: rotateToken ? generateToken() : fingerprint.token,
|
||||
})
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
toast({
|
||||
title: t`Error`,
|
||||
description: error.message,
|
||||
description: (error as Error).message,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/** biome-ignore-all lint/suspicious/noAssignInExpressions: it's fine :) */
|
||||
import type { PreinitializedMapStore } from "nanostores"
|
||||
import { pb, verifyAuth } from "@/lib/api"
|
||||
import {
|
||||
@@ -16,9 +17,10 @@ const COLLECTION = pb.collection<SystemRecord>("systems")
|
||||
const FIELDS_DEFAULT = "id,name,host,port,info,status"
|
||||
|
||||
/** Maximum system name length for display purposes */
|
||||
const MAX_SYSTEM_NAME_LENGTH = 20
|
||||
const MAX_SYSTEM_NAME_LENGTH = 22
|
||||
|
||||
let initialized = false
|
||||
// biome-ignore lint/suspicious/noConfusingVoidType: typescript rocks
|
||||
let unsub: (() => void) | undefined | void
|
||||
|
||||
/** Initialize the systems manager and set up listeners */
|
||||
@@ -104,20 +106,37 @@ async function fetchSystems(): Promise<SystemRecord[]> {
|
||||
}
|
||||
}
|
||||
|
||||
/** Makes sure the system has valid info object and throws if not */
|
||||
function validateSystemInfo(system: SystemRecord) {
|
||||
if (!("cpu" in system.info)) {
|
||||
throw new Error(`${system.name} has no CPU info`)
|
||||
}
|
||||
}
|
||||
|
||||
/** Add system to both name and ID stores */
|
||||
export function add(system: SystemRecord) {
|
||||
$allSystemsByName.setKey(system.name, system)
|
||||
$allSystemsById.setKey(system.id, system)
|
||||
try {
|
||||
validateSystemInfo(system)
|
||||
$allSystemsByName.setKey(system.name, system)
|
||||
$allSystemsById.setKey(system.id, system)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
/** Update system in stores */
|
||||
export function update(system: SystemRecord) {
|
||||
// if name changed, make sure old name is removed from the name store
|
||||
const oldName = $allSystemsById.get()[system.id]?.name
|
||||
if (oldName !== system.name) {
|
||||
$allSystemsByName.setKey(oldName, undefined as any)
|
||||
try {
|
||||
validateSystemInfo(system)
|
||||
// if name changed, make sure old name is removed from the name store
|
||||
const oldName = $allSystemsById.get()[system.id]?.name
|
||||
if (oldName !== system.name) {
|
||||
$allSystemsByName.setKey(oldName, undefined as unknown as SystemRecord)
|
||||
}
|
||||
add(system)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
add(system)
|
||||
}
|
||||
|
||||
/** Remove system from stores */
|
||||
@@ -132,7 +151,7 @@ export function remove(system: SystemRecord) {
|
||||
/** Remove system from specific store */
|
||||
function removeFromStore(system: SystemRecord, store: PreinitializedMapStore<Record<string, SystemRecord>>) {
|
||||
const key = store === $allSystemsByName ? system.name : system.id
|
||||
store.setKey(key, undefined as any)
|
||||
store.setKey(key, undefined as unknown as SystemRecord)
|
||||
}
|
||||
|
||||
/** Action functions for subscription */
|
||||
|
||||
@@ -41,38 +41,38 @@ msgstr "已选择 {0} / {1} 行"
|
||||
|
||||
#: src/lib/utils.ts
|
||||
msgid "1 hour"
|
||||
msgstr "1小时"
|
||||
msgstr "1 小时"
|
||||
|
||||
#. Load average
|
||||
#: src/components/charts/load-average-chart.tsx
|
||||
msgid "1 min"
|
||||
msgstr "1分钟"
|
||||
msgstr "1 分钟"
|
||||
|
||||
#: src/lib/utils.ts
|
||||
msgid "1 week"
|
||||
msgstr "1周"
|
||||
msgstr "1 周"
|
||||
|
||||
#: src/lib/utils.ts
|
||||
msgid "12 hours"
|
||||
msgstr "12小时"
|
||||
msgstr "12 小时"
|
||||
|
||||
#. Load average
|
||||
#: src/components/charts/load-average-chart.tsx
|
||||
msgid "15 min"
|
||||
msgstr "15分钟"
|
||||
msgstr "15 分钟"
|
||||
|
||||
#: src/lib/utils.ts
|
||||
msgid "24 hours"
|
||||
msgstr "24小时"
|
||||
msgstr "24 小时"
|
||||
|
||||
#: src/lib/utils.ts
|
||||
msgid "30 days"
|
||||
msgstr "30天"
|
||||
msgstr "30 天"
|
||||
|
||||
#. Load average
|
||||
#: src/components/charts/load-average-chart.tsx
|
||||
msgid "5 min"
|
||||
msgstr "5分钟"
|
||||
msgstr "5 分钟"
|
||||
|
||||
#. Table column
|
||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||
@@ -103,7 +103,7 @@ msgstr "添加客户端"
|
||||
|
||||
#: src/components/routes/settings/notifications.tsx
|
||||
msgid "Add URL"
|
||||
msgstr "添加URL"
|
||||
msgstr "添加 URL"
|
||||
|
||||
#: src/components/routes/settings/general.tsx
|
||||
msgid "Adjust display options for charts."
|
||||
@@ -137,7 +137,7 @@ msgstr "所有客户端"
|
||||
|
||||
#: src/components/systems-table/systems-table-columns.tsx
|
||||
msgid "Are you sure you want to delete {name}?"
|
||||
msgstr "您确定要删除{name}吗?"
|
||||
msgstr "您确定要删除 {name} 吗?"
|
||||
|
||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||
msgid "Are you sure?"
|
||||
@@ -189,11 +189,11 @@ msgstr "电池"
|
||||
|
||||
#: src/components/login/auth-form.tsx
|
||||
msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers."
|
||||
msgstr "Beszel支持OpenID Connect和其他OAuth2认证方式。"
|
||||
msgstr "Beszel 支持 OpenID Connect 和其他 OAuth2 认证方式。"
|
||||
|
||||
#: src/components/routes/settings/notifications.tsx
|
||||
msgid "Beszel uses <0>Shoutrrr</0> to integrate with popular notification services."
|
||||
msgstr "Beszel使用<0>Shoutrrr</0>以实现与常见的通知服务集成。"
|
||||
msgstr "Beszel 使用 <0>Shoutrrr</0> 以实现与常见的通知服务集成。"
|
||||
|
||||
#: src/components/add-system.tsx
|
||||
msgid "Binary"
|
||||
@@ -338,7 +338,7 @@ msgstr "复制下面的客户端<0>docker-compose.yml</0>内容,或使用<1>
|
||||
|
||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||
msgid "Copy YAML"
|
||||
msgstr "复制YAML"
|
||||
msgstr "复制 YAML"
|
||||
|
||||
#: src/components/systems-table/systems-table-columns.tsx
|
||||
msgid "CPU"
|
||||
@@ -348,7 +348,7 @@ msgstr "CPU"
|
||||
#: src/components/routes/system.tsx
|
||||
#: src/lib/alerts.ts
|
||||
msgid "CPU Usage"
|
||||
msgstr "CPU使用率"
|
||||
msgstr "CPU 使用率"
|
||||
|
||||
#: src/components/login/auth-form.tsx
|
||||
msgid "Create account"
|
||||
@@ -397,7 +397,7 @@ msgstr "磁盘"
|
||||
|
||||
#: src/components/routes/system.tsx
|
||||
msgid "Disk I/O"
|
||||
msgstr "磁盘I/O"
|
||||
msgstr "磁盘 I/O"
|
||||
|
||||
#: src/components/routes/settings/general.tsx
|
||||
msgid "Disk unit"
|
||||
@@ -415,15 +415,15 @@ msgstr "{extraFsName}的磁盘使用"
|
||||
|
||||
#: src/components/routes/system.tsx
|
||||
msgid "Docker CPU Usage"
|
||||
msgstr "Docker CPU使用"
|
||||
msgstr "Docker CPU 使用"
|
||||
|
||||
#: src/components/routes/system.tsx
|
||||
msgid "Docker Memory Usage"
|
||||
msgstr "Docker内存使用"
|
||||
msgstr "Docker 内存使用"
|
||||
|
||||
#: src/components/routes/system.tsx
|
||||
msgid "Docker Network I/O"
|
||||
msgstr "Docker网络I/O"
|
||||
msgstr "Docker 网络 I/O"
|
||||
|
||||
#: src/components/command-palette.tsx
|
||||
msgid "Documentation"
|
||||
@@ -603,15 +603,15 @@ msgstr "系统负载"
|
||||
|
||||
#: src/lib/alerts.ts
|
||||
msgid "Load Average 15m"
|
||||
msgstr "15分钟内的平均负载"
|
||||
msgstr "15 分钟内的平均负载"
|
||||
|
||||
#: src/lib/alerts.ts
|
||||
msgid "Load Average 1m"
|
||||
msgstr "1分钟负载平均值"
|
||||
msgstr "1 分钟负载平均值"
|
||||
|
||||
#: src/lib/alerts.ts
|
||||
msgid "Load Average 5m"
|
||||
msgstr "5分钟内的平均负载"
|
||||
msgstr "5 分钟内的平均负载"
|
||||
|
||||
#. Short label for load average
|
||||
#: src/components/systems-table/systems-table-columns.tsx
|
||||
@@ -660,11 +660,11 @@ msgstr "内存"
|
||||
#: src/components/routes/system.tsx
|
||||
#: src/lib/alerts.ts
|
||||
msgid "Memory Usage"
|
||||
msgstr "内存使用"
|
||||
msgstr "内存使用率"
|
||||
|
||||
#: src/components/routes/system.tsx
|
||||
msgid "Memory usage of docker containers"
|
||||
msgstr "Docker 容器的内存使用"
|
||||
msgstr "Docker 容器的内存使用率"
|
||||
|
||||
#: src/components/add-system.tsx
|
||||
#: src/components/alerts-history-columns.tsx
|
||||
@@ -709,7 +709,7 @@ msgstr "通知"
|
||||
|
||||
#: src/components/login/auth-form.tsx
|
||||
msgid "OAuth 2 / OIDC support"
|
||||
msgstr "支持 OAuth 2 / OIDC"
|
||||
msgstr "支持 OAuth 2/OIDC"
|
||||
|
||||
#: src/components/routes/settings/config-yaml.tsx
|
||||
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
|
||||
@@ -741,7 +741,7 @@ msgstr "第 {0} 页,共 {1} 页"
|
||||
|
||||
#: src/components/command-palette.tsx
|
||||
msgid "Pages / Settings"
|
||||
msgstr "页面/设置"
|
||||
msgstr "页面 / 设置"
|
||||
|
||||
#: src/components/login/auth-form.tsx
|
||||
#: src/components/login/auth-form.tsx
|
||||
@@ -774,7 +774,7 @@ msgstr "已暂停 ({pausedSystemsLength})"
|
||||
|
||||
#: src/components/routes/settings/notifications.tsx
|
||||
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-sheet.tsx
|
||||
msgid "Please check logs for more details."
|
||||
@@ -909,7 +909,7 @@ msgstr "登录"
|
||||
|
||||
#: src/components/command-palette.tsx
|
||||
msgid "SMTP settings"
|
||||
msgstr "SMTP设置"
|
||||
msgstr "SMTP 设置"
|
||||
|
||||
#: src/components/systems-table/systems-table.tsx
|
||||
msgid "Sort By"
|
||||
@@ -931,7 +931,7 @@ msgstr "系统使用的 SWAP 空间"
|
||||
|
||||
#: src/components/routes/system.tsx
|
||||
msgid "Swap Usage"
|
||||
msgstr "SWAP 使用"
|
||||
msgstr "SWAP 使用率"
|
||||
|
||||
#: src/components/alerts-history-columns.tsx
|
||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||
@@ -1024,7 +1024,7 @@ msgstr "令牌"
|
||||
#: src/components/routes/settings/layout.tsx
|
||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||
msgid "Tokens & Fingerprints"
|
||||
msgstr "令牌和指纹"
|
||||
msgstr "令牌与指纹"
|
||||
|
||||
#: 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."
|
||||
@@ -1032,19 +1032,19 @@ msgstr "令牌允许客户端连接和注册。指纹是每个系统唯一的稳
|
||||
|
||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
|
||||
msgstr "令牌和指纹用于验证到中心的WebSocket连接。"
|
||||
msgstr "令牌与指纹用于验证到中心的 WebSocket 连接。"
|
||||
|
||||
#: src/lib/alerts.ts
|
||||
msgid "Triggers when 1 minute load average exceeds a threshold"
|
||||
msgstr "当1分钟负载平均值超过阈值时触发"
|
||||
msgstr "当 1 分钟负载平均值超过阈值时触发"
|
||||
|
||||
#: src/lib/alerts.ts
|
||||
msgid "Triggers when 15 minute load average exceeds a threshold"
|
||||
msgstr "当15分钟负载平均值超过阈值时触发"
|
||||
msgstr "当 15 分钟负载平均值超过阈值时触发"
|
||||
|
||||
#: src/lib/alerts.ts
|
||||
msgid "Triggers when 5 minute load average exceeds a threshold"
|
||||
msgstr "当5分钟内的平均负载超过阈值时触发"
|
||||
msgstr "当 5 分钟内的平均负载超过阈值时触发"
|
||||
|
||||
#: src/lib/alerts.ts
|
||||
msgid "Triggers when any sensor exceeds a threshold"
|
||||
@@ -1056,7 +1056,7 @@ msgstr "当网络的上/下行速度超过阈值时触发"
|
||||
|
||||
#: src/lib/alerts.ts
|
||||
msgid "Triggers when CPU usage exceeds a threshold"
|
||||
msgstr "当CPU使用率超过阈值时触发"
|
||||
msgstr "当 CPU 使用率超过阈值时触发"
|
||||
|
||||
#: src/lib/alerts.ts
|
||||
msgid "Triggers when memory usage exceeds a threshold"
|
||||
@@ -1174,11 +1174,11 @@ msgstr "写入"
|
||||
|
||||
#: src/components/routes/settings/layout.tsx
|
||||
msgid "YAML Config"
|
||||
msgstr "YAML配置"
|
||||
msgstr "YAML 配置"
|
||||
|
||||
#: src/components/routes/settings/config-yaml.tsx
|
||||
msgid "YAML Configuration"
|
||||
msgstr "YAML配置"
|
||||
msgstr "YAML 配置"
|
||||
|
||||
#: src/components/routes/settings/layout.tsx
|
||||
msgid "Your user settings have been updated."
|
||||
|
||||
@@ -74,6 +74,19 @@ const Layout = () => {
|
||||
document.documentElement.dir = direction
|
||||
}, [direction])
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: only run on mount
|
||||
useEffect(() => {
|
||||
// refresh auth if not authenticated (required for trusted auth header)
|
||||
if (!authenticated) {
|
||||
pb.collection("users")
|
||||
.authRefresh()
|
||||
.then((res) => {
|
||||
pb.authStore.save(res.token, res.record)
|
||||
$authenticated.set(!!pb.authStore.isValid)
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<DirectionProvider dir={direction}>
|
||||
{!authenticated ? (
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
## 0.12.8
|
||||
|
||||
- Add setting for time format (12h / 24h). (#424)
|
||||
|
||||
- Add experimental one-time password (OTP) support.
|
||||
|
||||
- Add `TRUSTED_AUTH_HEADER` environment variable for authentication forwarding. (#399)
|
||||
|
||||
- Add `AUTO_LOGIN` environment variable for automatic login. (#399)
|
||||
|
||||
- Add FreeBSD support for agent install script and update command.
|
||||
|
||||
## 0.12.7
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "beszel.fullname" . }}-web
|
||||
name: {{ include "beszel.fullname" . }}
|
||||
labels:
|
||||
{{- include "beszel.labels" . | nindent 4 }}
|
||||
{{- if .Values.service.annotations }}
|
||||
|
||||
@@ -30,14 +30,10 @@ securityContext: {}
|
||||
|
||||
service:
|
||||
enabled: true
|
||||
type: LoadBalancer
|
||||
loadBalancerIP: "10.0.10.251"
|
||||
annotations: {}
|
||||
type: ClusterIP
|
||||
loadBalancerIP: ""
|
||||
port: 8090
|
||||
# -- Annotations for the DHCP service
|
||||
annotations:
|
||||
metallb.universe.tf/address-pool: pool
|
||||
metallb.universe.tf/allow-shared-ip: beszel-hub-web
|
||||
# -- Labels for the DHCP service
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
@@ -96,7 +92,7 @@ persistentVolumeClaim:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
|
||||
storageClass: "retain-local-path"
|
||||
storageClass: ""
|
||||
|
||||
# -- volume claim size
|
||||
size: "500Mi"
|
||||
|
||||
Reference in New Issue
Block a user