diff --git a/.idea/TTMT.ManageWebGUI.iml b/.idea/TTMT.ManageWebGUI.iml deleted file mode 100644 index c956989..0000000 --- a/.idea/TTMT.ManageWebGUI.iml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 0b4bf48..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index 77a3198..0000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1772524885874 - - - - - - \ No newline at end of file diff --git a/src/components/buttons/command-action-buttons.tsx b/src/components/buttons/command-action-buttons.tsx index c865080..569c4b7 100644 --- a/src/components/buttons/command-action-buttons.tsx +++ b/src/components/buttons/command-action-buttons.tsx @@ -14,8 +14,7 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; -import { useGetCommandsByTypes } from "@/hooks/queries/useCommandQueries"; -import { useSendCommand } from "@/hooks/queries"; +import { useGetSensitiveCommands, useExecuteSensitiveCommand } from "@/hooks/queries/useCommandQueries"; import { CommandType } from "@/types/command-registry"; import { Power, @@ -58,6 +57,12 @@ const COMMAND_TYPE_CONFIG = { color: "text-purple-600", bgColor: "bg-purple-50 hover:bg-purple-100", }, + [CommandType.RESET]: { + label : "Reset", + icon: Loader2, + color: "text-green-600", + bgColor: "bg-green-50 hover:bg-green-100", + } }; export function CommandActionButtons({ roomName, selectedDevices = [] }: CommandActionButtonsProps) { @@ -65,55 +70,52 @@ export function CommandActionButtons({ roomName, selectedDevices = [] }: Command open: boolean; command: any; commandType: CommandType; + isSensitive?: boolean; }>({ open: false, command: null, commandType: CommandType.RESTART, }); const [isExecuting, setIsExecuting] = useState(false); + const [sensitivePassword, setSensitivePassword] = useState(""); // Query commands for each type - const { data: restartCommands = [] } = useGetCommandsByTypes(CommandType.RESTART.toString()); - const { data: shutdownCommands = [] } = useGetCommandsByTypes(CommandType.SHUTDOWN.toString()); - const { data: taskkillCommands = [] } = useGetCommandsByTypes(CommandType.TASKKILL.toString()); - const { data: blockCommands = [] } = useGetCommandsByTypes(CommandType.BLOCK.toString()); + const { data: sensitiveCommands = [] } = useGetSensitiveCommands(); - // Send command mutation - const sendCommandMutation = useSendCommand(); + // Send command mutation (sensitive) + const executeSensitiveMutation = useExecuteSensitiveCommand(); - const commandsByType = { - [CommandType.RESTART]: restartCommands, - [CommandType.SHUTDOWN]: shutdownCommands, - [CommandType.TASKKILL]: taskkillCommands, - [CommandType.BLOCK]: blockCommands, - }; + // Build commands mapped by CommandType using the `command` field from sensitive data + const commandsByType: Record = (Object.values(CommandType) as Array) + .filter((v) => typeof v === "number") + .reduce((acc: Record, type) => { + acc[type as number] = (sensitiveCommands || []).filter((c: any) => Number(c.command) === Number(type)); + return acc; + }, {} as Record); const handleCommandClick = (command: any, commandType: CommandType) => { + // When building from sensitiveCommands, all items here are sensitive setConfirmDialog({ open: true, command, commandType, + isSensitive: true, }); }; const handleConfirmExecute = async () => { setIsExecuting(true); try { - // Chuẩn bị data theo format API (PascalCase) - const apiData = { - Command: confirmDialog.command.commandContent, - QoS: confirmDialog.command.qoS, - IsRetained: confirmDialog.command.isRetained, - }; - - // Gửi lệnh đến phòng - await sendCommandMutation.mutateAsync({ + // All rendered commands are sourced from sensitiveCommands — send via sensitive mutation + await executeSensitiveMutation.mutateAsync({ roomName, - data: apiData as any, + command: confirmDialog.command.commandContent, + password: sensitivePassword, }); toast.success(`Đã gửi lệnh: ${confirmDialog.command.commandName}`); - setConfirmDialog({ open: false, command: null, commandType: CommandType.RESTART }); + setConfirmDialog({ open: false, command: null, commandType: CommandType.RESTART, isSensitive: false }); + setSensitivePassword(""); // Reload page để tránh freeze setTimeout(() => { @@ -128,7 +130,8 @@ export function CommandActionButtons({ roomName, selectedDevices = [] }: Command const handleCloseDialog = () => { if (!isExecuting) { - setConfirmDialog({ open: false, command: null, commandType: CommandType.RESTART }); + setConfirmDialog({ open: false, command: null, commandType: CommandType.RESTART, isSensitive: false }); + setSensitivePassword(""); // Reload để tránh freeze setTimeout(() => { window.location.reload(); @@ -148,7 +151,7 @@ export function CommandActionButtons({ roomName, selectedDevices = [] }: Command variant="outline" disabled size="sm" - className="gap-2" + className="gap-2 flex-shrink-0" > {config.label} @@ -163,7 +166,7 @@ export function CommandActionButtons({ roomName, selectedDevices = [] }: Command + + {checking && isLoading && ( +
Đang kiểm tra...
+ )} + + {checking && !isLoading && status && ( +
+
Các file trong thư mục Setup({status.currentFiles?.length ?? 0})
+
+ {(status.currentFiles ?? []).length === 0 ? ( +
Không có file hiện tại
+ ) : ( + (status.currentFiles ?? []).map((f: any) => ( +
+
{f.fileName}
+ {f.lastModified && ( +
+ {new Date(f.lastModified).toLocaleString()} +
+ )} +
+ )) + )} +
+
+ )} + + {checking && !isLoading && !status && ( +
Không có dữ liệu
+ )} + + ); + } + const DeviceInfo = () => (
@@ -69,6 +127,11 @@ export function ComputerCard({
)} +
+
Kiểm tra thư mục
+ +
+
Trạng thái
`${BASE_URL}/Command/types/${types}`, UPDATE_COMMAND: (commandId: number) => `${BASE_URL}/Command/update/${commandId}`, DELETE_COMMAND: (commandId: number) => `${BASE_URL}/Command/delete/${commandId}`, + GET_SENSITIVE_COMMANDS: `${BASE_URL}/Command/sensitive`, + REQUEST_SEND_SENSITIVE_COMMAND: `${BASE_URL}/Command/send-sensitive`, }, SSE_EVENTS: { DEVICE_ONLINE: `${BASE_URL}/Sse/events/onlineDevices`, diff --git a/src/hooks/queries/useCommandQueries.ts b/src/hooks/queries/useCommandQueries.ts index f90e1d2..3bdb20c 100644 --- a/src/hooks/queries/useCommandQueries.ts +++ b/src/hooks/queries/useCommandQueries.ts @@ -82,3 +82,47 @@ export function useDeleteCommand() { }, }); } + + +/** + * Hook để lấy danh sách lệnh nhạy cảm + */ +export function useGetSensitiveCommands(enabled = true) { + return useQuery({ + queryKey: [...COMMAND_QUERY_KEYS.all, "sensitive"], + queryFn: () => commandService.getSensitiveCommands(), + enabled, + staleTime: 5 * 60 * 1000, // 5 minutes + }); +} + + +/** + * Hook để gửi lệnh nhạy cảm + */ +export function useExecuteSensitiveCommand() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ + roomName, + command, + password, + }: { + roomName: string; + command: any; + password: string; + }) => + // API expects a SensitiveCommandRequest with PascalCase keys + commandService.requestSendSensitiveCommand({ + Command: command, + Password: password, + RoomName: roomName, + }), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [...COMMAND_QUERY_KEYS.all, "sensitive"], + }); + }, + }); +} \ No newline at end of file diff --git a/src/hooks/queries/useDeviceCommQueries.ts b/src/hooks/queries/useDeviceCommQueries.ts index d1f8d8c..d4086d7 100644 --- a/src/hooks/queries/useDeviceCommQueries.ts +++ b/src/hooks/queries/useDeviceCommQueries.ts @@ -171,3 +171,28 @@ export function useGetClientFolderStatus(roomName?: string, enabled = true) { staleTime: 30 * 1000, }); } + +/** + * Hook to get folder status for a single device. The hook will fetch the + * folder status list for the device's room and return the matching entry + * for the provided `deviceId`. + */ +export function useGetClientFolderStatusForDevice( + deviceId?: string, + roomName?: string, + enabled = true +) { + return useQuery({ + queryKey: deviceId + ? [...DEVICE_COMM_QUERY_KEYS.all, "folder-status-device", deviceId] + : ["disabled"], + queryFn: async () => { + if (!roomName) return Promise.reject("No room"); + const list = await deviceCommService.getClientFolderStatus(roomName); + if (!Array.isArray(list)) return undefined; + return list.find((s: ClientFolderStatus) => s.deviceId === deviceId); + }, + enabled: enabled && !!deviceId && !!roomName, + staleTime: 30 * 1000, + }); +} diff --git a/src/routes/_auth/agent/index.tsx b/src/routes/_auth/agent/index.tsx index f6442fb..ba9d54e 100644 --- a/src/routes/_auth/agent/index.tsx +++ b/src/routes/_auth/agent/index.tsx @@ -74,7 +74,6 @@ function AgentsPage() { const columns: ColumnDef[] = [ { accessorKey: "version", header: "Phiên bản" }, { accessorKey: "fileName", header: "Tên file" }, - { accessorKey: "folderPath", header: "Đường dẫn" }, { accessorKey: "updatedAt", header: "Thời gian cập nhật", diff --git a/src/routes/_auth/apps/index.tsx b/src/routes/_auth/apps/index.tsx index 2d655da..8ae6fe6 100644 --- a/src/routes/_auth/apps/index.tsx +++ b/src/routes/_auth/apps/index.tsx @@ -55,7 +55,6 @@ function AppsComponent() { const columns: ColumnDef[] = [ { accessorKey: "version", header: "Phiên bản" }, { accessorKey: "fileName", header: "Tên file" }, - { accessorKey: "folderPath", header: "Đường dẫn" }, { accessorKey: "updatedAt", header: () =>
Thời gian cập nhật
, diff --git a/src/routes/_auth/commands/index.tsx b/src/routes/_auth/commands/index.tsx index 391f1b6..09f91a7 100644 --- a/src/routes/_auth/commands/index.tsx +++ b/src/routes/_auth/commands/index.tsx @@ -11,6 +11,7 @@ import { useSendCommand, } from "@/hooks/queries"; import { toast } from "sonner"; +import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip"; import { Check, X, Edit2, Trash2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import type { ColumnDef } from "@tanstack/react-table"; @@ -73,11 +74,19 @@ function CommandPage() { accessorKey: "commandName", header: "Tên lệnh", size: 100, - cell: ({ getValue }) => ( -
- {getValue() as string} -
- ), + cell: ({ getValue, row }) => { + const full = (getValue() as string) || row.original.commandName || ""; + return ( +
+ + + {full} + + {full} + +
+ ); + }, }, { accessorKey: "commandType", @@ -93,18 +102,6 @@ function CommandPage() { return {typeMap[type] || "UNKNOWN"}; }, }, - { - accessorKey: "description", - header: "Mô tả", - size: 120, - cell: ({ getValue }) => ( -
- - {(getValue() as string) || "-"} - -
- ), - }, { accessorKey: "commandContent", header: "Nội dung lệnh", @@ -153,7 +150,7 @@ function CommandPage() { }, { id: "select", - header: () =>
Chọn để thực thi
, + header: () =>
Thực thi
, cell: ({ row }) => ( -
+
{/* Command Action Buttons */} {devices.length > 0 && ( <> diff --git a/src/services/command.service.ts b/src/services/command.service.ts index 325641b..0d63b5e 100644 --- a/src/services/command.service.ts +++ b/src/services/command.service.ts @@ -51,3 +51,22 @@ export async function deleteCommand(commandId: number): Promise { ); return response.data; } + +/** + * Lấy danh sách lệnh nhạy cảm + * @return - Danh sách lệnh nhạy cảm + * */ +export async function getSensitiveCommands(): Promise { + const response = await axios.get(API_ENDPOINTS.COMMAND.GET_SENSITIVE_COMMANDS); + return response.data; +} + +/** + * Gửi yêu cầu thực thi lệnh nhạy cảm + * @param data - Dữ liệu lệnh nhạy cảm + * @return - Kết quả thực thi + * */ +export async function requestSendSensitiveCommand(data: any): Promise { + const response = await axios.post(API_ENDPOINTS.COMMAND.REQUEST_SEND_SENSITIVE_COMMAND, data); + return response.data; +} diff --git a/src/types/app-sidebar.ts b/src/types/app-sidebar.ts index 60e9916..02951ee 100644 --- a/src/types/app-sidebar.ts +++ b/src/types/app-sidebar.ts @@ -18,10 +18,10 @@ export const appSidebarSection = { versions: ["1.0.1", "1.1.0-alpha", "2.0.0-beta1"], navMain: [ { - title: "Thống kê tổng quan", + title: "Tổng quan", items: [ { - title: "Dashboard", + title: "Thống kê", url: "/dashboard", code: AppSidebarSectionCode.DASHBOARD, icon: Home, @@ -30,7 +30,7 @@ export const appSidebarSection = { ], }, { - title: "Quan lý phòng máy", + title: "Quản lý phòng máy", items: [ { title: "Danh sách phòng máy", @@ -42,17 +42,17 @@ export const appSidebarSection = { ], }, { - title: "Agent và phần mềm", + title: "Quản lý agent/thư mục Setup", items: [ { - title: "Danh sách Agent", + title: "Agent", url: "/agent", code: AppSidebarSectionCode.AGENT_MANAGEMENT, icon: AppWindow, permissions: [PermissionEnum.VIEW_AGENT], }, { - title: "Quản lý phần mềm", + title: "Thư mục Setup", url: "/apps", icon: AppWindow, permissions: [PermissionEnum.VIEW_APPS], @@ -60,7 +60,7 @@ export const appSidebarSection = { ], }, { - title: "Lệnh và các ứng dụng bị chặn", + title: "Quản lý lệnh/blacklist", items: [ { @@ -70,7 +70,7 @@ export const appSidebarSection = { permissions: [PermissionEnum.VIEW_COMMAND], }, { - title: "Danh sách ứng dụng/web bị chặn", + title: "Chặn ứng dụng/website", url: "/blacklists", icon: CircleX, permissions: [PermissionEnum.ALLOW_ALL], @@ -78,7 +78,7 @@ export const appSidebarSection = { ] }, { - title: "Phân quyền và người dùng", + title: "Quản lý tài khoản/phân quyền", items: [ { title: "Danh sách roles", diff --git a/src/types/command-registry.ts b/src/types/command-registry.ts index fbfc4d2..aa9445a 100644 --- a/src/types/command-registry.ts +++ b/src/types/command-registry.ts @@ -15,4 +15,5 @@ export enum CommandType { SHUTDOWN = 2, TASKKILL = 3, BLOCK = 4, + RESET = 5, } diff --git a/src/types/sensitive-command.ts b/src/types/sensitive-command.ts new file mode 100644 index 0000000..1d04ed6 --- /dev/null +++ b/src/types/sensitive-command.ts @@ -0,0 +1,9 @@ +import type { CommandType } from "./command-registry"; + +export type SensitiveCommand = { + commandName: string; + commandType: CommandType; + commandContent: string; + qoS: 0 | 1 | 2; + isRetained: boolean; +} \ No newline at end of file