mirror of
https://github.com/henrygd/beszel.git
synced 2026-03-25 06:56:17 +01:00
Compare commits
5 Commits
v0.18.2
...
031abbfcb3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
031abbfcb3 | ||
|
|
b59fcc26e5 | ||
|
|
acaa9381fe | ||
|
|
8d9e9260e6 | ||
|
|
0fc4a6daed |
@@ -435,7 +435,7 @@ func (sm *SmartManager) CollectSmart(deviceInfo *DeviceInfo) error {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// Try with -n standby first if we have existing data
|
// 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...)
|
cmd := exec.CommandContext(ctx, sm.binPath, args...)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
|
|
||||||
|
|||||||
@@ -128,17 +128,32 @@ export function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef<Syste
|
|||||||
cell: (info) => {
|
cell: (info) => {
|
||||||
const { name, id } = info.row.original
|
const { name, id } = info.row.original
|
||||||
const longestName = useStore($longestSystemNameLen)
|
const longestName = useStore($longestSystemNameLen)
|
||||||
|
const linkUrl = getPagePath($router, "system", { id })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span className="flex gap-2 items-center font-medium text-sm text-nowrap md:ps-1">
|
<span className="flex gap-2 items-center font-medium text-sm text-nowrap md:ps-1">
|
||||||
<IndicatorDot system={info.row.original} />
|
<IndicatorDot system={info.row.original} />
|
||||||
{/* NOTE: change to 1 ch if switching to monospace font */}
|
<Link
|
||||||
<span className="truncate" style={{ width: `${longestName / 1.1}ch` }}>
|
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}
|
{name}
|
||||||
</span>
|
</Link>
|
||||||
</span>
|
</span>
|
||||||
<Link
|
<Link
|
||||||
href={getPagePath($router, "system", { id })}
|
href={linkUrl}
|
||||||
className="inset-0 absolute size-full"
|
className="inset-0 absolute size-full"
|
||||||
aria-label={name}
|
aria-label={name}
|
||||||
></Link>
|
></Link>
|
||||||
@@ -439,9 +454,9 @@ function TableCellWithMeter(info: CellContext<SystemRecord, unknown>) {
|
|||||||
const meterClass = cn(
|
const meterClass = cn(
|
||||||
"h-full",
|
"h-full",
|
||||||
(info.row.original.status !== SystemStatus.Up && STATUS_COLORS.paused) ||
|
(info.row.original.status !== SystemStatus.Up && STATUS_COLORS.paused) ||
|
||||||
(threshold === MeterState.Good && STATUS_COLORS.up) ||
|
(threshold === MeterState.Good && STATUS_COLORS.up) ||
|
||||||
(threshold === MeterState.Warn && STATUS_COLORS.pending) ||
|
(threshold === MeterState.Warn && STATUS_COLORS.pending) ||
|
||||||
STATUS_COLORS.down
|
STATUS_COLORS.down
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-2 items-center tabular-nums tracking-tight w-full">
|
<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 (
|
return (
|
||||||
<span
|
<span
|
||||||
className={cn("shrink-0 size-2 rounded-full", className)}
|
className={cn("shrink-0 size-2 rounded-full", className)}
|
||||||
// style={{ marginBottom: "-1px" }}
|
// style={{ marginBottom: "-1px" }}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
$pausedSystems,
|
$pausedSystems,
|
||||||
$upSystems,
|
$upSystems,
|
||||||
} from "@/lib/stores"
|
} from "@/lib/stores"
|
||||||
import { updateFavicon } from "@/lib/utils"
|
import { getVisualStringWidth, updateFavicon } from "@/lib/utils"
|
||||||
import type { SystemRecord } from "@/types"
|
import type { SystemRecord } from "@/types"
|
||||||
import { SystemStatus } from "./enums"
|
import { SystemStatus } from "./enums"
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ function onSystemsChanged(_: Record<string, SystemRecord>, changedSystem: System
|
|||||||
|
|
||||||
// Update longest system name length
|
// Update longest system name length
|
||||||
const longestName = $longestSystemNameLen.get()
|
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) {
|
if (nameLen > longestName) {
|
||||||
$longestSystemNameLen.set(nameLen)
|
$longestSystemNameLen.set(nameLen)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export async function copyToClipboard(content: string) {
|
|||||||
duration,
|
duration,
|
||||||
description: t`Copied to clipboard`,
|
description: t`Copied to clipboard`,
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
$copyContent.set(content)
|
$copyContent.set(content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -316,7 +316,7 @@ export const getHostDisplayValue = (system: SystemRecord): string => system.host
|
|||||||
export const generateToken = () => {
|
export const generateToken = () => {
|
||||||
try {
|
try {
|
||||||
return crypto?.randomUUID()
|
return crypto?.randomUUID()
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
return Array.from({ length: 2 }, () => (performance.now() * Math.random()).toString(16).replace(".", "-")).join("-")
|
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
|
}) 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 */
|
/** Format seconds to hours, minutes, or seconds */
|
||||||
export function secondsToString(seconds: number, unit: "hour" | "minute" | "day"): string {
|
export function secondsToString(seconds: number, unit: "hour" | "minute" | "day"): string {
|
||||||
const count = Math.floor(seconds / (unit === "hour" ? 3600 : unit === "minute" ? 60 : 86400))
|
const count = Math.floor(seconds / (unit === "hour" ? 3600 : unit === "minute" ? 60 : 86400))
|
||||||
|
|||||||
@@ -12,6 +12,24 @@ is_freebsd() {
|
|||||||
[ "$(uname -s)" = "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
|
# If SELinux is enabled, set the context of the binary
|
||||||
set_selinux_context() {
|
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
|
# Add the user to the docker group to allow access to the Docker socket if group docker exists
|
||||||
if getent group docker; then
|
if getent group docker; then
|
||||||
echo "Adding beszel to docker group"
|
echo "Adding beszel to docker group"
|
||||||
usermod -aG docker beszel
|
addgroup beszel docker
|
||||||
fi
|
fi
|
||||||
|
|
||||||
elif is_openwrt; then
|
elif is_openwrt; then
|
||||||
@@ -598,6 +616,9 @@ echo "Downloading and installing the agent..."
|
|||||||
OS=$(uname -s | sed -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/')
|
OS=$(uname -s | sed -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/')
|
||||||
ARCH=$(detect_architecture)
|
ARCH=$(detect_architecture)
|
||||||
FILE_NAME="beszel-agent_${OS}_${ARCH}.tar.gz"
|
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
|
# Determine version to install
|
||||||
if [ "$VERSION" = "latest" ]; then
|
if [ "$VERSION" = "latest" ]; then
|
||||||
|
|||||||
Reference in New Issue
Block a user