diff --git a/internal/site/src/components/systems-table/systems-table-columns.tsx b/internal/site/src/components/systems-table/systems-table-columns.tsx index e63b6c15..d47f0f5c 100644 --- a/internal/site/src/components/systems-table/systems-table-columns.tsx +++ b/internal/site/src/components/systems-table/systems-table-columns.tsx @@ -128,28 +128,36 @@ export function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef { const { name, id } = info.row.original const longestName = useStore($longestSystemNameLen) + const linkUrl = getPagePath($router, "system", { id }) + return ( - - -
- - - {/* NOTE: change to 1 ch if switching to monospace font */} - - {name} - - - -
-
- -

{name}

-
-
+ <> + + + { + // 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} + + + + ) }, header: sortableHeader, @@ -446,9 +454,9 @@ function TableCellWithMeter(info: CellContext) { 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 (
@@ -560,7 +568,7 @@ export function IndicatorDot({ system, className }: { system: SystemRecord; clas return ( ) } diff --git a/internal/site/src/lib/systemsManager.ts b/internal/site/src/lib/systemsManager.ts index b96cac43..2ee58602 100644 --- a/internal/site/src/lib/systemsManager.ts +++ b/internal/site/src/lib/systemsManager.ts @@ -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, 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) } diff --git a/internal/site/src/lib/utils.ts b/internal/site/src/lib/utils.ts index 4e9ff276..6e52fb9e 100644 --- a/internal/site/src/lib/utils.ts +++ b/internal/site/src/lib/utils.ts @@ -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 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))