mirror of
https://github.com/henrygd/beszel.git
synced 2026-01-18 16:20:24 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
031abbfcb3 | ||
|
|
b59fcc26e5 | ||
|
|
acaa9381fe | ||
|
|
8d9e9260e6 | ||
|
|
0fc4a6daed | ||
|
|
af0c1d3af7 | ||
|
|
9ad3cd0ab9 | ||
|
|
00def272b0 | ||
|
|
383913505f | ||
|
|
ca8cb78c29 | ||
|
|
8821fb5dd0 | ||
|
|
3279a6ca53 | ||
|
|
6a1a98d73f | ||
|
|
1f067aad5b | ||
|
|
1388711105 |
@@ -76,6 +76,18 @@ builds:
|
||||
- goos: windows
|
||||
goarch: riscv64
|
||||
|
||||
- id: beszel-agent-linux-amd64-glibc
|
||||
binary: beszel-agent
|
||||
main: internal/cmd/agent/agent.go
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
flags:
|
||||
- -tags=glibc
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
|
||||
archives:
|
||||
- id: beszel-agent
|
||||
formats: [tar.gz]
|
||||
@@ -89,6 +101,15 @@ archives:
|
||||
- goos: windows
|
||||
formats: [zip]
|
||||
|
||||
- id: beszel-agent-linux-amd64-glibc
|
||||
formats: [tar.gz]
|
||||
ids:
|
||||
- beszel-agent-linux-amd64-glibc
|
||||
name_template: >-
|
||||
{{ .Binary }}_
|
||||
{{- .Os }}_
|
||||
{{- .Arch }}_glibc
|
||||
|
||||
- id: beszel
|
||||
formats: [tar.gz]
|
||||
ids:
|
||||
|
||||
22
agent/gpu.go
22
agent/gpu.go
@@ -5,6 +5,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
@@ -14,8 +15,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/henrygd/beszel/internal/entities/system"
|
||||
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -137,10 +136,10 @@ func (gm *GPUManager) getJetsonParser() func(output []byte) bool {
|
||||
// use closure to avoid recompiling the regex
|
||||
ramPattern := regexp.MustCompile(`RAM (\d+)/(\d+)MB`)
|
||||
gr3dPattern := regexp.MustCompile(`GR3D_FREQ (\d+)%`)
|
||||
tempPattern := regexp.MustCompile(`tj@(\d+\.?\d*)C`)
|
||||
tempPattern := regexp.MustCompile(`(?:tj|GPU)@(\d+\.?\d*)C`)
|
||||
// Orin Nano / NX do not have GPU specific power monitor
|
||||
// TODO: Maybe use VDD_IN for Nano / NX and add a total system power chart
|
||||
powerPattern := regexp.MustCompile(`(GPU_SOC|CPU_GPU_CV) (\d+)mW`)
|
||||
powerPattern := regexp.MustCompile(`(GPU_SOC|CPU_GPU_CV)\s+(\d+)mW|VDD_SYS_GPU\s+(\d+)/\d+`)
|
||||
|
||||
// jetson devices have only one gpu so we'll just initialize here
|
||||
gpuData := &system.GPUData{Name: "GPU"}
|
||||
@@ -169,7 +168,13 @@ func (gm *GPUManager) getJetsonParser() func(output []byte) bool {
|
||||
// Parse power usage
|
||||
powerMatches := powerPattern.FindSubmatch(output)
|
||||
if powerMatches != nil {
|
||||
power, _ := strconv.ParseFloat(string(powerMatches[2]), 64)
|
||||
// powerMatches[2] is the "(GPU_SOC|CPU_GPU_CV) <N>mW" capture
|
||||
// powerMatches[3] is the "VDD_SYS_GPU <N>/<N>" capture
|
||||
powerStr := string(powerMatches[2])
|
||||
if powerStr == "" {
|
||||
powerStr = string(powerMatches[3])
|
||||
}
|
||||
power, _ := strconv.ParseFloat(powerStr, 64)
|
||||
gpuData.Power += power / milliwattsInAWatt
|
||||
}
|
||||
gpuData.Count++
|
||||
@@ -232,10 +237,11 @@ func (gm *GPUManager) parseAmdData(output []byte) bool {
|
||||
totalMemory, _ := strconv.ParseFloat(v.MemoryTotal, 64)
|
||||
usage, _ := strconv.ParseFloat(v.Usage, 64)
|
||||
|
||||
if _, ok := gm.GpuDataMap[v.ID]; !ok {
|
||||
gm.GpuDataMap[v.ID] = &system.GPUData{Name: v.Name}
|
||||
id := v.ID
|
||||
if _, ok := gm.GpuDataMap[id]; !ok {
|
||||
gm.GpuDataMap[id] = &system.GPUData{Name: v.Name}
|
||||
}
|
||||
gpu := gm.GpuDataMap[v.ID]
|
||||
gpu := gm.GpuDataMap[id]
|
||||
gpu.Temperature, _ = strconv.ParseFloat(v.Temperature, 64)
|
||||
gpu.MemoryUsed = bytesToMegabytes(memoryUsage)
|
||||
gpu.MemoryTotal = bytesToMegabytes(totalMemory)
|
||||
|
||||
@@ -27,10 +27,11 @@ func (gm *GPUManager) updateIntelFromStats(sample *intelGpuStats) bool {
|
||||
defer gm.Unlock()
|
||||
|
||||
// only one gpu for now - cmd doesn't provide all by default
|
||||
gpuData, ok := gm.GpuDataMap["0"]
|
||||
id := "i0" // prefix with i to avoid conflicts with nvidia card ids
|
||||
gpuData, ok := gm.GpuDataMap[id]
|
||||
if !ok {
|
||||
gpuData = &system.GPUData{Name: "GPU", Engines: make(map[string]float64)}
|
||||
gm.GpuDataMap["0"] = gpuData
|
||||
gm.GpuDataMap[id] = gpuData
|
||||
}
|
||||
|
||||
gpuData.Power += sample.PowerGPU
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build (linux || windows) && (amd64 || arm64)
|
||||
//go:build amd64 && (windows || (linux && glibc))
|
||||
|
||||
package agent
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build linux && (amd64 || arm64)
|
||||
//go:build glibc && linux && amd64
|
||||
|
||||
package agent
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build (!linux && !windows) || (!amd64 && !arm64)
|
||||
//go:build (!linux && !windows) || !amd64 || (linux && !glibc)
|
||||
|
||||
package agent
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build windows && (amd64 || arm64)
|
||||
//go:build windows && amd64
|
||||
|
||||
package agent
|
||||
|
||||
|
||||
@@ -307,6 +307,19 @@ func TestParseJetsonData(t *testing.T) {
|
||||
Count: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "orin-style output with GPU@ temp and VDD_SYS_GPU power",
|
||||
input: "RAM 3276/7859MB (lfb 5x4MB) SWAP 1626/12122MB (cached 181MB) CPU [44%@1421,49%@2031,67%@2034,17%@1420,25%@1419,8%@1420] EMC_FREQ 1%@1866 GR3D_FREQ 0%@114 APE 150 MTS fg 1% bg 1% PLL@42.5C MCPU@42.5C PMIC@50C Tboard@38C GPU@39.5C BCPU@42.5C thermal@41.3C Tdiode@39.25C VDD_SYS_GPU 182/182 VDD_SYS_SOC 730/730 VDD_4V0_WIFI 0/0 VDD_IN 5297/5297 VDD_SYS_CPU 1917/1917 VDD_SYS_DDR 1241/1241",
|
||||
wantMetrics: &system.GPUData{
|
||||
Name: "GPU",
|
||||
MemoryUsed: 3276.0,
|
||||
MemoryTotal: 7859.0,
|
||||
Usage: 0.0,
|
||||
Power: 0.182, // 182mW -> 0.182W
|
||||
Temperature: 39.5,
|
||||
Count: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -1372,7 +1385,7 @@ func TestIntelUpdateFromStats(t *testing.T) {
|
||||
ok := gm.updateIntelFromStats(&sample1)
|
||||
assert.True(t, ok)
|
||||
|
||||
gpu := gm.GpuDataMap["0"]
|
||||
gpu := gm.GpuDataMap["i0"]
|
||||
require.NotNil(t, gpu)
|
||||
assert.Equal(t, "GPU", gpu.Name)
|
||||
assert.EqualValues(t, 10.5, gpu.Power)
|
||||
@@ -1394,7 +1407,7 @@ func TestIntelUpdateFromStats(t *testing.T) {
|
||||
ok = gm.updateIntelFromStats(&sample2)
|
||||
assert.True(t, ok)
|
||||
|
||||
gpu = gm.GpuDataMap["0"]
|
||||
gpu = gm.GpuDataMap["i0"]
|
||||
require.NotNil(t, gpu)
|
||||
assert.EqualValues(t, 10.5, gpu.Power)
|
||||
assert.EqualValues(t, 30.0, gpu.Engines["Render/3D"]) // 20 + 10
|
||||
@@ -1433,7 +1446,7 @@ echo "298 295 278 51 2.20 3.12 1675 942 5.75 1 2 9.50
|
||||
t.Fatalf("collectIntelStats error: %v", err)
|
||||
}
|
||||
|
||||
gpu := gm.GpuDataMap["0"]
|
||||
gpu := gm.GpuDataMap["i0"]
|
||||
require.NotNil(t, gpu)
|
||||
// Power should be sum of samples 2-4 (first is skipped): 2.0 + 1.8 + 2.2 = 6.0
|
||||
assert.EqualValues(t, 6.0, gpu.Power)
|
||||
|
||||
@@ -435,7 +435,7 @@ func (sm *SmartManager) CollectSmart(deviceInfo *DeviceInfo) error {
|
||||
defer cancel()
|
||||
|
||||
// Try with -n standby first if we have existing data
|
||||
args := sm.smartctlArgs(deviceInfo, true)
|
||||
args := sm.smartctlArgs(deviceInfo, hasExistingData)
|
||||
cmd := exec.CommandContext(ctx, sm.binPath, args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
|
||||
|
||||
@@ -19,11 +19,11 @@ func TestSystemdManagerGetServiceStats(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test with refresh = true
|
||||
result := manager.getServiceStats(true)
|
||||
result := manager.getServiceStats("any-service", true)
|
||||
assert.Nil(t, result)
|
||||
|
||||
// Test with refresh = false
|
||||
result = manager.getServiceStats(false)
|
||||
result = manager.getServiceStats("any-service", false)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import "github.com/blang/semver"
|
||||
|
||||
const (
|
||||
// Version is the current version of the application.
|
||||
Version = "0.18.0"
|
||||
Version = "0.18.2"
|
||||
// AppName is the name of the application.
|
||||
AppName = "beszel"
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM --platform=$BUILDPLATFORM golang:alpine AS builder
|
||||
FROM --platform=$BUILDPLATFORM golang:bookworm AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -10,7 +10,7 @@ COPY . ./
|
||||
|
||||
# Build
|
||||
ARG TARGETOS TARGETARCH
|
||||
RUN CGO_ENABLED=0 GOGC=75 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags "-w -s" -o /agent ./internal/cmd/agent
|
||||
RUN CGO_ENABLED=0 GOGC=75 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -tags glibc -ldflags "-w -s" -o /agent ./internal/cmd/agent
|
||||
|
||||
# --------------------------
|
||||
# Smartmontools builder stage
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -345,5 +346,32 @@ func archiveSuffix(binaryName, goos, goarch string) string {
|
||||
if goos == "windows" {
|
||||
return fmt.Sprintf("%s_%s_%s.zip", binaryName, goos, goarch)
|
||||
}
|
||||
// Use glibc build for agent on glibc systems (includes NVML support via purego)
|
||||
if binaryName == "beszel-agent" && goos == "linux" && goarch == "amd64" && isGlibc() {
|
||||
return fmt.Sprintf("%s_%s_%s_glibc.tar.gz", binaryName, goos, goarch)
|
||||
}
|
||||
return fmt.Sprintf("%s_%s_%s.tar.gz", binaryName, goos, goarch)
|
||||
}
|
||||
|
||||
func isGlibc() bool {
|
||||
for _, path := range []string{
|
||||
"/lib64/ld-linux-x86-64.so.2", // common on many distros
|
||||
"/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2", // Debian/Ubuntu
|
||||
"/lib/ld-linux-x86-64.so.2", // alternate
|
||||
} {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
// Fallback to ldd output when present (musl ldd reports musl, glibc reports GNU libc/glibc).
|
||||
if lddPath, err := exec.LookPath("ldd"); err == nil {
|
||||
out, err := exec.Command(lddPath, "--version").CombinedOutput()
|
||||
if err == nil {
|
||||
s := strings.ToLower(string(out))
|
||||
if strings.Contains(s, "gnu libc") || strings.Contains(s, "glibc") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
4
internal/site/package-lock.json
generated
4
internal/site/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "beszel",
|
||||
"version": "0.18.0",
|
||||
"version": "0.18.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "beszel",
|
||||
"version": "0.18.0",
|
||||
"version": "0.18.2",
|
||||
"dependencies": {
|
||||
"@henrygd/queue": "^1.0.7",
|
||||
"@henrygd/semaphore": "^0.0.2",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "beszel",
|
||||
"private": true,
|
||||
"version": "0.18.0",
|
||||
"version": "0.18.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host",
|
||||
@@ -77,4 +77,4 @@
|
||||
"optionalDependencies": {
|
||||
"@esbuild/linux-arm64": "^0.21.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,8 +57,13 @@ export default function ContainersTable({ systemId }: { systemId?: string }) {
|
||||
.then(
|
||||
({ items }) => {
|
||||
if (items.length === 0) {
|
||||
setData([]);
|
||||
return;
|
||||
setData((curItems) => {
|
||||
if (systemId) {
|
||||
return curItems?.filter((item) => item.system !== systemId) ?? []
|
||||
}
|
||||
return []
|
||||
})
|
||||
return
|
||||
}
|
||||
setData((curItems) => {
|
||||
const lastUpdated = Math.max(items[0].updated, items.at(-1)?.updated ?? 0)
|
||||
@@ -280,7 +285,7 @@ async function getInfoHtml(container: ContainerRecord): Promise<string> {
|
||||
])
|
||||
try {
|
||||
info = JSON.stringify(JSON.parse(info), null, 2)
|
||||
} catch (_) {}
|
||||
} catch (_) { }
|
||||
return info ? highlighter.codeToHtml(info, { lang: "json", theme: syntaxTheme }) : t`No results.`
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
@@ -337,7 +342,7 @@ function ContainerSheet({
|
||||
setLogsDisplay("")
|
||||
setInfoDisplay("")
|
||||
if (!container) return
|
||||
;(async () => {
|
||||
; (async () => {
|
||||
const [logsHtml, infoHtml] = await Promise.all([getLogsHtml(container), getInfoHtml(container)])
|
||||
setLogsDisplay(logsHtml)
|
||||
setInfoDisplay(infoHtml)
|
||||
|
||||
@@ -407,23 +407,20 @@ export default memo(function SystemDetail({ id }: { id: string }) {
|
||||
let hasGpuPowerData = false
|
||||
|
||||
if (lastGpus) {
|
||||
// check if there are any GPUs with engines
|
||||
for (const id in lastGpus) {
|
||||
hasGpuData = true
|
||||
if (lastGpus[id].e !== undefined) {
|
||||
hasGpuEnginesData = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// check if there are any GPUs with power data
|
||||
for (let i = 0; i < systemStats.length && !hasGpuPowerData; i++) {
|
||||
// check if there are any GPUs at all
|
||||
hasGpuData = Object.keys(lastGpus).length > 0
|
||||
// check if there are any GPUs with engines or power data
|
||||
for (let i = 0; i < systemStats.length && (!hasGpuEnginesData || !hasGpuPowerData); i++) {
|
||||
const gpus = systemStats[i].stats?.g
|
||||
if (!gpus) continue
|
||||
for (const id in gpus) {
|
||||
if (gpus[id].p !== undefined || gpus[id].pp !== undefined) {
|
||||
hasGpuPowerData = true
|
||||
break
|
||||
if (!hasGpuEnginesData && gpus[id].e !== undefined) {
|
||||
hasGpuEnginesData = true
|
||||
}
|
||||
if (!hasGpuPowerData && (gpus[id].p !== undefined || gpus[id].pp !== undefined)) {
|
||||
hasGpuPowerData = true
|
||||
}
|
||||
if (hasGpuEnginesData && hasGpuPowerData) break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -891,16 +888,30 @@ export default memo(function SystemDetail({ id }: { id: string }) {
|
||||
})
|
||||
|
||||
function GpuEnginesChart({ chartData }: { chartData: ChartData }) {
|
||||
const dataPoints: DataPoint[] = []
|
||||
const engines = Object.keys(chartData.systemStats?.at(-1)?.stats.g?.[0]?.e ?? {}).sort()
|
||||
for (const engine of engines) {
|
||||
dataPoints.push({
|
||||
label: engine,
|
||||
dataKey: ({ stats }: SystemStatsRecord) => stats?.g?.[0]?.e?.[engine] ?? 0,
|
||||
color: `hsl(${140 + (((engines.indexOf(engine) * 360) / engines.length) % 360)}, 65%, 52%)`,
|
||||
opacity: 0.35,
|
||||
})
|
||||
const { gpuId, engines } = useMemo(() => {
|
||||
for (let i = chartData.systemStats.length - 1; i >= 0; i--) {
|
||||
const gpus = chartData.systemStats[i].stats?.g
|
||||
if (!gpus) continue
|
||||
for (const id in gpus) {
|
||||
if (gpus[id].e) {
|
||||
return { gpuId: id, engines: Object.keys(gpus[id].e).sort() }
|
||||
}
|
||||
}
|
||||
}
|
||||
return { gpuId: null, engines: [] }
|
||||
}, [chartData.systemStats])
|
||||
|
||||
if (!gpuId) {
|
||||
return null
|
||||
}
|
||||
|
||||
const dataPoints: DataPoint[] = engines.map((engine, i) => ({
|
||||
label: engine,
|
||||
dataKey: ({ stats }: SystemStatsRecord) => stats?.g?.[gpuId]?.e?.[engine] ?? 0,
|
||||
color: `hsl(${140 + (((i * 360) / engines.length) % 360)}, 65%, 52%)`,
|
||||
opacity: 0.35,
|
||||
}))
|
||||
|
||||
return (
|
||||
<LineChartDefault
|
||||
legend={true}
|
||||
|
||||
@@ -128,17 +128,32 @@ export function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef<Syste
|
||||
cell: (info) => {
|
||||
const { name, id } = info.row.original
|
||||
const longestName = useStore($longestSystemNameLen)
|
||||
const linkUrl = getPagePath($router, "system", { id })
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className="flex gap-2 items-center font-medium text-sm text-nowrap md:ps-1">
|
||||
<IndicatorDot system={info.row.original} />
|
||||
{/* NOTE: change to 1 ch if switching to monospace font */}
|
||||
<span className="truncate" style={{ width: `${longestName / 1.1}ch` }}>
|
||||
<Link
|
||||
href={linkUrl}
|
||||
tabIndex={-1}
|
||||
className="truncate z-10 relative"
|
||||
style={{ width: `${longestName / 1.05}ch` }}
|
||||
onMouseEnter={(e) => {
|
||||
// set title on hover if text is truncated to show full name
|
||||
const a = e.currentTarget
|
||||
if (a.scrollWidth > a.clientWidth) {
|
||||
a.title = name
|
||||
} else {
|
||||
a.removeAttribute("title")
|
||||
}
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
</Link>
|
||||
</span>
|
||||
<Link
|
||||
href={getPagePath($router, "system", { id })}
|
||||
href={linkUrl}
|
||||
className="inset-0 absolute size-full"
|
||||
aria-label={name}
|
||||
></Link>
|
||||
@@ -439,9 +454,9 @@ function TableCellWithMeter(info: CellContext<SystemRecord, unknown>) {
|
||||
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
|
||||
(threshold === MeterState.Good && STATUS_COLORS.up) ||
|
||||
(threshold === MeterState.Warn && STATUS_COLORS.pending) ||
|
||||
STATUS_COLORS.down
|
||||
)
|
||||
return (
|
||||
<div className="flex gap-2 items-center tabular-nums tracking-tight w-full">
|
||||
@@ -553,7 +568,7 @@ export function IndicatorDot({ system, className }: { system: SystemRecord; clas
|
||||
return (
|
||||
<span
|
||||
className={cn("shrink-0 size-2 rounded-full", className)}
|
||||
// style={{ marginBottom: "-1px" }}
|
||||
// style={{ marginBottom: "-1px" }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
$pausedSystems,
|
||||
$upSystems,
|
||||
} from "@/lib/stores"
|
||||
import { updateFavicon } from "@/lib/utils"
|
||||
import { getVisualStringWidth, updateFavicon } from "@/lib/utils"
|
||||
import type { SystemRecord } from "@/types"
|
||||
import { SystemStatus } from "./enums"
|
||||
|
||||
@@ -79,7 +79,7 @@ function onSystemsChanged(_: Record<string, SystemRecord>, changedSystem: System
|
||||
|
||||
// Update longest system name length
|
||||
const longestName = $longestSystemNameLen.get()
|
||||
const nameLen = Math.min(MAX_SYSTEM_NAME_LENGTH, changedSystem?.name.length || 0)
|
||||
const nameLen = Math.min(MAX_SYSTEM_NAME_LENGTH, getVisualStringWidth(changedSystem?.name || ""))
|
||||
if (nameLen > longestName) {
|
||||
$longestSystemNameLen.set(nameLen)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export async function copyToClipboard(content: string) {
|
||||
duration,
|
||||
description: t`Copied to clipboard`,
|
||||
})
|
||||
} catch (e) {
|
||||
} catch (_e) {
|
||||
$copyContent.set(content)
|
||||
}
|
||||
}
|
||||
@@ -316,7 +316,7 @@ export const getHostDisplayValue = (system: SystemRecord): string => system.host
|
||||
export const generateToken = () => {
|
||||
try {
|
||||
return crypto?.randomUUID()
|
||||
} catch (e) {
|
||||
} catch (_e) {
|
||||
return Array.from({ length: 2 }, () => (performance.now() * Math.random()).toString(16).replace(".", "-")).join("-")
|
||||
}
|
||||
}
|
||||
@@ -429,6 +429,30 @@ export function runOnce<T extends (...args: any[]) => any>(fn: T): T {
|
||||
}) as T
|
||||
}
|
||||
|
||||
/** Get the visual width of a string, accounting for full-width characters */
|
||||
export function getVisualStringWidth(str: string): number {
|
||||
let width = 0
|
||||
for (const char of str) {
|
||||
const code = char.codePointAt(0) || 0
|
||||
// Hangul Jamo and Syllables are often slightly thinner than Hanzi/Kanji
|
||||
if ((code >= 0x1100 && code <= 0x115f) || (code >= 0xac00 && code <= 0xd7af)) {
|
||||
width += 1.8
|
||||
continue
|
||||
}
|
||||
// Count CJK and other full-width characters as 2 units, others as 1
|
||||
// Arabic and Cyrillic are counted as 1
|
||||
const isFullWidth =
|
||||
(code >= 0x2e80 && code <= 0x9fff) || // CJK Radicals, Symbols, and Ideographs
|
||||
(code >= 0xf900 && code <= 0xfaff) || // CJK Compatibility Ideographs
|
||||
(code >= 0xfe30 && code <= 0xfe6f) || // CJK Compatibility Forms
|
||||
(code >= 0xff00 && code <= 0xff60) || // Fullwidth Forms
|
||||
(code >= 0xffe0 && code <= 0xffe6) || // Fullwidth Symbols
|
||||
code > 0xffff // Emojis and other supplementary plane characters
|
||||
width += isFullWidth ? 2 : 1
|
||||
}
|
||||
return width
|
||||
}
|
||||
|
||||
/** Format seconds to hours, minutes, or seconds */
|
||||
export function secondsToString(seconds: number, unit: "hour" | "minute" | "day"): string {
|
||||
const count = Math.floor(seconds / (unit === "hour" ? 3600 : unit === "minute" ? 60 : 86400))
|
||||
|
||||
@@ -22,7 +22,7 @@ msgstr ""
|
||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||
msgid "{0} of {1} row(s) selected."
|
||||
msgstr "{0} dari {1} baris dipilih."
|
||||
msgstr "{0} dari {1} baris terpilih."
|
||||
|
||||
#: src/components/routes/system/info-bar.tsx
|
||||
msgid "{cores, plural, one {# core} other {# cores}}"
|
||||
@@ -125,11 +125,11 @@ msgstr "Tambah URL"
|
||||
|
||||
#: src/components/routes/settings/general.tsx
|
||||
msgid "Adjust display options for charts."
|
||||
msgstr "Sesuaikan pengaturan tampilan untuk untuk grafik."
|
||||
msgstr "Sesuaikan tampilan grafik."
|
||||
|
||||
#: src/components/routes/settings/general.tsx
|
||||
msgid "Adjust the width of the main layout"
|
||||
msgstr "Sesuaikan lebar layout utama"
|
||||
msgstr "Sesuaikan lebar layar utama"
|
||||
|
||||
#: src/components/command-palette.tsx
|
||||
#: src/components/command-palette.tsx
|
||||
@@ -180,7 +180,7 @@ msgstr "Apakah anda yakin?"
|
||||
|
||||
#: src/components/copy-to-clipboard.tsx
|
||||
msgid "Automatic copy requires a secure context."
|
||||
msgstr "Menyalin otomatis memerlukan konteks yang aman."
|
||||
msgstr "Copy memerlukan https."
|
||||
|
||||
#: src/components/routes/system.tsx
|
||||
msgid "Average"
|
||||
@@ -215,7 +215,7 @@ msgstr "Rata-rata utilisasi {0}"
|
||||
|
||||
#: src/components/routes/system.tsx
|
||||
msgid "Average utilization of GPU engines"
|
||||
msgstr "Rata-rata utilisasi mesin GPU"
|
||||
msgstr "Rata-rata utilisasi GPU"
|
||||
|
||||
#: src/components/command-palette.tsx
|
||||
#: src/components/navbar.tsx
|
||||
@@ -225,12 +225,12 @@ msgstr "Cadangan"
|
||||
#: src/components/routes/system.tsx
|
||||
#: src/lib/alerts.ts
|
||||
msgid "Bandwidth"
|
||||
msgstr "Pita lebar"
|
||||
msgstr "Bandwith"
|
||||
|
||||
#. Battery label in systems table header
|
||||
#: src/components/systems-table/systems-table-columns.tsx
|
||||
msgid "Bat"
|
||||
msgstr "Bat"
|
||||
msgstr "Baterai"
|
||||
|
||||
#: src/components/routes/system.tsx
|
||||
#: src/lib/alerts.ts
|
||||
@@ -258,7 +258,7 @@ msgstr "Di bawah {0}{1} dalam {2, plural, one {# menit} other {# menit}} terakhi
|
||||
|
||||
#: src/components/login/auth-form.tsx
|
||||
msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers."
|
||||
msgstr "Beszel mendukung OpenID Connecnt dan banyak penyedia autentikasi OAuth2."
|
||||
msgstr "Beszel mendukung OpenID Connect dan OAuth2 dari berbagai penyedia layanan."
|
||||
|
||||
#: src/components/routes/settings/notifications.tsx
|
||||
msgid "Beszel uses <0>Shoutrrr</0> to integrate with popular notification services."
|
||||
@@ -330,7 +330,7 @@ msgstr "Ubah pengaturan umum aplikasi."
|
||||
|
||||
#: src/components/routes/system.tsx
|
||||
msgid "Charge"
|
||||
msgstr "Isi"
|
||||
msgstr "Isi baterai"
|
||||
|
||||
#. Context: Battery state
|
||||
#: src/lib/i18n.ts
|
||||
@@ -339,19 +339,19 @@ msgstr "Sedang mengisi"
|
||||
|
||||
#: src/components/routes/settings/general.tsx
|
||||
msgid "Chart options"
|
||||
msgstr "Pengaturan grafik"
|
||||
msgstr "Pilihan grafik"
|
||||
|
||||
#: src/components/login/forgot-pass-form.tsx
|
||||
msgid "Check {email} for a reset link."
|
||||
msgstr "Periksa {email} untuk tautan atur ulang."
|
||||
msgstr "Periksa {email} untuk tautan atur ulang password."
|
||||
|
||||
#: src/components/routes/settings/layout.tsx
|
||||
msgid "Check logs for more details."
|
||||
msgstr "Periksa catatan untuk lebih detail."
|
||||
msgstr "Periksa riwayat untuk lebih detail."
|
||||
|
||||
#: src/components/routes/settings/notifications.tsx
|
||||
msgid "Check your notification service"
|
||||
msgstr "Periksa penyedia jasa notifikasi anda"
|
||||
msgstr "Periksa jasa penyedia notifikasi anda"
|
||||
|
||||
#: src/components/containers-table/containers-table.tsx
|
||||
#: src/components/routes/system/smart-table.tsx
|
||||
@@ -500,11 +500,11 @@ msgstr "Kritis (%)"
|
||||
|
||||
#: src/components/routes/system/network-sheet.tsx
|
||||
msgid "Cumulative Download"
|
||||
msgstr "Download Kumulatif"
|
||||
msgstr "Akumulasi Download"
|
||||
|
||||
#: src/components/routes/system/network-sheet.tsx
|
||||
msgid "Cumulative Upload"
|
||||
msgstr "Upload Kumulatif"
|
||||
msgstr "Akumulasi Upload"
|
||||
|
||||
#. Context: Battery state
|
||||
#: src/components/routes/system.tsx
|
||||
@@ -523,7 +523,7 @@ msgstr "Harian"
|
||||
|
||||
#: src/components/routes/settings/general.tsx
|
||||
msgid "Default time period"
|
||||
msgstr "Periode waktu default"
|
||||
msgstr "Standar waktu"
|
||||
|
||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||
#: src/components/routes/settings/quiet-hours.tsx
|
||||
@@ -534,7 +534,7 @@ msgstr "Hapus"
|
||||
|
||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||
msgid "Delete fingerprint"
|
||||
msgstr "Hapus sidik jari"
|
||||
msgstr "Hapus fingerprint"
|
||||
|
||||
#: src/components/systemd-table/systemd-table.tsx
|
||||
msgid "Description"
|
||||
@@ -551,7 +551,7 @@ msgstr "Perangkat"
|
||||
#. Context: Battery state
|
||||
#: src/lib/i18n.ts
|
||||
msgid "Discharging"
|
||||
msgstr "Sedang mengosongkan"
|
||||
msgstr "Sedang tidak di charge"
|
||||
|
||||
#: src/components/systems-table/systems-table-columns.tsx
|
||||
msgid "Disk"
|
||||
@@ -615,12 +615,12 @@ msgstr "Durasi"
|
||||
#: src/components/routes/settings/quiet-hours.tsx
|
||||
#: src/components/systems-table/systems-table-columns.tsx
|
||||
msgid "Edit"
|
||||
msgstr "Edit"
|
||||
msgstr "Ubah"
|
||||
|
||||
#: src/components/add-system.tsx
|
||||
#: src/components/routes/settings/quiet-hours.tsx
|
||||
msgid "Edit {foo}"
|
||||
msgstr "Edit {foo}"
|
||||
msgstr "Ubah {foo}"
|
||||
|
||||
#: src/components/login/auth-form.tsx
|
||||
#: src/components/login/forgot-pass-form.tsx
|
||||
@@ -652,7 +652,7 @@ msgstr "Masukkan alamat email..."
|
||||
|
||||
#: src/components/login/otp-forms.tsx
|
||||
msgid "Enter your one-time password."
|
||||
msgstr "Masukkan kata sandi satu kali anda."
|
||||
msgstr "Masukkan otp anda."
|
||||
|
||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||
msgid "Ephemeral"
|
||||
@@ -694,15 +694,15 @@ msgstr "Kedaluwarsa setelah satu jam atau saat restart hub."
|
||||
|
||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||
msgid "Export"
|
||||
msgstr "Ekspor"
|
||||
msgstr "Export"
|
||||
|
||||
#: src/components/routes/settings/config-yaml.tsx
|
||||
msgid "Export configuration"
|
||||
msgstr "Ekspor konfigurasi"
|
||||
msgstr "Export konfigurasi"
|
||||
|
||||
#: src/components/routes/settings/config-yaml.tsx
|
||||
msgid "Export your current systems configuration."
|
||||
msgstr "Ekspor konfigurasi sistem anda saat ini."
|
||||
msgstr "Export konfigurasi sistem anda saat ini."
|
||||
|
||||
#: src/components/routes/settings/general.tsx
|
||||
msgid "Fahrenheit (°F)"
|
||||
@@ -728,7 +728,7 @@ msgstr "Gagal menyimpan pengaturan"
|
||||
|
||||
#: src/components/routes/settings/notifications.tsx
|
||||
msgid "Failed to send test notification"
|
||||
msgstr "Gagal mengirim notifikasi tes"
|
||||
msgstr "Gagal mengirim tes notifikasi"
|
||||
|
||||
#: src/components/alerts/alerts-sheet.tsx
|
||||
msgid "Failed to update alert"
|
||||
@@ -750,7 +750,7 @@ msgstr "Filter..."
|
||||
|
||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||
msgid "Fingerprint"
|
||||
msgstr "Sidik jari"
|
||||
msgstr "Fingerprint"
|
||||
|
||||
#: src/components/routes/system/smart-table.tsx
|
||||
msgid "Firmware"
|
||||
@@ -787,7 +787,7 @@ msgstr "Global"
|
||||
|
||||
#: src/components/routes/system.tsx
|
||||
msgid "GPU Engines"
|
||||
msgstr "Mesin GPU"
|
||||
msgstr "GPU"
|
||||
|
||||
#: src/components/routes/system.tsx
|
||||
msgid "GPU Power Draw"
|
||||
@@ -799,7 +799,7 @@ msgstr "Penggunaan GPU"
|
||||
|
||||
#: src/components/systems-table/systems-table.tsx
|
||||
msgid "Grid"
|
||||
msgstr "Grid"
|
||||
msgstr "Kartu"
|
||||
|
||||
#: src/components/containers-table/containers-table-columns.tsx
|
||||
msgid "Health"
|
||||
@@ -843,11 +843,11 @@ msgstr "Bahasa"
|
||||
|
||||
#: src/components/systems-table/systems-table.tsx
|
||||
msgid "Layout"
|
||||
msgstr "Layout"
|
||||
msgstr "Tampilan"
|
||||
|
||||
#: src/components/routes/settings/general.tsx
|
||||
msgid "Layout width"
|
||||
msgstr "Lebar layout"
|
||||
msgstr "Lebar tampilan"
|
||||
|
||||
#: src/components/systemd-table/systemd-table.tsx
|
||||
msgid "Lifecycle"
|
||||
@@ -877,11 +877,11 @@ msgstr "Rata-rata Beban 5m"
|
||||
#. Short label for load average
|
||||
#: src/components/systems-table/systems-table-columns.tsx
|
||||
msgid "Load Avg"
|
||||
msgstr "Rata Beban"
|
||||
msgstr "Rata-rata Beban"
|
||||
|
||||
#: src/components/systemd-table/systemd-table.tsx
|
||||
msgid "Load state"
|
||||
msgstr "Status beban"
|
||||
msgstr "Beban saat ini"
|
||||
|
||||
#: src/components/systemd-table/systemd-table.tsx
|
||||
msgid "Loading..."
|
||||
@@ -889,7 +889,7 @@ msgstr "Memuat..."
|
||||
|
||||
#: src/components/navbar.tsx
|
||||
msgid "Log Out"
|
||||
msgstr "Keluar"
|
||||
msgstr "Log Out"
|
||||
|
||||
#: src/components/login/login.tsx
|
||||
msgid "Login"
|
||||
@@ -969,7 +969,7 @@ msgstr "Nama"
|
||||
#: src/components/containers-table/containers-table-columns.tsx
|
||||
#: src/components/systems-table/systems-table-columns.tsx
|
||||
msgid "Net"
|
||||
msgstr "Net"
|
||||
msgstr "Jaringan"
|
||||
|
||||
#: src/components/routes/system.tsx
|
||||
msgid "Network traffic of docker containers"
|
||||
@@ -1014,7 +1014,7 @@ msgstr "Tidak ada atribut S.M.A.R.T. yang tersedia untuk perangkat ini."
|
||||
#: src/components/systems-table/systems-table.tsx
|
||||
#: src/components/systems-table/systems-table.tsx
|
||||
msgid "No systems found."
|
||||
msgstr "Tidak ada sistem ditemukan."
|
||||
msgstr "Sistem tidak ditemukan."
|
||||
|
||||
#: src/components/command-palette.tsx
|
||||
#: src/components/routes/settings/layout.tsx
|
||||
@@ -1033,11 +1033,11 @@ msgstr "Pada setiap restart, sistem dalam database akan diperbarui untuk mencoco
|
||||
#: src/components/routes/settings/quiet-hours.tsx
|
||||
#: src/components/routes/settings/quiet-hours.tsx
|
||||
msgid "One-time"
|
||||
msgstr "Sekali"
|
||||
msgstr "Sekali pakai"
|
||||
|
||||
#: src/components/login/auth-form.tsx
|
||||
msgid "One-time password"
|
||||
msgstr "Kata sandi sekali pakai"
|
||||
msgstr "Kata sandi sekali pakai (OTP)"
|
||||
|
||||
#: src/components/routes/settings/quiet-hours.tsx
|
||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||
@@ -1123,7 +1123,7 @@ msgstr "Permanen"
|
||||
|
||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||
msgid "Persistence"
|
||||
msgstr "Ketekunan"
|
||||
msgstr "Tetap berlaku"
|
||||
|
||||
#: src/components/routes/settings/notifications.tsx
|
||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||
@@ -1174,7 +1174,7 @@ msgstr "Utilisasi tepat pada waktu yang direkam"
|
||||
|
||||
#: src/components/routes/settings/general.tsx
|
||||
msgid "Preferred Language"
|
||||
msgstr "Bahasa Pilihan"
|
||||
msgstr "Pilihan Bahasa"
|
||||
|
||||
#: src/components/systemd-table/systemd-table.tsx
|
||||
msgid "Process started"
|
||||
@@ -1203,7 +1203,7 @@ msgstr "Diterima"
|
||||
#: src/components/containers-table/containers-table.tsx
|
||||
#: src/components/routes/system/smart-table.tsx
|
||||
msgid "Refresh"
|
||||
msgstr "Refresh"
|
||||
msgstr "Muat ulang"
|
||||
|
||||
#: src/components/systemd-table/systemd-table.tsx
|
||||
msgid "Relationships"
|
||||
@@ -1250,7 +1250,7 @@ msgstr "Root"
|
||||
|
||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||
msgid "Rotate token"
|
||||
msgstr "Putar token"
|
||||
msgstr "Ganti ulang token"
|
||||
|
||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||
msgid "Rows per page"
|
||||
@@ -1291,7 +1291,7 @@ msgstr "Jadwal"
|
||||
|
||||
#: src/components/routes/settings/quiet-hours.tsx
|
||||
msgid "Schedule quiet hours where notifications will not be sent, such as during maintenance periods."
|
||||
msgstr "Jadwalkan jam tenang dimana notifikasi tidak akan dikirim, seperti selama periode pemeliharaan."
|
||||
msgstr "Jadwalkan jam tenang dimana notifikasi tidak akan dikirim, seperti saat periode pemeliharaan."
|
||||
|
||||
#: src/components/routes/settings/quiet-hours.tsx
|
||||
msgid "Schedule quiet hours where notifications will not be sent."
|
||||
@@ -1430,7 +1430,7 @@ msgstr "Tugas"
|
||||
#: src/components/routes/system/smart-table.tsx
|
||||
#: src/components/systems-table/systems-table-columns.tsx
|
||||
msgid "Temp"
|
||||
msgstr "Temp"
|
||||
msgstr "Temperatur"
|
||||
|
||||
#: src/components/routes/system.tsx
|
||||
#: src/lib/alerts.ts
|
||||
@@ -1459,7 +1459,7 @@ msgstr "Kemudian masuk ke backend dan reset kata sandi akun pengguna anda di tab
|
||||
|
||||
#: 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."
|
||||
msgstr ""
|
||||
msgstr "Aksi ini tidak dapat di kembalikan. ini akan menghapus permanen semua record {name} dari database"
|
||||
|
||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||
msgid "This will permanently delete all selected records from the database."
|
||||
@@ -1484,11 +1484,11 @@ msgstr "Ke email"
|
||||
#: src/components/routes/system/info-bar.tsx
|
||||
#: src/components/routes/system/info-bar.tsx
|
||||
msgid "Toggle grid"
|
||||
msgstr "Toggle grid"
|
||||
msgstr "Ganti tampilan"
|
||||
|
||||
#: src/components/mode-toggle.tsx
|
||||
msgid "Toggle theme"
|
||||
msgstr "Toggle tema"
|
||||
msgstr "Ganti tema"
|
||||
|
||||
#: src/components/add-system.tsx
|
||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||
@@ -1499,15 +1499,15 @@ msgstr "Token"
|
||||
#: src/components/routes/settings/layout.tsx
|
||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||
msgid "Tokens & Fingerprints"
|
||||
msgstr "Token & Sidik Jari"
|
||||
msgstr "Token & Fingerprint"
|
||||
|
||||
#: 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."
|
||||
msgstr "Token memungkinkan agen untuk terhubung dan mendaftar. Sidik jari adalah pengidentifikasi stabil unik untuk setiap sistem, ditetapkan pada koneksi pertama."
|
||||
msgstr "Token memungkinkan agen untuk terhubung dan mendaftar. Fingerprint adalah sistem indentifikasi yang stabil dan unik untuk setiap sistem, diatur pada koneksi pertama."
|
||||
|
||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
|
||||
msgstr "Token dan sidik jari digunakan untuk mengautentikasi koneksi WebSocket ke hub."
|
||||
msgstr "Token dan Fingerprint digunakan untuk mengautentikasi koneksi WebSocket ke hub."
|
||||
|
||||
#: src/components/ui/chart.tsx
|
||||
#: src/components/ui/chart.tsx
|
||||
@@ -1553,11 +1553,11 @@ msgstr "Dipicu ketika sensor apa pun melebihi ambang batas"
|
||||
|
||||
#: src/lib/alerts.ts
|
||||
msgid "Triggers when battery charge drops below a threshold"
|
||||
msgstr "Dipicu ketika muatan baterai turun di bawah ambang batas"
|
||||
msgstr "Dipicu ketika baterai turun di bawah ambang batas"
|
||||
|
||||
#: src/lib/alerts.ts
|
||||
msgid "Triggers when combined up/down exceeds a threshold"
|
||||
msgstr "Dipicu ketika kombinasi up/down melebihi ambang batas"
|
||||
msgstr "Dipicu ketika up atau down melebihi ambang batas"
|
||||
|
||||
#: src/lib/alerts.ts
|
||||
msgid "Triggers when CPU usage exceeds a threshold"
|
||||
@@ -1592,7 +1592,7 @@ msgstr "File unit"
|
||||
#. Temperature / network units
|
||||
#: src/components/routes/settings/general.tsx
|
||||
msgid "Unit preferences"
|
||||
msgstr "Preferensi unit"
|
||||
msgstr "Pengaturan satuan"
|
||||
|
||||
#: src/components/command-palette.tsx
|
||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||
@@ -1613,11 +1613,11 @@ msgstr "Tidak terbatas"
|
||||
#: src/components/routes/system/info-bar.tsx
|
||||
#: src/components/systems-table/systems-table-columns.tsx
|
||||
msgid "Up"
|
||||
msgstr "Up"
|
||||
msgstr "Nyala"
|
||||
|
||||
#: src/components/systems-table/systems-table.tsx
|
||||
msgid "Up ({upSystemsLength})"
|
||||
msgstr "Up ({upSystemsLength})"
|
||||
msgstr "Nyala selama ({upSystemsLength})"
|
||||
|
||||
#: src/components/routes/settings/quiet-hours.tsx
|
||||
msgid "Update"
|
||||
@@ -1682,7 +1682,7 @@ msgstr "Lihat 200 peringatan terbaru anda."
|
||||
|
||||
#: src/components/systems-table/systems-table.tsx
|
||||
msgid "Visible Fields"
|
||||
msgstr "Field yang Terlihat"
|
||||
msgstr "Metrik yang Terlihat"
|
||||
|
||||
#: src/components/routes/system.tsx
|
||||
msgid "Waiting for enough records to display"
|
||||
@@ -1706,11 +1706,11 @@ msgstr "Ambang peringatan"
|
||||
|
||||
#: src/components/routes/settings/notifications.tsx
|
||||
msgid "Webhook / Push notifications"
|
||||
msgstr "Notifikasi Webhook / Push"
|
||||
msgstr "Webhook / Push notifikasi"
|
||||
|
||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||
msgid "When enabled, this token allows agents to self-register without prior system creation."
|
||||
msgstr "Ketika diaktifkan, token ini memungkinkan agen untuk mendaftar sendiri tanpa pembuatan sistem sebelumnya."
|
||||
msgstr "Ketika diaktifkan, token ini memungkinkan agen untuk mendaftar sendiri tanpa pembuatan sistem."
|
||||
|
||||
#: src/components/add-system.tsx
|
||||
#: src/components/routes/settings/tokens-fingerprints.tsx
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
## 0.18.2
|
||||
|
||||
- Add separate dynamically linked glibc build for Linux. (#1618)
|
||||
|
||||
- Fix GPU ID collision between Intel and NVIDIA collectors. (#1522)
|
||||
|
||||
- Only hide GPU engine graph if entire usage is 0%. (#1624)
|
||||
|
||||
- Add Jetson tegrastats regex support for pre-Jetpack 5 versions. (#1631)
|
||||
|
||||
- Improve Indonesian translations. (#1625)
|
||||
|
||||
## 0.18.1
|
||||
|
||||
- Fix bug in 0.18.0 where all containers were cleared from the "All Containers" page when any system returned no containers.
|
||||
|
||||
## 0.18.0
|
||||
|
||||
- Add experimental NVML GPU collector. (#1522, #1587)
|
||||
|
||||
@@ -12,6 +12,24 @@ is_freebsd() {
|
||||
[ "$(uname -s)" = "FreeBSD" ]
|
||||
}
|
||||
|
||||
is_glibc() {
|
||||
# Prefer glibc-enabled agent (NVML via purego) on linux/amd64 glibc systems.
|
||||
# Check common dynamic loader paths first (fast + reliable).
|
||||
for p in \
|
||||
/lib64/ld-linux-x86-64.so.2 \
|
||||
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 \
|
||||
/lib/ld-linux-x86-64.so.2; do
|
||||
[ -e "$p" ] && return 0
|
||||
done
|
||||
|
||||
# Fallback to ldd output if available.
|
||||
if command -v ldd >/dev/null 2>&1; then
|
||||
ldd --version 2>&1 | grep -qiE 'gnu libc|glibc' && return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
# If SELinux is enabled, set the context of the binary
|
||||
set_selinux_context() {
|
||||
@@ -522,7 +540,7 @@ if is_alpine; then
|
||||
# Add the user to the docker group to allow access to the Docker socket if group docker exists
|
||||
if getent group docker; then
|
||||
echo "Adding beszel to docker group"
|
||||
usermod -aG docker beszel
|
||||
addgroup beszel docker
|
||||
fi
|
||||
|
||||
elif is_openwrt; then
|
||||
@@ -598,6 +616,9 @@ echo "Downloading and installing the agent..."
|
||||
OS=$(uname -s | sed -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/')
|
||||
ARCH=$(detect_architecture)
|
||||
FILE_NAME="beszel-agent_${OS}_${ARCH}.tar.gz"
|
||||
if [ "$OS" = "linux" ] && [ "$ARCH" = "amd64" ] && is_glibc; then
|
||||
FILE_NAME="beszel-agent_${OS}_${ARCH}_glibc.tar.gz"
|
||||
fi
|
||||
|
||||
# Determine version to install
|
||||
if [ "$VERSION" = "latest" ]; then
|
||||
|
||||
Reference in New Issue
Block a user