Fix Proxmox dashboard table overflow on wide displays (related to #643)
Removed 2xl: width overrides that caused the table to exceed container width. At ≥1536px viewport, the 2xl breakpoint expanded table columns to ~1528px total width while .pulse-shell container provides only ~1416px usable space, forcing Net In/Net Out columns off-screen and requiring horizontal scroll. Table now caps at xl: breakpoint widths (~1266px) which fit comfortably within the container at all viewport sizes. Net In/Net Out columns are now visible without scrolling on 1080p, 4K, and all wide displays. Changed files: - Dashboard.tsx: Remove 2xl: width classes from all table header columns - GuestRow.tsx: Remove 2xl: width classes from all table cell columns
This commit is contained in:
parent
4891f06e76
commit
68caf5592b
2 changed files with 22 additions and 22 deletions
|
|
@ -912,7 +912,7 @@ export function Dashboard(props: DashboardProps) {
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-gray-50 dark:bg-gray-700/50 text-gray-600 dark:text-gray-300 border-b border-gray-200 dark:border-gray-600">
|
<tr class="bg-gray-50 dark:bg-gray-700/50 text-gray-600 dark:text-gray-300 border-b border-gray-200 dark:border-gray-600">
|
||||||
<th
|
<th
|
||||||
class="pl-4 pr-2 py-1 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[160px] sm:w-[200px] lg:w-[240px] xl:w-[280px] 2xl:w-[340px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500"
|
class="pl-4 pr-2 py-1 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[160px] sm:w-[200px] lg:w-[240px] xl:w-[280px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500"
|
||||||
onClick={() => handleSort('name')}
|
onClick={() => handleSort('name')}
|
||||||
onKeyDown={(e) => e.key === 'Enter' && handleSort('name')}
|
onKeyDown={(e) => e.key === 'Enter' && handleSort('name')}
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
|
@ -922,63 +922,63 @@ export function Dashboard(props: DashboardProps) {
|
||||||
Name {sortKey() === 'name' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
Name {sortKey() === 'name' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class="px-2 py-1 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[48px] sm:w-[56px] lg:w-[60px] xl:w-[64px] 2xl:w-[72px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
class="px-2 py-1 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[48px] sm:w-[56px] lg:w-[60px] xl:w-[64px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||||
onClick={() => handleSort('type')}
|
onClick={() => handleSort('type')}
|
||||||
>
|
>
|
||||||
Type {sortKey() === 'type' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
Type {sortKey() === 'type' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class="px-1.5 py-1 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wide w-[44px] sm:w-[52px] lg:w-[60px] xl:w-[68px] 2xl:w-[80px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
class="px-1.5 py-1 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wide w-[44px] sm:w-[52px] lg:w-[60px] xl:w-[68px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||||
onClick={() => handleSort('vmid')}
|
onClick={() => handleSort('vmid')}
|
||||||
>
|
>
|
||||||
VMID {sortKey() === 'vmid' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
VMID {sortKey() === 'vmid' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class="px-1.5 py-1 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wide w-[60px] sm:w-[70px] lg:w-[80px] xl:w-[92px] 2xl:w-[112px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
class="px-1.5 py-1 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wide w-[60px] sm:w-[70px] lg:w-[80px] xl:w-[92px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||||
onClick={() => handleSort('uptime')}
|
onClick={() => handleSort('uptime')}
|
||||||
>
|
>
|
||||||
Uptime {sortKey() === 'uptime' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
Uptime {sortKey() === 'uptime' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class="px-2 py-1 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[100px] sm:w-[110px] lg:w-[130px] xl:w-[150px] 2xl:w-[180px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
class="px-2 py-1 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[100px] sm:w-[110px] lg:w-[130px] xl:w-[150px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||||
onClick={() => handleSort('cpu')}
|
onClick={() => handleSort('cpu')}
|
||||||
>
|
>
|
||||||
CPU {sortKey() === 'cpu' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
CPU {sortKey() === 'cpu' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class="px-2 py-1 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[100px] sm:w-[110px] lg:w-[130px] xl:w-[150px] 2xl:w-[180px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
class="px-2 py-1 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[100px] sm:w-[110px] lg:w-[130px] xl:w-[150px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||||
onClick={() => handleSort('memory')}
|
onClick={() => handleSort('memory')}
|
||||||
>
|
>
|
||||||
Memory {sortKey() === 'memory' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
Memory {sortKey() === 'memory' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class="px-2 py-1 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[100px] sm:w-[110px] lg:w-[130px] xl:w-[150px] 2xl:w-[180px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
class="px-2 py-1 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[100px] sm:w-[110px] lg:w-[130px] xl:w-[150px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||||
onClick={() => handleSort('disk')}
|
onClick={() => handleSort('disk')}
|
||||||
>
|
>
|
||||||
Disk {sortKey() === 'disk' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
Disk {sortKey() === 'disk' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class="px-2 py-1 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[56px] sm:w-[62px] lg:w-[70px] xl:w-[78px] 2xl:w-[96px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
class="px-2 py-1 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[56px] sm:w-[62px] lg:w-[70px] xl:w-[78px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||||
onClick={() => handleSort('diskRead')}
|
onClick={() => handleSort('diskRead')}
|
||||||
>
|
>
|
||||||
Disk Read{' '}
|
Disk Read{' '}
|
||||||
{sortKey() === 'diskRead' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
{sortKey() === 'diskRead' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class="px-2 py-1 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[56px] sm:w-[62px] lg:w-[70px] xl:w-[78px] 2xl:w-[96px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
class="px-2 py-1 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[56px] sm:w-[62px] lg:w-[70px] xl:w-[78px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||||
onClick={() => handleSort('diskWrite')}
|
onClick={() => handleSort('diskWrite')}
|
||||||
>
|
>
|
||||||
Disk Write{' '}
|
Disk Write{' '}
|
||||||
{sortKey() === 'diskWrite' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
{sortKey() === 'diskWrite' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class="px-2 py-1 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[56px] sm:w-[62px] lg:w-[70px] xl:w-[78px] 2xl:w-[96px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
class="px-2 py-1 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[56px] sm:w-[62px] lg:w-[70px] xl:w-[78px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||||
onClick={() => handleSort('networkIn')}
|
onClick={() => handleSort('networkIn')}
|
||||||
>
|
>
|
||||||
Net In {sortKey() === 'networkIn' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
Net In {sortKey() === 'networkIn' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class="px-2 py-1 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[56px] sm:w-[62px] lg:w-[70px] xl:w-[78px] 2xl:w-[96px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
class="px-2 py-1 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[56px] sm:w-[62px] lg:w-[70px] xl:w-[78px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||||
onClick={() => handleSort('networkOut')}
|
onClick={() => handleSort('networkOut')}
|
||||||
>
|
>
|
||||||
Net Out{' '}
|
Net Out{' '}
|
||||||
|
|
|
||||||
|
|
@ -398,7 +398,7 @@ export function GuestRow(props: GuestRowProps) {
|
||||||
// Get first cell styling
|
// Get first cell styling
|
||||||
const firstCellClass = createMemo(() => {
|
const firstCellClass = createMemo(() => {
|
||||||
const base =
|
const base =
|
||||||
'py-0.5 pr-2 whitespace-nowrap relative w-[160px] sm:w-[200px] lg:w-[240px] xl:w-[280px] 2xl:w-[340px]';
|
'py-0.5 pr-2 whitespace-nowrap relative w-[160px] sm:w-[200px] lg:w-[240px] xl:w-[280px]';
|
||||||
const indent = props.isGroupedView ? GROUPED_FIRST_CELL_INDENT : DEFAULT_FIRST_CELL_INDENT;
|
const indent = props.isGroupedView ? GROUPED_FIRST_CELL_INDENT : DEFAULT_FIRST_CELL_INDENT;
|
||||||
return `${base} ${indent}`;
|
return `${base} ${indent}`;
|
||||||
});
|
});
|
||||||
|
|
@ -534,7 +534,7 @@ export function GuestRow(props: GuestRowProps) {
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
{/* Type */}
|
{/* Type */}
|
||||||
<td class="py-0.5 px-2 whitespace-nowrap w-[48px] sm:w-[56px] lg:w-[60px] xl:w-[64px] 2xl:w-[72px]">
|
<td class="py-0.5 px-2 whitespace-nowrap w-[48px] sm:w-[56px] lg:w-[60px] xl:w-[64px]">
|
||||||
<div class="flex h-[24px] items-center">
|
<div class="flex h-[24px] items-center">
|
||||||
<span
|
<span
|
||||||
class={`inline-block px-1.5 py-0.5 text-xs font-medium rounded ${
|
class={`inline-block px-1.5 py-0.5 text-xs font-medium rounded ${
|
||||||
|
|
@ -549,13 +549,13 @@ export function GuestRow(props: GuestRowProps) {
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
{/* VMID */}
|
{/* VMID */}
|
||||||
<td class="py-0.5 px-1.5 whitespace-nowrap w-[44px] sm:w-[52px] lg:w-[60px] xl:w-[68px] 2xl:w-[80px] text-sm text-gray-600 dark:text-gray-400 align-middle">
|
<td class="py-0.5 px-1.5 whitespace-nowrap w-[44px] sm:w-[52px] lg:w-[60px] xl:w-[68px] text-sm text-gray-600 dark:text-gray-400 align-middle">
|
||||||
{props.guest.vmid}
|
{props.guest.vmid}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
{/* Uptime */}
|
{/* Uptime */}
|
||||||
<td
|
<td
|
||||||
class={`py-0.5 px-1.5 w-[60px] sm:w-[70px] lg:w-[80px] xl:w-[92px] 2xl:w-[112px] text-sm whitespace-nowrap align-middle ${
|
class={`py-0.5 px-1.5 w-[60px] sm:w-[70px] lg:w-[80px] xl:w-[92px] text-sm whitespace-nowrap align-middle ${
|
||||||
props.guest.uptime < 3600 ? 'text-orange-500' : 'text-gray-600 dark:text-gray-400'
|
props.guest.uptime < 3600 ? 'text-orange-500' : 'text-gray-600 dark:text-gray-400'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|
@ -565,7 +565,7 @@ export function GuestRow(props: GuestRowProps) {
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
{/* CPU */}
|
{/* CPU */}
|
||||||
<td class="py-0.5 px-2 w-[100px] sm:w-[110px] lg:w-[130px] xl:w-[150px] 2xl:w-[180px]">
|
<td class="py-0.5 px-2 w-[100px] sm:w-[110px] lg:w-[130px] xl:w-[150px]">
|
||||||
<Show when={isRunning()} fallback={<span class="text-sm text-gray-400">-</span>}>
|
<Show when={isRunning()} fallback={<span class="text-sm text-gray-400">-</span>}>
|
||||||
<MetricBar
|
<MetricBar
|
||||||
value={cpuPercent()}
|
value={cpuPercent()}
|
||||||
|
|
@ -581,7 +581,7 @@ export function GuestRow(props: GuestRowProps) {
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
{/* Memory */}
|
{/* Memory */}
|
||||||
<td class="py-0.5 px-2 w-[100px] sm:w-[110px] lg:w-[130px] xl:w-[150px] 2xl:w-[180px]">
|
<td class="py-0.5 px-2 w-[100px] sm:w-[110px] lg:w-[130px] xl:w-[150px]">
|
||||||
<div title={memoryTooltip() ?? undefined}>
|
<div title={memoryTooltip() ?? undefined}>
|
||||||
<Show when={isRunning()} fallback={<span class="text-sm text-gray-400">-</span>}>
|
<Show when={isRunning()} fallback={<span class="text-sm text-gray-400">-</span>}>
|
||||||
<MetricBar
|
<MetricBar
|
||||||
|
|
@ -595,7 +595,7 @@ export function GuestRow(props: GuestRowProps) {
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
{/* Disk – surface usage even if guest is currently stopped so users can see last reported values */}
|
{/* Disk – surface usage even if guest is currently stopped so users can see last reported values */}
|
||||||
<td class="py-0.5 px-2 w-[100px] sm:w-[110px] lg:w-[130px] xl:w-[150px] 2xl:w-[180px]">
|
<td class="py-0.5 px-2 w-[100px] sm:w-[110px] lg:w-[130px] xl:w-[150px]">
|
||||||
<Show
|
<Show
|
||||||
when={hasDiskUsage()}
|
when={hasDiskUsage()}
|
||||||
fallback={
|
fallback={
|
||||||
|
|
@ -618,24 +618,24 @@ export function GuestRow(props: GuestRowProps) {
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
{/* Disk I/O */}
|
{/* Disk I/O */}
|
||||||
<td class="py-0.5 px-2 w-[56px] sm:w-[62px] lg:w-[70px] xl:w-[78px] 2xl:w-[96px]">
|
<td class="py-0.5 px-2 w-[56px] sm:w-[62px] lg:w-[70px] xl:w-[78px]">
|
||||||
<div class="flex h-[24px] items-center">
|
<div class="flex h-[24px] items-center">
|
||||||
<IOMetric value={props.guest.diskRead} disabled={!isRunning()} />
|
<IOMetric value={props.guest.diskRead} disabled={!isRunning()} />
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="py-0.5 px-2 w-[56px] sm:w-[62px] lg:w-[70px] xl:w-[78px] 2xl:w-[96px]">
|
<td class="py-0.5 px-2 w-[56px] sm:w-[62px] lg:w-[70px] xl:w-[78px]">
|
||||||
<div class="flex h-[24px] items-center">
|
<div class="flex h-[24px] items-center">
|
||||||
<IOMetric value={props.guest.diskWrite} disabled={!isRunning()} />
|
<IOMetric value={props.guest.diskWrite} disabled={!isRunning()} />
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
{/* Network I/O */}
|
{/* Network I/O */}
|
||||||
<td class="py-0.5 px-2 w-[56px] sm:w-[62px] lg:w-[70px] xl:w-[78px] 2xl:w-[96px]">
|
<td class="py-0.5 px-2 w-[56px] sm:w-[62px] lg:w-[70px] xl:w-[78px]">
|
||||||
<div class="flex h-[24px] items-center">
|
<div class="flex h-[24px] items-center">
|
||||||
<IOMetric value={props.guest.networkIn} disabled={!isRunning()} />
|
<IOMetric value={props.guest.networkIn} disabled={!isRunning()} />
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="py-0.5 px-2 w-[56px] sm:w-[62px] lg:w-[70px] xl:w-[78px] 2xl:w-[96px]">
|
<td class="py-0.5 px-2 w-[56px] sm:w-[62px] lg:w-[70px] xl:w-[78px]">
|
||||||
<div class="flex h-[24px] items-center">
|
<div class="flex h-[24px] items-center">
|
||||||
<IOMetric value={props.guest.networkOut} disabled={!isRunning()} />
|
<IOMetric value={props.guest.networkOut} disabled={!isRunning()} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue