Compare commits

...

11 Commits

Author SHA1 Message Date
henrygd
3e73399b87 fix battery detection on newer macs (#1170) 2025-09-15 12:02:50 -04:00
Ryan W
e149366451 Fixing service name in helm chart and making default values unopinionated (#1166) 2025-09-13 12:09:36 -04:00
henrygd
8da1ded73e strip whitespace from TOKEN_FILE (#984) 2025-09-12 12:59:53 -04:00
henrygd
efa37b2312 web: extra check for valid system before adding (#1063) 2025-09-11 15:37:11 -04:00
henrygd
bcdb4c92b5 add freebsd to list of copyable commands 2025-09-11 15:07:37 -04:00
henrygd
a7d07310b6 Add AUTO_LOGIN environment variable for automatic login. (#399) 2025-09-11 14:01:09 -04:00
hank
8db87e5497 Update Crowdin configuration file 2025-09-11 12:45:43 -04:00
henrygd
e601a0d564 add TRUSTED_AUTH_HEADER for auth forwarding (#399) 2025-09-10 21:26:59 -04:00
Fankesyooni
07491108cd new zh-CN translations (#1160) 2025-09-10 13:34:17 -04:00
henrygd
42ab17de1f move i18n.yml to project root 2025-09-10 13:30:42 -04:00
henrygd
2d14174f61 update i18n.yml 2025-09-10 13:28:06 -04:00
15 changed files with 292 additions and 68 deletions

View File

@@ -20,9 +20,8 @@ func HasReadableBattery() bool {
} }
haveCheckedBattery = true haveCheckedBattery = true
bat, err := battery.Get(0) bat, err := battery.Get(0)
if err == nil && bat != nil { systemHasBattery = err == nil && bat != nil && bat.Design != 0 && bat.Full != 0
systemHasBattery = true if !systemHasBattery {
} else {
slog.Debug("No battery found", "err", err) slog.Debug("No battery found", "err", err)
} }
return systemHasBattery return systemHasBattery

View File

@@ -85,7 +85,7 @@ func getToken() (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
return string(tokenBytes), nil return strings.TrimSpace(string(tokenBytes)), nil
} }
// getOptions returns the WebSocket client options, creating them if necessary. // getOptions returns the WebSocket client options, creating them if necessary.

View File

@@ -537,4 +537,25 @@ func TestGetToken(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "", token, "Empty file should return empty string") 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")
})
} }

View File

@@ -1,3 +1,3 @@
files: files:
- source: /beszel/site/src/locales/en/en.po - source: /internal/site/src/locales/en/
translation: /beszel/site/src/locales/%two_letters_code%/%two_letters_code%.po translation: /internal/site/src/locales/%two_letters_code%/%two_letters_code%.po

View File

@@ -69,6 +69,8 @@ func (h *Hub) StartHub() error {
if err := config.SyncSystems(e); err != nil { if err := config.SyncSystems(e); err != nil {
return err return err
} }
// register middlewares
h.registerMiddlewares(e)
// register api routes // register api routes
if err := h.registerApiRoutes(e); err != nil { if err := h.registerApiRoutes(e); err != nil {
return err return err
@@ -171,6 +173,37 @@ func (h *Hub) registerCronJobs(_ *core.ServeEvent) error {
return nil 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 // custom api routes
func (h *Hub) registerApiRoutes(se *core.ServeEvent) error { func (h *Hub) registerApiRoutes(se *core.ServeEvent) error {
// auth protected routes // auth protected routes

View File

@@ -711,3 +711,117 @@ func TestCreateUserEndpointAvailability(t *testing.T) {
scenario.Test(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)
}
}

View File

@@ -17,7 +17,10 @@
"linter": { "linter": {
"enabled": true, "enabled": true,
"rules": { "rules": {
"recommended": true "recommended": true,
"correctness": {
"useUniqueElementIds": "off"
}
} }
}, },
"javascript": { "javascript": {
@@ -35,4 +38,4 @@
} }
} }
} }
} }

View File

