diff --git a/package-lock.json b/package-lock.json index 192f319..e8434a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,10 +7,12 @@ "name": ".", "dependencies": { "@radix-ui/react-avatar": "^1.1.10", + "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-progress": "^1.1.7", "@radix-ui/react-radio-group": "^1.3.8", + "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tooltip": "^1.2.7", @@ -1319,6 +1321,12 @@ "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==" }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, "node_modules/@radix-ui/primitive": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", @@ -1375,6 +1383,66 @@ } } }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", @@ -1816,6 +1884,67 @@ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", "license": "MIT" }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", + "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-separator": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", diff --git a/package.json b/package.json index 8e15669..1bbdb37 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,12 @@ }, "dependencies": { "@radix-ui/react-avatar": "^1.1.10", + "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-progress": "^1.1.7", "@radix-ui/react-radio-group": "^1.3.8", + "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tooltip": "^1.2.7", diff --git a/src/components/preset-command.tsx b/src/components/preset-command.tsx new file mode 100644 index 0000000..73d42e2 --- /dev/null +++ b/src/components/preset-command.tsx @@ -0,0 +1,151 @@ +import { Button } from "@/components/ui/button" +import { ScrollArea } from "@/components/ui/scroll-area" +import { Checkbox } from "@/components/ui/checkbox" +import { Play, PlayCircle } from "lucide-react" +import { useState } from "react" + +interface PresetCommand { + id: string + label: string + command: string + description?: string +} + +interface PresetCommandsProps { + onSelectCommand: (command: string) => void + onExecuteMultiple?: (commands: string[]) => void + disabled?: boolean +} + +// Danh sách các command có sẵn +const PRESET_COMMANDS: PresetCommand[] = [ + { + id: "check-disk", + label: "Kiểm tra dung lượng ổ đĩa", + command: "df -h", + description: "Hiển thị thông tin dung lượng các ổ đĩa", + }, + { + id: "check-memory", + label: "Kiểm tra RAM", + command: "free -h", + description: "Hiển thị thông tin bộ nhớ RAM", + }, + { + id: "check-cpu", + label: "Kiểm tra CPU", + command: "top -bn1 | head -20", + description: "Hiển thị thông tin CPU và tiến trình", + }, + { + id: "list-processes", + label: "Danh sách tiến trình", + command: "ps aux", + description: "Liệt kê tất cả tiến trình đang chạy", + }, + { + id: "network-info", + label: "Thông tin mạng", + command: "ifconfig", + description: "Hiển thị cấu hình mạng", + }, + { + id: "system-info", + label: "Thông tin hệ thống", + command: "uname -a", + description: "Hiển thị thông tin hệ điều hành", + }, + { + id: "uptime", + label: "Thời gian hoạt động", + command: "uptime", + description: "Hiển thị thời gian hệ thống đã chạy", + }, + { + id: "reboot", + label: "Khởi động lại", + command: "reboot", + description: "Khởi động lại thiết bị", + }, +] + +export function PresetCommands({ onSelectCommand, onExecuteMultiple, disabled }: PresetCommandsProps) { + const [selectedCommands, setSelectedCommands] = useState>(new Set()) + + const handleToggleCommand = (commandId: string) => { + setSelectedCommands((prev) => { + const newSet = new Set(prev) + if (newSet.has(commandId)) { + newSet.delete(commandId) + } else { + newSet.add(commandId) + } + return newSet + }) + } + + const handleExecuteSelected = () => { + const commands = PRESET_COMMANDS.filter((cmd) => selectedCommands.has(cmd.id)).map((cmd) => cmd.command) + if (commands.length > 0 && onExecuteMultiple) { + onExecuteMultiple(commands) + setSelectedCommands(new Set()) // Clear selection after execution + } + } + + const handleSelectAll = () => { + if (selectedCommands.size === PRESET_COMMANDS.length) { + setSelectedCommands(new Set()) + } else { + setSelectedCommands(new Set(PRESET_COMMANDS.map((cmd) => cmd.id))) + } + } + + return ( +
+
+ + {selectedCommands.size > 0 && ( + + )} +
+ + +
+ {PRESET_COMMANDS.map((preset) => ( +
+ handleToggleCommand(preset.id)} + disabled={disabled} + className="mt-1" + /> +
+
{preset.label}
+ {preset.description &&
{preset.description}
} + {preset.command} +
+ +
+ ))} +
+
+
+ ) +} \ No newline at end of file diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..defeb01 --- /dev/null +++ b/src/components/ui/checkbox.tsx @@ -0,0 +1,30 @@ +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { CheckIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Checkbox({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + + ) +} + +export { Checkbox } diff --git a/src/components/ui/scroll-area.tsx b/src/components/ui/scroll-area.tsx new file mode 100644 index 0000000..9376f59 --- /dev/null +++ b/src/components/ui/scroll-area.tsx @@ -0,0 +1,56 @@ +import * as React from "react" +import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" + +import { cn } from "@/lib/utils" + +function ScrollArea({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + {children} + + + + + ) +} + +function ScrollBar({ + className, + orientation = "vertical", + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { ScrollArea, ScrollBar } diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index 67e2ce9..bed6aa4 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -14,6 +14,7 @@ import { Route as AuthRouteImport } from './routes/_auth' import { Route as IndexRouteImport } from './routes/index' import { Route as AuthenticatedRoomIndexRouteImport } from './routes/_authenticated/room/index' import { Route as AuthenticatedCommandIndexRouteImport } from './routes/_authenticated/command/index' +import { Route as AuthenticatedBlacklistIndexRouteImport } from './routes/_authenticated/blacklist/index' import { Route as AuthenticatedAppsIndexRouteImport } from './routes/_authenticated/apps/index' import { Route as AuthenticatedAgentIndexRouteImport } from './routes/_authenticated/agent/index' import { Route as AuthLoginIndexRouteImport } from './routes/_auth/login/index' @@ -43,6 +44,12 @@ const AuthenticatedCommandIndexRoute = path: '/command/', getParentRoute: () => AuthenticatedRoute, } as any) +const AuthenticatedBlacklistIndexRoute = + AuthenticatedBlacklistIndexRouteImport.update({ + id: '/blacklist/', + path: '/blacklist/', + getParentRoute: () => AuthenticatedRoute, + } as any) const AuthenticatedAppsIndexRoute = AuthenticatedAppsIndexRouteImport.update({ id: '/apps/', path: '/apps/', @@ -70,6 +77,7 @@ export interface FileRoutesByFullPath { '/login': typeof AuthLoginIndexRoute '/agent': typeof AuthenticatedAgentIndexRoute '/apps': typeof AuthenticatedAppsIndexRoute + '/blacklist': typeof AuthenticatedBlacklistIndexRoute '/command': typeof AuthenticatedCommandIndexRoute '/room': typeof AuthenticatedRoomIndexRoute '/room/$roomName': typeof AuthenticatedRoomRoomNameIndexRoute @@ -79,6 +87,7 @@ export interface FileRoutesByTo { '/login': typeof AuthLoginIndexRoute '/agent': typeof AuthenticatedAgentIndexRoute '/apps': typeof AuthenticatedAppsIndexRoute + '/blacklist': typeof AuthenticatedBlacklistIndexRoute '/command': typeof AuthenticatedCommandIndexRoute '/room': typeof AuthenticatedRoomIndexRoute '/room/$roomName': typeof AuthenticatedRoomRoomNameIndexRoute @@ -91,6 +100,7 @@ export interface FileRoutesById { '/_auth/login/': typeof AuthLoginIndexRoute '/_authenticated/agent/': typeof AuthenticatedAgentIndexRoute '/_authenticated/apps/': typeof AuthenticatedAppsIndexRoute + '/_authenticated/blacklist/': typeof AuthenticatedBlacklistIndexRoute '/_authenticated/command/': typeof AuthenticatedCommandIndexRoute '/_authenticated/room/': typeof AuthenticatedRoomIndexRoute '/_authenticated/room/$roomName/': typeof AuthenticatedRoomRoomNameIndexRoute @@ -102,6 +112,7 @@ export interface FileRouteTypes { | '/login' | '/agent' | '/apps' + | '/blacklist' | '/command' | '/room' | '/room/$roomName' @@ -111,6 +122,7 @@ export interface FileRouteTypes { | '/login' | '/agent' | '/apps' + | '/blacklist' | '/command' | '/room' | '/room/$roomName' @@ -122,6 +134,7 @@ export interface FileRouteTypes { | '/_auth/login/' | '/_authenticated/agent/' | '/_authenticated/apps/' + | '/_authenticated/blacklist/' | '/_authenticated/command/' | '/_authenticated/room/' | '/_authenticated/room/$roomName/' @@ -170,6 +183,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticatedCommandIndexRouteImport parentRoute: typeof AuthenticatedRoute } + '/_authenticated/blacklist/': { + id: '/_authenticated/blacklist/' + path: '/blacklist' + fullPath: '/blacklist' + preLoaderRoute: typeof AuthenticatedBlacklistIndexRouteImport + parentRoute: typeof AuthenticatedRoute + } '/_authenticated/apps/': { id: '/_authenticated/apps/' path: '/apps' @@ -214,6 +234,7 @@ const AuthRouteWithChildren = AuthRoute._addFileChildren(AuthRouteChildren) interface AuthenticatedRouteChildren { AuthenticatedAgentIndexRoute: typeof AuthenticatedAgentIndexRoute AuthenticatedAppsIndexRoute: typeof AuthenticatedAppsIndexRoute + AuthenticatedBlacklistIndexRoute: typeof AuthenticatedBlacklistIndexRoute AuthenticatedCommandIndexRoute: typeof AuthenticatedCommandIndexRoute AuthenticatedRoomIndexRoute: typeof AuthenticatedRoomIndexRoute AuthenticatedRoomRoomNameIndexRoute: typeof AuthenticatedRoomRoomNameIndexRoute @@ -222,6 +243,7 @@ interface AuthenticatedRouteChildren { const AuthenticatedRouteChildren: AuthenticatedRouteChildren = { AuthenticatedAgentIndexRoute: AuthenticatedAgentIndexRoute, AuthenticatedAppsIndexRoute: AuthenticatedAppsIndexRoute, + AuthenticatedBlacklistIndexRoute: AuthenticatedBlacklistIndexRoute, AuthenticatedCommandIndexRoute: AuthenticatedCommandIndexRoute, AuthenticatedRoomIndexRoute: AuthenticatedRoomIndexRoute, AuthenticatedRoomRoomNameIndexRoute: AuthenticatedRoomRoomNameIndexRoute, diff --git a/src/routes/_authenticated/blacklist/index.tsx b/src/routes/_authenticated/blacklist/index.tsx new file mode 100644 index 0000000..5a7c25d --- /dev/null +++ b/src/routes/_authenticated/blacklist/index.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_authenticated/blacklist/')({ + component: RouteComponent, +}) + +function RouteComponent() { + return
Hello "/_authenticated/blacklist/"!
+} diff --git a/src/routes/_authenticated/room/$roomName/index.tsx b/src/routes/_authenticated/room/$roomName/index.tsx index 7efa4cb..e621ad2 100644 --- a/src/routes/_authenticated/room/$roomName/index.tsx +++ b/src/routes/_authenticated/room/$roomName/index.tsx @@ -22,7 +22,6 @@ import { ChevronLeft, ChevronRight, Clock, - Hash, Loader2, MapPin, Monitor, @@ -65,20 +64,6 @@ function RoomDetailComponent() { ), }, - { - header: () => ( -
- - MAC Address -
- ), - accessorKey: "macAddress", - cell: ({ getValue }) => ( - - {getValue() as string} - - ), - }, { header: () => (
@@ -114,16 +99,57 @@ function RoomDetailComponent() { header: () => (
- Địa chỉ IP + Phòng
), - accessorKey: "ipAddress", + accessorKey: "room", cell: ({ getValue }) => ( - - {getValue() as string} - + {getValue() as string} ), }, + { + header: () => ( +
+ + Thông tin mạng +
+ ), + accessorKey: "networkInfos", + cell: ({ getValue }) => { + const networkInfos = getValue() as { + macAddress?: string; + ipAddress?: string; + }[]; + + if (!networkInfos || networkInfos.length === 0) { + return ( + + Không có dữ liệu + + ); + } + + return ( +
+ {networkInfos.map((info, idx) => ( +
+ + + {info.macAddress ?? "-"} + + + + {info.ipAddress ?? "-"} + +
+ ))} +
+ ); + }, + }, { header: "Trạng thái", accessorKey: "isOffline",