This commit is contained in:
lda committed 2026-04-14 04:27:04 +07:00
1 parent 2eb9fa4ebe
commit d5fb07ee9f
5 files changed
+376 -41

No files matched your search

+35 -8
View File
@@ -213,11 +213,37 @@ export function DevicesPage({
async function copyValue(label: string, value: string) {
const trimmed = value.trim();
if (!trimmed) return;
async function copyWithFallback(text: string) {
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.setAttribute("readonly", "");
textarea.style.position = "fixed";
textarea.style.top = "0";
textarea.style.left = "0";
textarea.style.opacity = "0";
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
const ok = document.execCommand("copy");
document.body.removeChild(textarea);
if (!ok) throw new Error("fallback-copy-failed");
}
try {
await navigator.clipboard.writeText(trimmed);
if (window.isSecureContext && navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(trimmed);
} else {
await copyWithFallback(trimmed);
}
setCopyStatus(`Copied ${label}`);
} catch {
setCopyStatus(`Copy failed for ${label}`);
try {
await copyWithFallback(trimmed);
setCopyStatus(`Copied ${label}`);
} catch {
setCopyStatus(`Copy failed for ${label}`);
}
}
}
@@ -288,8 +314,8 @@ export function DevicesPage({
}
return (
<section className="grid gap-3 xl:grid-cols-[2fr_1fr]">
<Card>
<section className="grid gap-3 xl:grid-cols-[minmax(0,2fr)_minmax(20rem,1fr)]">
<Card className="min-w-0">
<CardHeader className="flex flex-row items-center justify-between gap-2 space-y-0">
<CardTitle>Devices</CardTitle>
<Button
@@ -303,8 +329,8 @@ export function DevicesPage({
</CardHeader>
<CardContent>
<div className="mt-2 grid gap-2 sm:grid-cols-3">
<label className="grid gap-1 text-sm text-muted-foreground">
<div className="mt-2 grid gap-2 md:grid-cols-3">
<label className="grid min-w-0 gap-1 text-sm text-muted-foreground">
<span>Agent</span>
<Select
value={selectedAgentId}
@@ -312,7 +338,7 @@ export function DevicesPage({
if (value) onSelectAgent(value);
}}
>
<SelectTrigger className="w-full">
<SelectTrigger className="w-full min-w-0">
<SelectValue placeholder="Select agent" />
</SelectTrigger>
<SelectContent>
@@ -326,9 +352,10 @@ export function DevicesPage({
</Select>
</label>
<label className="grid gap-1 text-sm text-muted-foreground sm:col-span-2">
<label className="grid min-w-0 gap-1 text-sm text-muted-foreground md:col-span-2">
<span>Search</span>
<Input
className="w-full min-w-0"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="name, ip, mac, interface"
+39 -25
View File
@@ -1,5 +1,12 @@
import { Button } from "@/components/ui/button";
import { Button, buttonVariants } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { cn } from "@/lib/utils";
import type { DeviceRow, SortDir } from "@/pages/devices/types";
import {
chooseWakeTarget,
@@ -129,33 +136,40 @@ export function DeviceInventoryTable({
wakeBusyId === row.id || !selectedAgentId || bulkWakeBusy
}
size="sm"
className="device-btn"
>
{wakeBusyId === row.id ? "Waking..." : "Wake"}
</Button>
<Button
type="button"
variant="outline"
size="xs"
onClick={() => onCopyValue("name", chooseWakeTarget(row))}
>
Copy name
</Button>
<Button
type="button"
variant="outline"
size="xs"
onClick={() => onCopyValue("ip", row.ips[0] || "")}
>
Copy IP
</Button>
<Button
type="button"
variant="outline"
size="xs"
onClick={() => onCopyValue("mac", row.macs[0] || "")}
>
Copy MAC
</Button>
<DropdownMenu>
<DropdownMenuTrigger
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"device-copy-trigger",
)}
>
Copy
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="min-w-32">
<DropdownMenuItem
onClick={() => onCopyValue("target", chooseWakeTarget(row))}
>
Copy target
</DropdownMenuItem>
<DropdownMenuItem onClick={() => onCopyValue("name", row.name)}>
Copy name
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => onCopyValue("ip", row.ips[0] || "")}
>
Copy IP
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => onCopyValue("mac", row.macs[0] || "")}
>
Copy MAC
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</span>
</div>
))}
+17 -3
View File
@@ -44,15 +44,19 @@
.device-list {
margin-top: 0.8rem;
overflow-x: auto;
overscroll-behavior-x: contain;
}
.device-row {
display: grid;
grid-template-columns:
36px minmax(110px, 1.1fr) minmax(160px, 2fr) minmax(130px, 1.5fr)
minmax(110px, 0.9fr) minmax(120px, 1fr) minmax(220px, auto);
minmax(100px, 0.85fr) minmax(100px, 0.9fr) minmax(170px, max-content);
align-items: center;
gap: 0.55rem;
column-gap: 0.4rem;
row-gap: 0.55rem;
min-width: 980px;
}
.device-header {
@@ -77,11 +81,19 @@
.device-action {
display: flex;
justify-content: flex-end;
flex-wrap: wrap;
flex-wrap: nowrap;
gap: 0.35rem;
justify-self: end;
}
.device-btn {
min-width: 4.25rem;
}
.device-copy-trigger {
min-width: 6.2rem;
}
.device-select {
display: flex;
justify-content: center;
@@ -102,6 +114,7 @@
"mac mac"
"interfaces interfaces";
align-items: start;
min-width: 0;
}
.device-row .device-cell {
@@ -148,6 +161,7 @@
grid-area: action;
justify-self: end;
justify-content: flex-end;
flex-wrap: wrap;
}
.quick-wake {