@@ -22,7 +22,7 @@ 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 { 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 { InputCopy } from "./ui/input-copy"
import { getPagePath } from "@nanostores/router" import { getPagePath } from "@nanostores/router"
import { import {
@@ -253,6 +253,12 @@ export const SystemDialog = ({ setOpen, system }: { setOpen: (open: boolean) =>
copyWindowsCommand(isUnixSocket ? hostValue : port.current?.value, publicKey, token), copyWindowsCommand(isUnixSocket ? hostValue : port.current?.value, publicKey, token),
icons: [WindowsIcon], 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`, text: t`Manual setup instructions`,
url: "https://beszel.dev/guide/agent-installation#binary", url: "https://beszel.dev/guide/agent-installation#binary",

View File

@@ -9,6 +9,7 @@ import {
RotateCwIcon, RotateCwIcon,
ServerIcon, ServerIcon,
Trash2Icon, Trash2Icon,
ExternalLinkIcon,
} from "lucide-react" } from "lucide-react"
import { memo, useEffect, useMemo, useState } from "react" import { memo, useEffect, useMemo, useState } from "react"
import { import {
@@ -28,7 +29,7 @@ import {
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu" } 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 { Separator } from "@/components/ui/separator"
import { Switch } from "@/components/ui/switch" import { Switch } from "@/components/ui/switch"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
@@ -150,6 +151,7 @@ const SectionUniversalToken = memo(() => {
setIsLoading(false) setIsLoading(false)
} }
// biome-ignore lint/correctness/useExhaustiveDependencies: only on mount
useEffect(() => { useEffect(() => {
updateToken() updateToken()
}, []) }, [])
@@ -221,6 +223,16 @@ const ActionsButtonUniversalToken = memo(({ token, checked }: { token: string; c
onClick: () => copyWindowsCommand(port, publicKey, token), onClick: () => copyWindowsCommand(port, publicKey, token),
icons: [WindowsIcon], 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 ( return (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -291,8 +303,8 @@ const SectionTable = memo(({ fingerprints = [] }: { fingerprints: FingerprintRec
</tr> </tr>
</TableHeader> </TableHeader>
<TableBody className="whitespace-pre"> <TableBody className="whitespace-pre">
{fingerprints.map((fingerprint, i) => ( {fingerprints.map((fingerprint) => (
<TableRow key={i}> <TableRow key={fingerprint.id}>
<TableCell className="font-medium ps-5 py-2 max-w-60 truncate"> <TableCell className="font-medium ps-5 py-2 max-w-60 truncate">
{fingerprint.expand.system.name} {fingerprint.expand.system.name}
</TableCell> </TableCell>
@@ -317,10 +329,10 @@ async function updateFingerprint(fingerprint: FingerprintRecord, rotateToken = f
fingerprint: "", fingerprint: "",
token: rotateToken ? generateToken() : fingerprint.token, token: rotateToken ? generateToken() : fingerprint.token,
}) })
} catch (error: any) { } catch (error: unknown) {
toast({ toast({
title: t`Error`, title: t`Error`,
description: error.message, description: (error as Error).message,
}) })
} }
} }

View File

@@ -1,3 +1,4 @@
/** biome-ignore-all lint/suspicious/noAssignInExpressions: it's fine :) */
import type { PreinitializedMapStore } from "nanostores" import type { PreinitializedMapStore } from "nanostores"
import { pb, verifyAuth } from "@/lib/api" import { pb, verifyAuth } from "@/lib/api"
import { import {
@@ -16,9 +17,10 @@ const COLLECTION = pb.collection<SystemRecord>("systems")
const FIELDS_DEFAULT = "id,name,host,port,info,status" const FIELDS_DEFAULT = "id,name,host,port,info,status"
/** Maximum system name length for display purposes */ /** Maximum system name length for display purposes */
const MAX_SYSTEM_NAME_LENGTH = 20 const MAX_SYSTEM_NAME_LENGTH = 22
let initialized = false let initialized = false
// biome-ignore lint/suspicious/noConfusingVoidType: typescript rocks
let unsub: (() => void) | undefined | void let unsub: (() => void) | undefined | void
/** Initialize the systems manager and set up listeners */ /** 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 */ /** Add system to both name and ID stores */
export function add(system: SystemRecord) { export function add(system: SystemRecord) {
$allSystemsByName.setKey(system.name, system) try {
$allSystemsById.setKey(system.id, system) validateSystemInfo(system)
$allSystemsByName.setKey(system.name, system)
$allSystemsById.setKey(system.id, system)
} catch (error) {
console.error(error)
}
} }
/** Update system in stores */ /** Update system in stores */
export function update(system: SystemRecord) { export function update(system: SystemRecord) {
// if name changed, make sure old name is removed from the name store try {
const oldName = $allSystemsById.get()[system.id]?.name validateSystemInfo(system)
if (oldName !== system.name) { // if name changed, make sure old name is removed from the name store
$allSystemsByName.setKey(oldName, undefined as any) 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 */ /** Remove system from stores */
@@ -132,7 +151,7 @@ export function remove(system: SystemRecord) {
/** Remove system from specific store */ /** Remove system from specific store */
function removeFromStore(system: SystemRecord, store: PreinitializedMapStore<Record<string, SystemRecord>>) { function removeFromStore(system: SystemRecord, store: PreinitializedMapStore<Record<string, SystemRecord>>) {
const key = store === $allSystemsByName ? system.name : system.id 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 */ /** Action functions for subscription */

View File

@@ -41,38 +41,38 @@ msgstr "已选择 {0} / {1} 行"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1小时" 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 "1分钟" msgstr "1 分钟"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 week" msgid "1 week"
msgstr "1周" msgstr "1 周"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "12 hours" msgid "12 hours"
msgstr "12小时" 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 "15分钟" msgstr "15 分钟"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "24 hours" msgid "24 hours"
msgstr "24小时" msgstr "24 小时"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "30 days" msgid "30 days"
msgstr "30天" 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 "5分钟" msgstr "5 分钟"
#. Table column #. Table column
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
@@ -103,7 +103,7 @@ msgstr "添加客户端"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Add URL" msgid "Add URL"
msgstr "添加URL" msgstr "添加 URL"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Adjust display options for charts." msgid "Adjust display options for charts."
@@ -137,7 +137,7 @@ msgstr "所有客户端"
#: src/components/systems-table/systems-table-columns.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?"
@@ -189,11 +189,11 @@ 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 认证方式。"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Beszel uses <0>Shoutrrr</0> to integrate with popular notification services." msgid "Beszel uses <0>Shoutrrr</0> to integrate with popular notification services."
msgstr "Beszel使用<0>Shoutrrr</0>以实现与常见的通知服务集成。" msgstr "Beszel 使用 <0>Shoutrrr</0> 以实现与常见的通知服务集成。"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Binary" msgid "Binary"
@@ -338,7 +338,7 @@ msgstr "复制下面的客户端<0>docker-compose.yml</0>内容,或使用<1>
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy YAML" msgid "Copy YAML"
msgstr "复制YAML" msgstr "复制 YAML"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
@@ -348,7 +348,7 @@ msgstr "CPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "CPU使用率" msgstr "CPU 使用率"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Create account" msgid "Create account"
@@ -397,7 +397,7 @@ msgstr "磁盘"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Disk I/O" msgid "Disk I/O"
msgstr "磁盘I/O" msgstr "磁盘 I/O"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Disk unit" msgid "Disk unit"
@@ -415,15 +415,15 @@ msgstr "{extraFsName}的磁盘使用"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Docker CPU Usage" msgid "Docker CPU Usage"
msgstr "Docker CPU使用" msgstr "Docker CPU 使用"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Docker Memory Usage" msgid "Docker Memory Usage"
msgstr "Docker内存使用" msgstr "Docker 内存使用"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Docker Network I/O" msgid "Docker Network I/O"
msgstr "Docker网络I/O" msgstr "Docker 网络 I/O"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Documentation" msgid "Documentation"
@@ -603,15 +603,15 @@ msgstr "系统负载"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Load Average 15m" msgid "Load Average 15m"
msgstr "15分钟内的平均负载" msgstr "15 分钟内的平均负载"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Load Average 1m" msgid "Load Average 1m"
msgstr "1分钟负载平均值" msgstr "1 分钟负载平均值"
#: src/lib/alerts.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-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -660,11 +660,11 @@ msgstr "内存"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
msgstr "内存使用" msgstr "内存使用"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Docker 容器的内存使用" msgstr "Docker 容器的内存使用"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
@@ -709,7 +709,7 @@ msgstr "通知"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "OAuth 2 / OIDC support" msgid "OAuth 2 / OIDC support"
msgstr "支持 OAuth 2 / OIDC" msgstr "支持 OAuth 2/OIDC"
#: src/components/routes/settings/config-yaml.tsx #: 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." 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 #: src/components/command-palette.tsx
msgid "Pages / Settings" msgid "Pages / Settings"
msgstr "页面/设置" msgstr "页面 / 设置"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
@@ -774,7 +774,7 @@ msgstr "已暂停 ({pausedSystemsLength})"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "请<0>配置SMTP服务器</0>以确保警报被传递。" msgstr "请<0>配置 SMTP 服务器</0>以确保警报被传递。"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Please check logs for more details." msgid "Please check logs for more details."
@@ -909,7 +909,7 @@ msgstr "登录"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "SMTP settings" msgid "SMTP settings"
msgstr "SMTP设置" msgstr "SMTP 设置"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Sort By" msgid "Sort By"
@@ -931,7 +931,7 @@ msgstr "系统使用的 SWAP 空间"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Swap Usage" msgid "Swap Usage"
msgstr "SWAP 使用" msgstr "SWAP 使用"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
@@ -1024,7 +1024,7 @@ msgstr "令牌"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/tokens-fingerprints.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."
@@ -1032,19 +1032,19 @@ 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 "令牌指纹用于验证到中心的WebSocket连接。" msgstr "令牌指纹用于验证到中心的 WebSocket 连接。"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold" msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "当1分钟负载平均值超过阈值时触发" msgstr "当 1 分钟负载平均值超过阈值时触发"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 15 minute load average exceeds a threshold" msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr "当15分钟负载平均值超过阈值时触发" msgstr "当 15 分钟负载平均值超过阈值时触发"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 5 minute load average exceeds a threshold" msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr "当5分钟内的平均负载超过阈值时触发" msgstr "当 5 分钟内的平均负载超过阈值时触发"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
@@ -1056,7 +1056,7 @@ msgstr "当网络的上/下行速度超过阈值时触发"
#: src/lib/alerts.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/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
@@ -1174,11 +1174,11 @@ msgstr "写入"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "YAML Config" msgid "YAML Config"
msgstr "YAML配置" msgstr "YAML 配置"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "YAML Configuration" msgid "YAML Configuration"
msgstr "YAML配置" msgstr "YAML 配置"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Your user settings have been updated." msgid "Your user settings have been updated."

View File

@@ -74,6 +74,19 @@ const Layout = () => {
document.documentElement.dir = direction document.documentElement.dir = direction
}, [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 ( return (
<DirectionProvider dir={direction}> <DirectionProvider dir={direction}>
{!authenticated ? ( {!authenticated ? (

View File

@@ -1,5 +1,13 @@
## 0.12.8 ## 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. - Add FreeBSD support for agent install script and update command.
## 0.12.7 ## 0.12.7

View File

@@ -1,7 +1,7 @@
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: {{ include "beszel.fullname" . }}-web name: {{ include "beszel.fullname" . }}
labels: labels:
{{- include "beszel.labels" . | nindent 4 }} {{- include "beszel.labels" . | nindent 4 }}
{{- if .Values.service.annotations }} {{- if .Values.service.annotations }}

View File

@@ -30,14 +30,10 @@ securityContext: {}
service: service:
enabled: true enabled: true
type: LoadBalancer annotations: {}
loadBalancerIP: "10.0.10.251" type: ClusterIP
loadBalancerIP: ""
port: 8090 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: ingress:
enabled: false enabled: false
@@ -96,7 +92,7 @@ persistentVolumeClaim:
accessModes: accessModes:
- ReadWriteOnce - ReadWriteOnce
storageClass: "retain-local-path" storageClass: ""
# -- volume claim size # -- volume claim size
size: "500Mi" size: "500Mi"