will this be reverted?
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { Button as ButtonPrimitive } from "@base-ui/react/button";
|
import { Button as ButtonPrimitive } from "@base-ui/react/button"
|
||||||
import { cva, type VariantProps } from "class-variance-authority";
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
@@ -37,8 +37,8 @@ const buttonVariants = cva(
|
|||||||
variant: "default",
|
variant: "default",
|
||||||
size: "default",
|
size: "default",
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
function Button({
|
function Button({
|
||||||
className,
|
className,
|
||||||
@@ -52,7 +52,7 @@ function Button({
|
|||||||
className={cn(buttonVariants({ variant, size, className }))}
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Button, buttonVariants };
|
export { Button, buttonVariants }
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { XIcon } from "lucide-react"
|
||||||
|
|
||||||
|
function Dialog({ ...props }: DialogPrimitive.Root.Props) {
|
||||||
|
return <DialogPrimitive.Root data-slot="dialog" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {
|
||||||
|
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {
|
||||||
|
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogClose({ ...props }: DialogPrimitive.Close.Props) {
|
||||||
|
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogOverlay({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: DialogPrimitive.Backdrop.Props) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Backdrop
|
||||||
|
data-slot="dialog-overlay"
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogContent({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
showCloseButton = true,
|
||||||
|
...props
|
||||||
|
}: DialogPrimitive.Popup.Props & {
|
||||||
|
showCloseButton?: boolean
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<DialogPortal>
|
||||||
|
<DialogOverlay />
|
||||||
|
<DialogPrimitive.Popup
|
||||||
|
data-slot="dialog-content"
|
||||||
|
className={cn(
|
||||||
|
"fixed top-1/2 start-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 rtl:translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
{showCloseButton && (
|
||||||
|
<DialogPrimitive.Close
|
||||||
|
data-slot="dialog-close"
|
||||||
|
render={
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="absolute top-2 end-2"
|
||||||
|
size="icon-sm"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<XIcon
|
||||||
|
/>
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</DialogPrimitive.Close>
|
||||||
|
)}
|
||||||
|
</DialogPrimitive.Popup>
|
||||||
|
</DialogPortal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="dialog-header"
|
||||||
|
className={cn("flex flex-col gap-2", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogFooter({
|
||||||
|
className,
|
||||||
|
showCloseButton = false,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"div"> & {
|
||||||
|
showCloseButton?: boolean
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="dialog-footer"
|
||||||
|
className={cn(
|
||||||
|
"-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
{showCloseButton && (
|
||||||
|
<DialogPrimitive.Close render={<Button variant="outline" />}>
|
||||||
|
Close
|
||||||
|
</DialogPrimitive.Close>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Title
|
||||||
|
data-slot="dialog-title"
|
||||||
|
className={cn(
|
||||||
|
"font-heading text-base leading-none font-medium",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogDescription({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: DialogPrimitive.Description.Props) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Description
|
||||||
|
data-slot="dialog-description"
|
||||||
|
className={cn(
|
||||||
|
"text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Dialog,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogOverlay,
|
||||||
|
DialogPortal,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { DeviceInventoryTable } from "@/pages/devices/DeviceInventoryTable";
|
import { DeviceInventoryTable } from "@/pages/devices/DeviceInventoryTable";
|
||||||
|
import { DeviceDetailsDialog } from "@/pages/devices/DeviceDetailsDialog";
|
||||||
import { WakeHistoryCard } from "@/pages/devices/WakeHistoryCard";
|
import { WakeHistoryCard } from "@/pages/devices/WakeHistoryCard";
|
||||||
import type {
|
import type {
|
||||||
DeviceRow,
|
DeviceRow,
|
||||||
@@ -112,6 +113,7 @@ export function DevicesPage({
|
|||||||
const [bulkWakeBusy, setBulkWakeBusy] = useState(false);
|
const [bulkWakeBusy, setBulkWakeBusy] = useState(false);
|
||||||
const [quickWake, setQuickWake] = useState("");
|
const [quickWake, setQuickWake] = useState("");
|
||||||
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
||||||
|
const [detailsDevice, setDetailsDevice] = useState<DeviceRow | null>(null);
|
||||||
const [copyStatus, setCopyStatus] = useState("");
|
const [copyStatus, setCopyStatus] = useState("");
|
||||||
const [recentWakes, setRecentWakes] = useState<WakeEvent[]>([]);
|
const [recentWakes, setRecentWakes] = useState<WakeEvent[]>([]);
|
||||||
function appendWakeEvent(event: WakeEvent) {
|
function appendWakeEvent(event: WakeEvent) {
|
||||||
@@ -288,9 +290,17 @@ export function DevicesPage({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Sort
|
// Sort
|
||||||
const sorter = sorters[sort.key];
|
if (sort.key === "name") {
|
||||||
result = [...result].sort(sorter);
|
result = [...result].sort((a, b) => {
|
||||||
if (sort.dir === "desc") result.reverse();
|
if (a.isUnnamed !== b.isUnnamed) return a.isUnnamed ? 1 : -1;
|
||||||
|
const cmp = a.name.localeCompare(b.name);
|
||||||
|
return sort.dir === "desc" ? -cmp : cmp;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const sorter = sorters[sort.key];
|
||||||
|
result = [...result].sort(sorter);
|
||||||
|
if (sort.dir === "desc") result.reverse();
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}, [rows, query, sort, presenceFilter]);
|
}, [rows, query, sort, presenceFilter]);
|
||||||
|
|
||||||
@@ -342,7 +352,7 @@ export function DevicesPage({
|
|||||||
<span>Agent</span>
|
<span>Agent</span>
|
||||||
<Select
|
<Select
|
||||||
value={selectedAgentId}
|
value={selectedAgentId}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value: string) => {
|
||||||
if (value) onSelectAgent(value);
|
if (value) onSelectAgent(value);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -505,6 +515,14 @@ export function DevicesPage({
|
|||||||
onSortChange={setSort}
|
onSortChange={setSort}
|
||||||
onWakeDevice={(device) => void wakeDevice(device)}
|
onWakeDevice={(device) => void wakeDevice(device)}
|
||||||
onCopyValue={(label, value) => void copyValue(label, value)}
|
onCopyValue={(label, value) => void copyValue(label, value)}
|
||||||
|
onOpenDetails={setDetailsDevice}
|
||||||
|
/>
|
||||||
|
<DeviceDetailsDialog
|
||||||
|
device={detailsDevice}
|
||||||
|
open={Boolean(detailsDevice)}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open) setDetailsDevice(null);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import type { DeviceRow } from "@/pages/devices/types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
device: DeviceRow | null;
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function summarizeAll(values: string[]): string {
|
||||||
|
if (!values.length) return "-";
|
||||||
|
return values.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DeviceDetailsDialog({ device, open, onOpenChange }: Props) {
|
||||||
|
if (!device) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className="max-w-2xl p-0">
|
||||||
|
<div className="grid gap-4 p-4 text-sm">
|
||||||
|
<DialogHeader className="pr-8">
|
||||||
|
<div className="flex items-start justify-between gap-3">
|
||||||
|
<div className="min-w-0">
|
||||||
|
<DialogTitle className="truncate">{device.name}</DialogTitle>
|
||||||
|
<DialogDescription>Device details</DialogDescription>
|
||||||
|
</div>
|
||||||
|
<Badge variant="outline">{device.presence}</Badge>
|
||||||
|
</div>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="mb-1 font-medium">IP addresses</p>
|
||||||
|
<p className="text-muted-foreground">{summarizeAll(device.ips)}</p>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<div>
|
||||||
|
<p className="mb-1 font-medium">MAC addresses</p>
|
||||||
|
<p className="text-muted-foreground">{summarizeAll(device.macs)}</p>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<div>
|
||||||
|
<p className="mb-1 font-medium">Interfaces</p>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
{summarizeAll(device.interfaces)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ type Props = {
|
|||||||
onSortChange: (next: { key: SortKey; dir: SortDir }) => void;
|
onSortChange: (next: { key: SortKey; dir: SortDir }) => void;
|
||||||
onWakeDevice: (device: DeviceRow) => void;
|
onWakeDevice: (device: DeviceRow) => void;
|
||||||
onCopyValue: (label: string, value: string) => void;
|
onCopyValue: (label: string, value: string) => void;
|
||||||
|
onOpenDetails: (device: DeviceRow) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
function sortArrow(active: boolean, dir: SortDir) {
|
function sortArrow(active: boolean, dir: SortDir) {
|
||||||
@@ -48,7 +49,13 @@ export function DeviceInventoryTable({
|
|||||||
onSortChange,
|
onSortChange,
|
||||||
onWakeDevice,
|
onWakeDevice,
|
||||||
onCopyValue,
|
onCopyValue,
|
||||||
|
onOpenDetails,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
function shouldIgnoreRowClick(target: EventTarget | null): boolean {
|
||||||
|
if (!(target instanceof Element)) return false;
|
||||||
|
return Boolean(target.closest("button, input, a, [role='menuitem']"));
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="device-list grid gap-2">
|
<div className="device-list grid gap-2">
|
||||||
<div className="device-row device-header rounded-md border bg-muted/60 px-3 py-2 text-sm">
|
<div className="device-row device-header rounded-md border bg-muted/60 px-3 py-2 text-sm">
|
||||||
@@ -85,14 +92,26 @@ export function DeviceInventoryTable({
|
|||||||
>
|
>
|
||||||
Presence {sortArrow(sort.key === "presence", sort.dir)}
|
Presence {sortArrow(sort.key === "presence", sort.dir)}
|
||||||
</span>
|
</span>
|
||||||
<span className="device-cell">Interfaces</span>
|
|
||||||
<span className="device-cell device-action">Actions</span>
|
<span className="device-cell device-action">Actions</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{rows.map((row) => (
|
{rows.map((row) => (
|
||||||
<div
|
<div
|
||||||
className="device-row rounded-md border bg-card px-3 py-2 text-sm"
|
className="device-row cursor-pointer rounded-md border bg-card px-3 py-2 text-sm transition-colors hover:bg-accent/30"
|
||||||
key={row.id}
|
key={row.id}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={(event) => {
|
||||||
|
if (shouldIgnoreRowClick(event.target)) return;
|
||||||
|
onOpenDetails(row);
|
||||||
|
}}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (shouldIgnoreRowClick(event.target)) return;
|
||||||
|
if (event.key === "Enter" || event.key === " ") {
|
||||||
|
event.preventDefault();
|
||||||
|
onOpenDetails(row);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<span className="device-cell device-select" data-label="Pick">
|
<span className="device-cell device-select" data-label="Pick">
|
||||||
<input
|
<input
|
||||||
@@ -122,13 +141,6 @@ export function DeviceInventoryTable({
|
|||||||
<span className="device-cell" data-label="Presence">
|
<span className="device-cell" data-label="Presence">
|
||||||
<Badge variant="outline">{row.presence}</Badge>
|
<Badge variant="outline">{row.presence}</Badge>
|
||||||
</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="">
|
<span className="device-cell device-action" data-label="">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => onWakeDevice(row)}
|
onClick={() => onWakeDevice(row)}
|
||||||
|
|||||||
@@ -52,11 +52,11 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns:
|
grid-template-columns:
|
||||||
36px minmax(110px, 1.1fr) minmax(160px, 2fr) minmax(130px, 1.5fr)
|
36px minmax(110px, 1.1fr) minmax(160px, 2fr) minmax(130px, 1.5fr)
|
||||||
minmax(100px, 0.85fr) minmax(100px, 0.9fr) minmax(170px, max-content);
|
minmax(100px, 0.85fr) minmax(170px, max-content);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
column-gap: 0.4rem;
|
column-gap: 0.4rem;
|
||||||
row-gap: 0.55rem;
|
row-gap: 0.55rem;
|
||||||
min-width: 980px;
|
min-width: 860px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.device-header {
|
.device-header {
|
||||||
@@ -111,8 +111,7 @@
|
|||||||
"name name"
|
"name name"
|
||||||
"presence presence"
|
"presence presence"
|
||||||
"ip ip"
|
"ip ip"
|
||||||
"mac mac"
|
"mac mac";
|
||||||
"interfaces interfaces";
|
|
||||||
align-items: start;
|
align-items: start;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
@@ -162,10 +161,6 @@
|
|||||||
grid-area: mac;
|
grid-area: mac;
|
||||||
}
|
}
|
||||||
|
|
||||||
.device-row .device-cell[data-label="Interfaces"] {
|
|
||||||
grid-area: interfaces;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-row .device-action {
|
.device-row .device-action {
|
||||||
grid-area: action;
|
grid-area: action;
|
||||||
justify-self: end;
|
justify-self: end;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export type DeviceRow = {
|
export type DeviceRow = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
isUnnamed: boolean;
|
||||||
ips: string[];
|
ips: string[];
|
||||||
macs: string[];
|
macs: string[];
|
||||||
interfaces: string[];
|
interfaces: string[];
|
||||||
|
|||||||
@@ -50,11 +50,13 @@ export function parseInventoryRows(payload: unknown): DeviceRow[] {
|
|||||||
: [];
|
: [];
|
||||||
const presence =
|
const presence =
|
||||||
typeof device.presence === "string" ? device.presence : "unknown";
|
typeof device.presence === "string" ? device.presence : "unknown";
|
||||||
|
const isUnnamed = names.length === 0;
|
||||||
|
|
||||||
const id = macs[0] || ips[0] || names[0] || `row-${idx}`;
|
const id = macs[0] || ips[0] || names[0] || `row-${idx}`;
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
name: names[0] || "(unnamed)",
|
name: names[0] || "(unnamed)",
|
||||||
|
isUnnamed,
|
||||||
ips,
|
ips,
|
||||||
macs,
|
macs,
|
||||||
interfaces,
|
interfaces,
|
||||||
@@ -122,9 +124,7 @@ export function parseWakeSummary(response: unknown): {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function chooseWakeTarget(device: DeviceRow): string {
|
export function chooseWakeTarget(device: DeviceRow): string {
|
||||||
return device.name !== "(unnamed)"
|
return device.isUnnamed ? device.macs[0] || device.ips[0] || "" : device.name;
|
||||||
? device.name
|
|
||||||
: device.macs[0] || device.ips[0] || "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function summarize(values: string[]): string {
|
export function summarize(values: string[]): string {
|
||||||
|
|||||||
Reference in New Issue
Block a user