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:
1 parent
846953194c
commit
7b39b7be4d
1 file changed
+58
-56
+58
-56
@@ -186,68 +186,70 @@ export default function Proxmox({ user }: { user: CurrentUser }) {
|
|||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
{n.error && <div className="alert alert-danger">{n.error}</div>}
|
{n.error && <div className="alert alert-danger">{n.error}</div>}
|
||||||
<div className="row g-3">
|
<div className="row g-3">
|
||||||
<div className="col-sm-6">
|
<div className="col-md-6">
|
||||||
<div className="text-secondary small">Uptime</div>
|
<div className="mb-3">
|
||||||
<div>{formatUptime(n.uptime)}</div>
|
<div className="text-secondary small">Uptime</div>
|
||||||
</div>
|
<div>{formatUptime(n.uptime)}</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>
|
</div>
|
||||||
{n.loadAverage && (
|
<div className="mb-3">
|
||||||
<div className="text-secondary small">Load: {n.loadAverage.map((l) => l.toFixed(2)).join(", ")}</div>
|
<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>
|
||||||
<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="col-md-6">
|
||||||
<div className="mt-3">
|
|
||||||
<div className="text-secondary small mb-1">Storage</div>
|
<div className="text-secondary small mb-1">Storage</div>
|
||||||
<table className="table table-sm table-vcenter mb-0">
|
{n.storages.length > 0 ? (
|
||||||
<tbody>
|
<table className="table table-sm table-vcenter mb-0">
|
||||||
{n.storages.map((s) => (
|
<tbody>
|
||||||
<tr key={s.id}>
|
{n.storages.map((s) => (
|
||||||
<td>
|
<tr key={s.id}>
|
||||||
{s.id}
|
<td>
|
||||||
{!s.active && <span className="badge bg-secondary-lt text-secondary ms-1">Inactive</span>}
|
{s.id}
|
||||||
</td>
|
{!s.active && <span className="badge bg-secondary-lt text-secondary ms-1">Inactive</span>}
|
||||||
<td className="text-secondary">{s.type}</td>
|
</td>
|
||||||
<td>
|
<td className="text-secondary">{s.type}</td>
|
||||||
{formatBytes(s.usedBytes)} / {formatBytes(s.totalBytes)}
|
<td>
|
||||||
{usageBar(s.usedBytes, s.totalBytes)}
|
{formatBytes(s.usedBytes)} / {formatBytes(s.totalBytes)}
|
||||||
</td>
|
{usageBar(s.usedBytes, s.totalBytes)}
|
||||||
</tr>
|
</td>
|
||||||
))}
|
</tr>
|
||||||
</tbody>
|
))}
|
||||||
</table>
|
</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>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user