Compare commits

...

5 Commits

Author SHA1 Message Date
henrygd
031abbfcb3 ui: conditional title attribute and better CJK truncation
- Adds CJK support for system name truncation
- Change tooltip to title attribute and show only if system name is truncated
2026-01-16 18:17:45 -05:00
Fahleen Arif
b59fcc26e5 feat: add tooltip to system name in systems table for better accessibility (#1640) 2026-01-16 17:43:29 -05:00
Tamás Vince
acaa9381fe fix: update smartctlArgs call to use hasExistingData flag (#1645) 2026-01-16 15:30:52 -05:00
Loïc Tosser
8d9e9260e6 Change usermod to addgroup for docker access (#1641)
On Alpine Linux, the correct command to add a user to an existing group is addgroup <username> <groupname> rather than usermod -aG. The usermod command is part of the shadow package which is not installed by default on Alpine.
2026-01-14 16:45:23 -05:00
henrygd
0fc4a6daed update install-agent.sh to prefer glibc binary on linux glibc systems 2026-01-12 19:13:14 -05:00
5 changed files with 74 additions and 14 deletions

View File

@@ -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()

View File

@@ -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" }}
/>
)
}

View File

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

View File

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

View File

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