diff --git a/frontend-modern/src/components/Backups/UnifiedBackups.tsx b/frontend-modern/src/components/Backups/UnifiedBackups.tsx
index c51c5ac86..1d702da83 100644
--- a/frontend-modern/src/components/Backups/UnifiedBackups.tsx
+++ b/frontend-modern/src/components/Backups/UnifiedBackups.tsx
@@ -1,7 +1,7 @@
import { Component, createSignal, Show, For, createMemo, createEffect } from 'solid-js';
import { useNavigate } from '@solidjs/router';
import { useWebSocket } from '@/App';
-import { formatBytes, formatAbsoluteTime, formatRelativeTime, formatUptime } from '@/utils/format';
+import { formatBytes, formatAbsoluteTime, formatRelativeTime, formatUptime, formatPercent } from '@/utils/format';
import { createLocalStorageBooleanSignal, STORAGE_KEYS } from '@/utils/localStorage';
import { parseFilterStack, evaluateFilterStack } from '@/utils/searchQuery';
import { UnifiedNodeSelector } from '@/components/shared/UnifiedNodeSelector';
@@ -1302,15 +1302,15 @@ const UnifiedBackups: Component = () => {
-
+
|
{
|
|
diff --git a/frontend-modern/src/components/Dashboard/GuestRow.tsx b/frontend-modern/src/components/Dashboard/GuestRow.tsx
index 401886051..24515d4e7 100644
--- a/frontend-modern/src/components/Dashboard/GuestRow.tsx
+++ b/frontend-modern/src/components/Dashboard/GuestRow.tsx
@@ -1,6 +1,6 @@
import { createMemo, createSignal, createEffect, on, Show, For } from 'solid-js';
import type { VM, Container } from '@/types/api';
-import { formatBytes, formatUptime } from '@/utils/format';
+import { formatBytes, formatPercent, formatUptime } from '@/utils/format';
import { MetricBar } from './MetricBar';
import { IOMetric } from './IOMetric';
import { TagBadges } from './TagBadges';
@@ -113,7 +113,7 @@ export function GuestRow(props: GuestRowProps) {
if (!props.guest.memory) return undefined;
const used = props.guest.memory.used ?? 0;
const total = props.guest.memory.total ?? 0;
- return `${formatBytes(used)}/${formatBytes(total)}`;
+ return `${formatBytes(used, 0)}/${formatBytes(total, 0)}`;
});
const memoryExtraLines = createMemo(() => {
if (!props.guest.memory) return undefined;
@@ -124,11 +124,11 @@ export function GuestRow(props: GuestRowProps) {
props.guest.memory.balloon > 0 &&
props.guest.memory.balloon !== total
) {
- lines.push(`Balloon: ${formatBytes(props.guest.memory.balloon)}`);
+ lines.push(`Balloon: ${formatBytes(props.guest.memory.balloon, 0)}`);
}
if (props.guest.memory.swapTotal && props.guest.memory.swapTotal > 0) {
const swapUsed = props.guest.memory.swapUsed ?? 0;
- lines.push(`Swap: ${formatBytes(swapUsed)} / ${formatBytes(props.guest.memory.swapTotal)}`);
+ lines.push(`Swap: ${formatBytes(swapUsed, 0)} / ${formatBytes(props.guest.memory.swapTotal, 0)}`);
}
return lines.length > 0 ? lines : undefined;
});
@@ -584,10 +584,10 @@ export function GuestRow(props: GuestRowProps) {
-}>
-}>
@@ -621,10 +621,10 @@ export function GuestRow(props: GuestRowProps) {
>
= (props) => {
? clampPercent((memoryUsed / memoryTotal) * 100)
: 0;
const memoryLabel =
- memoryTotal > 0 ? `${formatBytes(memoryUsed)} / ${formatBytes(memoryTotal)}` : undefined;
+ memoryTotal > 0 ? `${formatBytes(memoryUsed, 0)} / ${formatBytes(memoryTotal, 0)}` : undefined;
let diskPercent = 0;
let diskLabel: string | undefined;
@@ -110,7 +110,7 @@ export const DockerHosts: Component = (props) => {
);
if (totals.total > 0) {
diskPercent = clampPercent((totals.used / totals.total) * 100);
- diskLabel = `${formatBytes(totals.used)} / ${formatBytes(totals.total)}`;
+ diskLabel = `${formatBytes(totals.used, 0)} / ${formatBytes(totals.total, 0)}`;
}
}
diff --git a/frontend-modern/src/components/Docker/DockerUnifiedTable.tsx b/frontend-modern/src/components/Docker/DockerUnifiedTable.tsx
index e6bb22b6d..61377cfc5 100644
--- a/frontend-modern/src/components/Docker/DockerUnifiedTable.tsx
+++ b/frontend-modern/src/components/Docker/DockerUnifiedTable.tsx
@@ -533,9 +533,9 @@ const DockerContainerRow: Component<{
const memPercent = () => Math.max(0, Math.min(100, container.memoryPercent ?? 0));
const memUsageLabel = () => {
if (!container.memoryUsageBytes) return undefined;
- const used = formatBytes(container.memoryUsageBytes);
+ const used = formatBytes(container.memoryUsageBytes, 0);
const limit = container.memoryLimitBytes
- ? formatBytes(container.memoryLimitBytes)
+ ? formatBytes(container.memoryLimitBytes, 0)
: undefined;
return limit ? `${used} / ${limit}` : used;
};
@@ -695,7 +695,7 @@ const DockerContainerRow: Component<{
{statusLabel()}
|
-
+ |
0}
fallback={—}
@@ -703,7 +703,7 @@ const DockerContainerRow: Component<{
|
-
+ |
0}
fallback={—}
@@ -1132,8 +1132,8 @@ const DockerServiceRow: Component<{
{badge.label}
|
- — |
- — |
+ — |
+ — |
{(service.runningTasks ?? 0)}/{service.desiredTasks ?? 0}
@@ -1451,8 +1451,8 @@ const DockerUnifiedTable: Component = (props) => {
}
>
-
-
+
+
@@ -1467,10 +1467,10 @@ const DockerUnifiedTable: Component = (props) => {
|
Status
|
-
+ |
CPU
|
-
+ |
Memory
|
diff --git a/frontend-modern/src/components/Hosts/HostsOverview.tsx b/frontend-modern/src/components/Hosts/HostsOverview.tsx
index db22c8816..e6e1dbae4 100644
--- a/frontend-modern/src/components/Hosts/HostsOverview.tsx
+++ b/frontend-modern/src/components/Hosts/HostsOverview.tsx
@@ -2,7 +2,7 @@ import type { Component } from 'solid-js';
import { For, Show, createMemo, createSignal, createEffect, on, onMount, onCleanup } from 'solid-js';
import { useNavigate } from '@solidjs/router';
import type { Host } from '@/types/api';
-import { formatBytes, formatRelativeTime, formatUptime } from '@/utils/format';
+import { formatBytes, formatPercent, formatRelativeTime, formatUptime } from '@/utils/format';
import { Card } from '@/components/shared/Card';
import { ScrollableTable } from '@/components/shared/ScrollableTable';
import { EmptyState } from '@/components/shared/EmptyState';
@@ -211,8 +211,8 @@ export const HostsOverview: Component = (props) => {
{(host) => {
const cpuPercent = () => host.cpuUsage ?? 0;
const memPercent = () => host.memory?.usage ?? 0;
- const memUsed = () => formatBytes(host.memory?.used ?? 0);
- const memTotal = () => formatBytes(host.memory?.total ?? 0);
+ const memUsed = () => formatBytes(host.memory?.used ?? 0, 0);
+ const memTotal = () => formatBytes(host.memory?.total ?? 0, 0);
// Drawer state
const [drawerOpen, setDrawerOpen] = createSignal(drawerState.get(host.id) ?? false);
@@ -301,7 +301,7 @@ export const HostsOverview: Component = (props) => {
fallback={—}
>
@@ -413,14 +413,14 @@ export const HostsOverview: Component = (props) => {
{disk.mountpoint || disk.device}
- {formatBytes(disk.used ?? 0)} / {formatBytes(disk.total ?? 0)}
+ {formatBytes(disk.used ?? 0, 0)} / {formatBytes(disk.total ?? 0, 0)}
0}>
diff --git a/frontend-modern/src/components/Storage/Storage.tsx b/frontend-modern/src/components/Storage/Storage.tsx
index 392e6e908..eb6adb972 100644
--- a/frontend-modern/src/components/Storage/Storage.tsx
+++ b/frontend-modern/src/components/Storage/Storage.tsx
@@ -2,7 +2,7 @@ import { Component, For, Show, createSignal, createMemo, createEffect } from 'so
import { useNavigate } from '@solidjs/router';
import { useWebSocket } from '@/App';
import { getAlertStyles } from '@/utils/alerts';
-import { formatBytes } from '@/utils/format';
+import { formatBytes, formatPercent } from '@/utils/format';
import type { Storage as StorageType, CephCluster } from '@/types/api';
import { ComponentErrorBoundary } from '@/components/ErrorBoundary';
import { UnifiedNodeSelector } from '@/components/shared/UnifiedNodeSelector';
@@ -858,7 +858,7 @@ const Storage: Component = () => {
const used = Math.max(0, cluster.usedBytes || 0);
const percent = total > 0 ? (used / total) * 100 : 0;
parts.push(
- `${formatBytes(used)} / ${formatBytes(total)} (${percent.toFixed(1)}%)`,
+ `${formatBytes(used, 0)} / ${formatBytes(total, 0)} (${formatPercent(percent)})`,
);
if (
Number.isFinite(cluster.numOsds) &&
@@ -883,7 +883,7 @@ const Storage: Component = () => {
if (totals.total > 0) {
const percent = (totals.used / totals.total) * 100;
parts.push(
- `${formatBytes(totals.used)} / ${formatBytes(totals.total)} (${percent.toFixed(1)}%)`,
+ `${formatBytes(totals.used, 0)} / ${formatBytes(totals.total, 0)} (${formatPercent(percent)})`,
);
}
}
@@ -900,7 +900,7 @@ const Storage: Component = () => {
if (!pool) return '';
const total = Math.max(1, pool.storedBytes + pool.availableBytes);
const percent = total > 0 ? (pool.storedBytes / total) * 100 : 0;
- return `${pool.name}: ${percent.toFixed(1)}%`;
+ return `${pool.name}: ${formatPercent(percent)}`;
})
.filter(Boolean)
.join(', ');
@@ -917,7 +917,7 @@ const Storage: Component = () => {
const total = Math.max(1, item.total || 0);
const used = Math.max(0, item.used || 0);
const percent = total > 0 ? (used / total) * 100 : 0;
- return `${item.name}: ${percent.toFixed(1)}%`;
+ return `${item.name}: ${formatPercent(percent)}`;
})
.filter(Boolean)
.join(', ');
@@ -1124,18 +1124,18 @@ const Storage: Component = () => {
/>
- {usagePercent.toFixed(0)}% (
- {formatBytes(storage.used || 0)}/
- {formatBytes(storage.total || 0)})
+ {formatPercent(usagePercent)} (
+ {formatBytes(storage.used || 0, 0)}/
+ {formatBytes(storage.total || 0, 0)})
- {formatBytes(storage.free || 0)}
+ {formatBytes(storage.free || 0, 0)}
|
- {formatBytes(storage.total || 0)}
+ {formatBytes(storage.total || 0, 0)}
|
|
| |
diff --git a/frontend-modern/src/components/shared/NodeSummaryTable.tsx b/frontend-modern/src/components/shared/NodeSummaryTable.tsx
index 08e206e4b..0081c8127 100644
--- a/frontend-modern/src/components/shared/NodeSummaryTable.tsx
+++ b/frontend-modern/src/components/shared/NodeSummaryTable.tsx
@@ -195,12 +195,12 @@ export const NodeSummaryTable: Component = (props) => {
if (item.type === 'pve') {
const node = item.data as Node;
if (!node.disk) return undefined;
- return `${formatBytes(node.disk.used)}/${formatBytes(node.disk.total)}`;
+ return `${formatBytes(node.disk.used, 0)}/${formatBytes(node.disk.total, 0)}`;
}
const pbs = item.data as PBSInstance;
if (!pbs.datastores || pbs.datastores.length === 0) return undefined;
const totals = getPbsTotals(pbs);
- return `${formatBytes(totals.used)}/${formatBytes(totals.total)}`;
+ return `${formatBytes(totals.used, 0)}/${formatBytes(totals.total, 0)}`;
};
const getTemperatureValue = (item: SortableItem) => {
@@ -583,9 +583,9 @@ export const NodeSummaryTable: Component = (props) => {
label={formatPercent(memoryPercentValue ?? 0)}
sublabel={
isPVE && node!.memory
- ? `${formatBytes(node!.memory.used)}/${formatBytes(node!.memory.total)}`
+ ? `${formatBytes(node!.memory.used, 0)}/${formatBytes(node!.memory.total, 0)}`
: isPBS && pbs!.memoryTotal
- ? `${formatBytes(pbs!.memoryUsed)}/${formatBytes(pbs!.memoryTotal)}`
+ ? `${formatBytes(pbs!.memoryUsed, 0)}/${formatBytes(pbs!.memoryTotal, 0)}`
: undefined
}
type="memory"
diff --git a/frontend-modern/src/utils/format.ts b/frontend-modern/src/utils/format.ts
index 08196c692..d2feb5235 100644
--- a/frontend-modern/src/utils/format.ts
+++ b/frontend-modern/src/utils/format.ts
@@ -17,19 +17,12 @@ export function formatSpeed(bytesPerSecond: number, decimals = 0): string {
export function formatPercent(value: number): string {
if (!Number.isFinite(value)) return '0%';
-
const abs = Math.abs(value);
-
- if (abs >= 10) {
- return `${Math.round(value)}%`;
+ if (abs === 0) return '0%';
+ if (abs < 0.5) {
+ return '0%';
}
-
- if (abs >= 1) {
- return `${value.toFixed(1)}%`;
- }
-
- // Preserve tiny signals without overly long labels
- return `${value.toFixed(2)}%`;
+ return `${Math.round(value)}%`;
}
export function formatUptime(seconds: number): string {
|