import { createFileRoute } from "@tanstack/react-router"; import { useState } from "react"; import { CommandSubmitTemplate } from "@/template/command-submit-template"; import { CommandRegistryForm, type CommandRegistryFormData } from "@/components/forms/command-registry-form"; import { useGetCommandList, useGetRoomList, useAddCommand, useUpdateCommand, useDeleteCommand, useSendCommand, } from "@/hooks/queries"; import { toast } from "sonner"; import { Check, X, Edit2, Trash2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import type { ColumnDef } from "@tanstack/react-table"; import type { ShellCommandData } from "@/components/forms/command-form"; import type { CommandRegistry } from "@/types/command-registry"; export const Route = createFileRoute("/_auth/commands/")({ head: () => ({ meta: [{ title: "Gửi lệnh từ xa" }] }), component: CommandPage, loader: async ({ context }) => { // Read active tab from URL search params (client-side) to reflect breadcrumb let activeTab = "list"; try { if (typeof window !== "undefined") { const params = new URLSearchParams(window.location.search); activeTab = params.get("tab") || "list"; } } catch (e) { activeTab = "list"; } context.breadcrumbs = [ { title: "Quản lý lệnh", path: "/_auth/commands/" }, { title: activeTab === "execute" ? "Lệnh thủ công" : "Danh sách", path: `/ _auth/commands/?tab=${activeTab}`, }, ]; }, }); function CommandPage() { const [isDialogOpen, setIsDialogOpen] = useState(false); const [selectedCommand, setSelectedCommand] = useState(null); const [table, setTable] = useState(); // Fetch commands const { data: commands = [], isLoading } = useGetCommandList(); // Fetch rooms const { data: roomData = [] } = useGetRoomList(); const commandList: CommandRegistry[] = Array.isArray(commands) ? commands.map((cmd: any) => ({ ...cmd, qoS: cmd.qoS ?? 0, isRetained: cmd.isRetained ?? false, })) : []; // Mutations const addCommandMutation = useAddCommand(); const updateCommandMutation = useUpdateCommand(); const deleteCommandMutation = useDeleteCommand(); const sendCommandMutation = useSendCommand(); // Columns for command table const columns: ColumnDef[] = [ { accessorKey: "commandName", header: "Tên lệnh", size: 100, cell: ({ getValue }) => (
{getValue() as string}
), }, { accessorKey: "commandType", header: "Loại lệnh", cell: ({ getValue }) => { const type = getValue() as number; const typeMap: Record = { 1: "RESTART", 2: "SHUTDOWN", 3: "TASKKILL", 4: "BLOCK", }; return {typeMap[type] || "UNKNOWN"}; }, }, { accessorKey: "description", header: "Mô tả", size: 120, cell: ({ getValue }) => (
{(getValue() as string) || "-"}
), }, { accessorKey: "commandContent", header: "Nội dung lệnh", size: 130, cell: ({ getValue }) => (
{(getValue() as string).substring(0, 40)}...
), }, { accessorKey: "qoS", header: "QoS", cell: ({ getValue }) => { const qos = getValue() as number | undefined; const qosValue = qos !== undefined ? qos : 0; const colors = { 0: "text-blue-600", 1: "text-amber-600", 2: "text-red-600", }; return ( {qosValue} ); }, }, { accessorKey: "isRetained", header: "Lưu trữ", cell: ({ getValue }) => { const retained = getValue() as boolean; return retained ? (
) : (
Không
); }, }, { id: "select", header: () =>
Chọn để thực thi
, cell: ({ row }) => ( ), enableSorting: false, enableHiding: false, }, { id: "actions", header: () =>
Hành động
, cell: ({ row }) => (
), enableSorting: false, enableHiding: false, }, ]; // Handle form submit const handleFormSubmit = async (data: CommandRegistryFormData) => { try { if (selectedCommand) { // Update await updateCommandMutation.mutateAsync({ commandId: selectedCommand.id, data, }); } else { // Add await addCommandMutation.mutateAsync(data); } setIsDialogOpen(false); setSelectedCommand(null); toast.success(selectedCommand ? "Cập nhật lệnh thành công!" : "Thêm lệnh thành công!"); } catch (error) { console.error("Form submission error:", error); toast.error(selectedCommand ? "Cập nhật lệnh thất bại!" : "Thêm lệnh thất bại!"); } }; // Handle delete const handleDeleteCommand = async (commandId: number) => { if (!confirm("Bạn có chắc muốn xóa lệnh này?")) return; try { await deleteCommandMutation.mutateAsync(commandId); toast.success("Xóa lệnh thành công!"); } catch (error) { console.error("Delete error:", error); toast.error("Xóa lệnh thất bại!"); } }; // Handle execute commands from list const handleExecuteSelected = async (targets: string[]) => { if (!table) { toast.error("Không thể lấy thông tin bảng!"); return; } const selectedRows = table.getSelectedRowModel().rows; if (selectedRows.length === 0) { toast.error("Vui lòng chọn ít nhất một lệnh để thực thi!"); return; } try { for (const target of targets) { for (const row of selectedRows) { // API expects PascalCase directly const apiData = { Command: row.original.commandContent, QoS: row.original.qoS, IsRetained: row.original.isRetained, }; await sendCommandMutation.mutateAsync({ roomName: target, data: apiData as any, }); } } toast.success("Đã gửi yêu cầu thực thi lệnh cho các mục đã chọn!"); if (table) { table.setRowSelection({}); } } catch (error) { toast.error("Có lỗi xảy ra khi thực thi!"); } }; // Handle execute custom command const handleExecuteCustom = async (targets: string[], commandData: ShellCommandData) => { try { for (const target of targets) { // API expects PascalCase directly const apiData = { Command: commandData.command, QoS: commandData.qos, IsRetained: commandData.isRetained, }; await sendCommandMutation.mutateAsync({ roomName: target, data: apiData as any, }); } toast.success("Đã gửi lệnh tùy chỉnh cho các mục đã chọn!"); } catch (error) { toast.error("Gửi lệnh tùy chỉnh thất bại!"); } }; return ( <> { setSelectedCommand(null); setIsDialogOpen(true); }} onTableInit={setTable} formContent={ setIsDialogOpen(false)} initialData={selectedCommand || undefined} title={selectedCommand ? "Sửa Lệnh" : "Thêm Lệnh Mới"} /> } onExecuteSelected={handleExecuteSelected} onExecuteCustom={handleExecuteCustom} isExecuting={sendCommandMutation.isPending} rooms={roomData} scrollable={true} maxHeight="500px" enablePagination={false} defaultPageSize={10} /> ); }