Split Proxmox node card into system info (left) / storage (right)
Was a single stacked column (uptime/CPU/memory/swap, then the storage table below); now a two-column layout within the card body — system stats on the left, storage table (or the missing-permission hint) on the right — so the card reads more like a dashboard tile at a glance. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+58
-56
@@ -186,68 +186,70 @@ export default function Proxmox({ user }: { user: CurrentUser }) {
|
||||
<div className="card-body">
|
||||
{n.error && <div className="alert alert-danger">{n.error}</div>}
|
||||
<div className="row g-3">
|
||||
<div className="col-sm-6">
|
||||
<div className="text-secondary small">Uptime</div>
|
||||
<div>{formatUptime(n.uptime)}</div>
|
||||
</div>
|
||||
<div className="col-sm-6">
|
||||
<div className="text-secondary small">CPU</div>
|
||||
<div>
|
||||
{n.cpuUsagePercent !== null ? `${n.cpuUsagePercent.toFixed(1)}%` : "—"}
|
||||
{n.cpuCores ? <span className="text-secondary"> ({n.cpuCores} cores)</span> : null}
|
||||
<div className="col-md-6">
|
||||
<div className="mb-3">
|
||||
<div className="text-secondary small">Uptime</div>
|
||||
<div>{formatUptime(n.uptime)}</div>
|
||||
</div>
|
||||
{n.loadAverage && (
|
||||
<div className="text-secondary small">Load: {n.loadAverage.map((l) => l.toFixed(2)).join(", ")}</div>
|
||||
<div className="mb-3">
|
||||
<div className="text-secondary small">CPU</div>
|
||||
<div>
|
||||
{n.cpuUsagePercent !== null ? `${n.cpuUsagePercent.toFixed(1)}%` : "—"}
|
||||
{n.cpuCores ? <span className="text-secondary"> ({n.cpuCores} cores)</span> : null}
|
||||
</div>
|
||||
{n.loadAverage && (
|
||||
<div className="text-secondary small">Load: {n.loadAverage.map((l) => l.toFixed(2)).join(", ")}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={n.swapTotalBytes ? "mb-3" : ""}>
|
||||
<div className="text-secondary small">Memory</div>
|
||||
<div>
|
||||
{formatBytes(n.memUsedBytes)} / {formatBytes(n.memTotalBytes)}
|
||||
</div>
|
||||
{usageBar(n.memUsedBytes, n.memTotalBytes)}
|
||||
</div>
|
||||
{!!n.swapTotalBytes && (
|
||||
<div>
|
||||
<div className="text-secondary small">Swap</div>
|
||||
<div>
|
||||
{formatBytes(n.swapUsedBytes)} / {formatBytes(n.swapTotalBytes)}
|
||||
</div>
|
||||
{usageBar(n.swapUsedBytes, n.swapTotalBytes)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-sm-6">
|
||||
<div className="text-secondary small">Memory</div>
|
||||
<div>
|
||||
{formatBytes(n.memUsedBytes)} / {formatBytes(n.memTotalBytes)}
|
||||
</div>
|
||||
{usageBar(n.memUsedBytes, n.memTotalBytes)}
|
||||
</div>
|
||||
{!!n.swapTotalBytes && (
|
||||
<div className="col-sm-6">
|
||||
<div className="text-secondary small">Swap</div>
|
||||
<div>
|
||||
{formatBytes(n.swapUsedBytes)} / {formatBytes(n.swapTotalBytes)}
|
||||
</div>
|
||||
{usageBar(n.swapUsedBytes, n.swapTotalBytes)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{n.storages.length > 0 ? (
|
||||
<div className="mt-3">
|
||||
<div className="col-md-6">
|
||||
<div className="text-secondary small mb-1">Storage</div>
|
||||
<table className="table table-sm table-vcenter mb-0">
|
||||
<tbody>
|
||||
{n.storages.map((s) => (
|
||||
<tr key={s.id}>
|
||||
<td>
|
||||
{s.id}
|
||||
{!s.active && <span className="badge bg-secondary-lt text-secondary ms-1">Inactive</span>}
|
||||
</td>
|
||||
<td className="text-secondary">{s.type}</td>
|
||||
<td>
|
||||
{formatBytes(s.usedBytes)} / {formatBytes(s.totalBytes)}
|
||||
{usageBar(s.usedBytes, s.totalBytes)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{n.storages.length > 0 ? (
|
||||
<table className="table table-sm table-vcenter mb-0">
|
||||
<tbody>
|
||||
{n.storages.map((s) => (
|
||||
<tr key={s.id}>
|
||||
<td>
|
||||
{s.id}
|
||||
{!s.active && <span className="badge bg-secondary-lt text-secondary ms-1">Inactive</span>}
|
||||
</td>
|
||||
<td className="text-secondary">{s.type}</td>
|
||||
<td>
|
||||
{formatBytes(s.usedBytes)} / {formatBytes(s.totalBytes)}
|
||||
{usageBar(s.usedBytes, s.totalBytes)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
) : (
|
||||
!n.error && (
|
||||
<div className="text-secondary small">
|
||||
No storage visible to this API token. Proxmox filters the storage list
|
||||
per-token — grant it <code>Datastore.Audit</code> (e.g. on{" "}
|
||||
<code>/storage</code>) to see disk usage here.
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
!n.error && (
|
||||
<div className="text-secondary small mt-3">
|
||||
No storage visible to this API token. Proxmox filters the storage list per-token —
|
||||
grant it <code>Datastore.Audit</code> (e.g. on <code>/storage</code>) to see disk
|
||||
usage here.
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user