Pulse/frontend-modern/src/utils/format.ts
rcourtman 98b0721461 feat: improve responsive table layout with tighter columns
- Add 4 separate I/O columns (D Read, D Write, N In, N Out) to guest table
- Tighten column widths: fixed-width I/O columns, flexible progress bar columns
- Remove sticky columns from NodeSummaryTable (not needed)
- Shorten "Containers" to "CTs" in node summary for consistency
- Always show full VM/LXC labels (no mobile abbreviation)
- Increase name column minWidth to 100px for mobile readability
- Add formatSpeed utility function for I/O display
- Add responsive infrastructure: useBreakpoint hook, useGridTemplate hook
2025-11-25 20:37:28 +00:00

110 lines
3.2 KiB
TypeScript

// Type-safe formatting utilities
export function formatBytes(bytes: number, decimals = 1): string {
if (!bytes || bytes < 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${(bytes / Math.pow(k, i)).toFixed(decimals)} ${sizes[i]}`;
}
export function formatSpeed(bytesPerSecond: number, decimals = 0): string {
if (!bytesPerSecond || bytesPerSecond < 0) return '0 B/s';
return `${formatBytes(bytesPerSecond, decimals)}/s`;
}
export function formatSpeedCompact(bytesPerSecond: number): string {
if (!bytesPerSecond || bytesPerSecond < 0) return '0';
const k = 1024;
const mbps = bytesPerSecond / (k * k);
if (mbps < 1) return '<1';
if (mbps < 10) return mbps.toFixed(1);
return Math.round(mbps).toString();
}
export function formatPercent(value: number): string {
if (!Number.isFinite(value)) return '0%';
const abs = Math.abs(value);
if (abs === 0) return '0%';
if (abs < 0.5) {
return '0%';
}
return `${Math.round(value)}%`;
}
export function formatUptime(seconds: number, condensed = false): string {
if (!seconds || seconds < 0) return '0s';
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
if (days > 0) {
return condensed ? `${days}d` : `${days}d ${hours}h`;
} else if (hours > 0) {
return condensed ? `${hours}h` : `${hours}h ${minutes}m`;
} else {
return `${minutes}m`;
}
}
export function formatAbsoluteTime(timestamp: number): string {
if (!timestamp) return '';
const date = new Date(timestamp);
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
];
const month = months[date.getMonth()];
const day = date.getDate();
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
return `${day} ${month} ${hours}:${minutes}`;
}
export function formatRelativeTime(timestamp: number): string {
if (!timestamp) return '';
const now = Date.now();
const diffMs = now - timestamp;
// Handle invalid or future timestamps
if (isNaN(diffMs) || !isFinite(diffMs)) return '';
if (diffMs < 0) return '0s ago'; // Future timestamp, treat as current
const diffSeconds = Math.floor(diffMs / 1000);
const diffMinutes = Math.floor(diffSeconds / 60);
const diffHours = Math.floor(diffMinutes / 60);
const diffDays = Math.floor(diffHours / 24);
const diffMonths = Math.floor(diffDays / 30);
const diffYears = Math.floor(diffDays / 365);
if (diffSeconds < 60) {
return `${diffSeconds}s ago`;
} else if (diffMinutes < 60) {
return diffMinutes === 1 ? '1 min ago' : `${diffMinutes} mins ago`;
} else if (diffHours < 24) {
return diffHours === 1 ? '1 hour ago' : `${diffHours} hours ago`;
} else if (diffDays < 30) {
return diffDays === 1 ? '1 day ago' : `${diffDays} days ago`;
} else if (diffMonths < 12) {
return diffMonths === 1 ? '1 month ago' : `${diffMonths} months ago`;
} else {
return diffYears === 1 ? '1 year ago' : `${diffYears} years ago`;
}
}