device page, fmt;
This commit is contained in:
@@ -120,6 +120,12 @@ function chooseWakeTarget(device: DeviceRow): string {
|
||||
: (device.macs[0] || device.ips[0] || "");
|
||||
}
|
||||
|
||||
function summarize(values: string[]): string {
|
||||
if (!values.length) return "-";
|
||||
if (values.length === 1) return values[0];
|
||||
return `${values[0]} (+${values.length - 1})`;
|
||||
}
|
||||
|
||||
function loadHistory(): WakeEvent[] {
|
||||
try {
|
||||
const raw = window.localStorage.getItem(WAKE_HISTORY_KEY);
|
||||
@@ -337,46 +343,42 @@ export function DevicesPage({ agents, selectedAgentId, onSelectAgent, onAfterWak
|
||||
<p className="muted">Showing {filtered.length} of {rows.length}</p>
|
||||
{error && <pre className="error">{error}</pre>}
|
||||
<div className="list device-list">
|
||||
<div className="row plain device-row device-header" style={{ fontWeight: 600 }}>
|
||||
<div className="row plain device-row device-header">
|
||||
<span
|
||||
className="sortable-col"
|
||||
style={{ cursor: "pointer" }}
|
||||
className="sortable-col device-cell"
|
||||
onClick={() => setSort((s) => ({ key: "name", dir: s.key === "name" && s.dir === "asc" ? "desc" : "asc" }))}
|
||||
>
|
||||
Name {sort.key === "name" ? (sort.dir === "asc" ? "▲" : "▼") : ""}
|
||||
</span>
|
||||
<span
|
||||
className="sortable-col"
|
||||
style={{ cursor: "pointer" }}
|
||||
className="sortable-col device-cell"
|
||||
onClick={() => setSort((s) => ({ key: "ip", dir: s.key === "ip" && s.dir === "asc" ? "desc" : "asc" }))}
|
||||
>
|
||||
IP {sort.key === "ip" ? (sort.dir === "asc" ? "▲" : "▼") : ""}
|
||||
</span>
|
||||
<span
|
||||
className="sortable-col"
|
||||
style={{ cursor: "pointer" }}
|
||||
className="sortable-col device-cell"
|
||||
onClick={() => setSort((s) => ({ key: "mac", dir: s.key === "mac" && s.dir === "asc" ? "desc" : "asc" }))}
|
||||
>
|
||||
MAC {sort.key === "mac" ? (sort.dir === "asc" ? "▲" : "▼") : ""}
|
||||
</span>
|
||||
<span
|
||||
className="sortable-col"
|
||||
style={{ cursor: "pointer" }}
|
||||
className="sortable-col device-cell"
|
||||
onClick={() => setSort((s) => ({ key: "presence", dir: s.key === "presence" && s.dir === "asc" ? "desc" : "asc" }))}
|
||||
>
|
||||
Presence {sort.key === "presence" ? (sort.dir === "asc" ? "▲" : "▼") : ""}
|
||||
</span>
|
||||
<span>Interfaces</span>
|
||||
<span></span>
|
||||
<span className="device-cell">Interfaces</span>
|
||||
<span className="device-cell device-action"></span>
|
||||
</div>
|
||||
{filtered.map((row) => (
|
||||
<div className="row plain device-row" key={row.id}>
|
||||
<span>{row.name}</span>
|
||||
<span className="muted">{row.ips.join(", ") || "-"}</span>
|
||||
<span className="muted">{row.macs.join(", ") || "-"}</span>
|
||||
<span className="pill">{row.presence}</span>
|
||||
<span>{row.interfaces.join(", ") || "-"}</span>
|
||||
<span>
|
||||
<span className="device-cell" data-label="Name" title={row.name}>{row.name}</span>
|
||||
<span className="device-cell muted" data-label="IP" title={row.ips.join(", ") || "-"}>{summarize(row.ips)}</span>
|
||||
<span className="device-cell muted" data-label="MAC" title={row.macs.join(", ") || "-"}>{summarize(row.macs)}</span>
|
||||
<span className="device-cell" data-label="Presence"><span className="pill">{row.presence}</span></span>
|
||||
<span className="device-cell" data-label="Interfaces" title={row.interfaces.join(", ") || "-"}>{summarize(row.interfaces)}</span>
|
||||
<span className="device-cell device-action" data-label="">
|
||||
<button onClick={() => void wakeDevice(row)} disabled={wakeBusyId === row.id || !selectedAgentId}>
|
||||
{wakeBusyId === row.id ? "Waking..." : "Wake"}
|
||||
</button>
|
||||
|
||||
+79
-21
@@ -168,33 +168,91 @@ input, select { width: 100%; padding: 0.45rem 0.55rem; }
|
||||
}
|
||||
|
||||
.device-row {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.device-main {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.device-main strong {
|
||||
display: inline-block;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.device-meta {
|
||||
display: flex;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(110px, 1.1fr) minmax(160px, 2fr) minmax(130px, 1.5fr) minmax(110px, 0.9fr) minmax(120px, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 0.55rem;
|
||||
}
|
||||
|
||||
.device-header {
|
||||
font-weight: 600;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
background: #091429;
|
||||
}
|
||||
|
||||
.device-cell {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sortable-col {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.device-action {
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.device-row {
|
||||
flex-direction: column;
|
||||
.device-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.device-meta {
|
||||
justify-content: flex-start;
|
||||
.device-row {
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-template-areas:
|
||||
"name action"
|
||||
"presence presence"
|
||||
"ip ip"
|
||||
"mac mac"
|
||||
"interfaces interfaces";
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.device-row .device-cell {
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
text-overflow: clip;
|
||||
}
|
||||
|
||||
.device-row .device-cell[data-label]::before {
|
||||
content: attr(data-label);
|
||||
display: block;
|
||||
font-size: 0.72rem;
|
||||
color: var(--muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
margin-bottom: 0.1rem;
|
||||
}
|
||||
|
||||
.device-row .device-cell[data-label="Name"] {
|
||||
grid-area: name;
|
||||
}
|
||||
|
||||
.device-row .device-cell[data-label="Presence"] {
|
||||
grid-area: presence;
|
||||
}
|
||||
|
||||
.device-row .device-cell[data-label="IP"] {
|
||||
grid-area: ip;
|
||||
}
|
||||
|
||||
.device-row .device-cell[data-label="MAC"] {
|
||||
grid-area: mac;
|
||||
}
|
||||
|
||||
.device-row .device-cell[data-label="Interfaces"] {
|
||||
grid-area: interfaces;
|
||||
}
|
||||
|
||||
.device-row .device-action {
|
||||
grid-area: action;
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.quick-wake {
|
||||
|
||||
Reference in New Issue
Block a user