feat: add device select & cmd exec

This commit is contained in:
2026-09-22 14:42:36 +08:00
parent b7bbce7374
commit 166c96caf2
5 changed files with 190 additions and 8 deletions
+105 -4
View File
@@ -1,6 +1,6 @@
import { useEffect, useMemo } from "react";
import { useEffect, useMemo, useRef } from "react";
import { useWAClient } from "./context";
import { useQuery } from "@tanstack/react-query";
import { useMutation, useQuery } from "@tanstack/react-query";
import {
Select,
SelectContent,
@@ -8,12 +8,27 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { createDisclosure } from "@/lib/utils";
import { FileTerminalIcon } from "lucide-react";
import { Textarea } from "@/components/ui/textarea";
import { Badge } from "@/components/ui/badge";
type Props = {
curDeviceId: string;
setDeviceId: (id: string) => void;
};
const commandDlg = createDisclosure<{ deviceId: string }>();
export default function DevicesSection({ curDeviceId, setDeviceId }: Props) {
const { client, isReady } = useWAClient();
const { data: devices } = useQuery({
@@ -35,7 +50,7 @@ export default function DevicesSection({ curDeviceId, setDeviceId }: Props) {
const isOnline = Date.now() - lastAlive <= 60_000;
return {
label: i.name || "Unknown",
label: i.name || i.id || "Unknown",
value: i.id || String(idx),
lastAlive,
isOnline,
@@ -48,7 +63,7 @@ export default function DevicesSection({ curDeviceId, setDeviceId }: Props) {
}, [deviceList, curDeviceId]);
return (
<div className="border-t p-4">
<div className="border-t p-4 flex items-center gap-2">
<Select
items={deviceList}
value={curDeviceId}
@@ -86,6 +101,92 @@ export default function DevicesSection({ curDeviceId, setDeviceId }: Props) {
))}
</SelectContent>
</Select>
<Button
onClick={() => commandDlg.onOpen({ deviceId: curDeviceId })}
variant="outline"
>
<FileTerminalIcon />
</Button>
<CommandModal />
</div>
);
}
const cmdPreset = [
{ title: "Status", command: "status" },
{ title: "Has Focus", command: "return document.hasFocus();" },
{ title: "Reload", command: "window.location.reload(); return true;" },
];
function CommandModal() {
const { data, isOpen } = commandDlg.useStore();
const { client } = useWAClient();
const cmdTextRef = useRef<HTMLTextAreaElement>(null);
const cmd = useMutation({
async mutationFn(cmd: string) {
if (cmd === "status") {
return client.status();
}
return client.exec(cmd, data?.deviceId);
},
});
const onSubmit = () => {
const command = cmdTextRef.current?.value || "";
cmd.mutate(command);
};
return (
<Dialog open={isOpen} onOpenChange={commandDlg.onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>Execute Command</DialogTitle>
</DialogHeader>
<div>
<Textarea ref={cmdTextRef} placeholder="cmd..." />
<div className="flex items-center gap-2 overflow-x-auto mt-2">
{cmdPreset.map((i) => (
<button
key={i.command}
onClick={() => (cmdTextRef.current!.value = i.command)}
>
<Badge className="cursor-pointer">{i.title}</Badge>
</button>
))}
</div>
{cmd.error ? (
<div className="mt-3">
<p className="text-sm font-medium leading-tight">Error:</p>
<pre className="text-sm text-destructive mt-1">
{JSON.stringify(cmd.error, null, 2)}
</pre>
</div>
) : null}
{cmd.data !== undefined ? (
<div className="mt-3">
<p className="text-sm font-medium leading-tight">Result:</p>
<pre className="text-sm mt-1">
{JSON.stringify((cmd as any).data, null, 2)}
</pre>
</div>
) : null}
</div>
<DialogFooter>
<DialogClose>
<Button variant="ghost">Cancel</Button>
</DialogClose>
<Button onClick={onSubmit} disabled={cmd.isPending}>
Execute
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}