change quick command button in room detail page
This commit is contained in:
parent
67f5dbbb08
commit
dc7ed4c71a
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/TTMT.ManageWebGUI.iml" filepath="$PROJECT_DIR$/.idea/TTMT.ManageWebGUI.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AutoImportSettings">
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="94f75d5b-3c15-4d7e-9da7-ad6f5328faf8" name="Changes" comment="">
|
||||
<change beforePath="$PROJECT_DIR$/src/hooks/useAuth.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/hooks/useAuth.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/routeTree.gen.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/routeTree.gen.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/routes/_auth/blacklist/index.tsx" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/routes/_auth/command/index.tsx" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/routes/_auth/room/$roomName/index.tsx" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/routes/_auth/room/index.tsx" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/types/app-sidebar.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/types/app-sidebar.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/types/permission.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/types/permission.ts" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectColorInfo"><![CDATA[{
|
||||
"associatedIndex": 1
|
||||
}]]></component>
|
||||
<component name="ProjectId" id="3AQVfIkiaizPRlnpMICDG3COfJV" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||
"dart.analysis.tool.window.visible": "false",
|
||||
"git-widget-placeholder": "main",
|
||||
"ignore.virus.scanning.warn.message": "true",
|
||||
"javascript.preferred.runtime.type.id": "node",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"ts.external.directory.path": "D:\\MyProject\\NAVISProject\\TTMT.ManageWebGUI\\node_modules\\typescript\\lib",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
}
|
||||
}]]></component>
|
||||
<component name="SharedIndexes">
|
||||
<attachedChunks>
|
||||
<set>
|
||||
<option value="bundled-js-predefined-d6986cc7102b-9b0f141eb926-JavaScript-WS-253.31033.133" />
|
||||
</set>
|
||||
</attachedChunks>
|
||||
</component>
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="94f75d5b-3c15-4d7e-9da7-ad6f5328faf8" name="Changes" comment="" />
|
||||
<created>1772524885874</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1772524885874</updated>
|
||||
<workItem from="1772524887267" duration="1839000" />
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -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<number, any[]> = (Object.values(CommandType) as Array<number | string>)
|
||||
.filter((v) => typeof v === "number")
|
||||
.reduce((acc: Record<number, any[]>, type) => {
|
||||
acc[type as number] = (sensitiveCommands || []).filter((c: any) => Number(c.command) === Number(type));
|
||||
return acc;
|
||||
}, {} as Record<number, any[]>);
|
||||
|
||||
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"
|
||||
>
|
||||
<Icon className={`h-4 w-4 ${config.color}`} />
|
||||
{config.label}
|
||||
|
|
@ -163,7 +166,7 @@ export function CommandActionButtons({ roomName, selectedDevices = [] }: Command
|
|||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className={`gap-2 ${config.bgColor} border-${config.color.split('-')[1]}-200`}
|
||||
className={`gap-2 ${config.bgColor} border-${config.color.split('-')[1]}-200 flex-shrink-0`}
|
||||
>
|
||||
<Icon className={`h-4 w-4 ${config.color}`} />
|
||||
{config.label}
|
||||
|
|
@ -202,7 +205,7 @@ export function CommandActionButtons({ roomName, selectedDevices = [] }: Command
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<div className="flex gap-2 flex-nowrap overflow-x-auto items-center whitespace-nowrap">
|
||||
{Object.values(CommandType)
|
||||
.filter((value) => typeof value === "number")
|
||||
.map((commandType) => renderCommandButton(commandType as CommandType))}
|
||||
|
|
@ -225,6 +228,18 @@ export function CommandActionButtons({ roomName, selectedDevices = [] }: Command
|
|||
{confirmDialog.command.description}
|
||||
</p>
|
||||
)}
|
||||
{confirmDialog.isSensitive && (
|
||||
<div className="mt-2">
|
||||
<label className="block text-sm font-medium mb-1">Mật khẩu</label>
|
||||
<input
|
||||
type="password"
|
||||
value={sensitivePassword}
|
||||
onChange={(e) => setSensitivePassword(e.target.value)}
|
||||
className="w-full px-2 py-1 rounded border"
|
||||
placeholder="Nhập mật khẩu để xác nhận"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="bg-muted p-3 rounded-md space-y-1">
|
||||
<p className="text-sm">
|
||||
<span className="font-medium">Phòng:</span> {roomName}
|
||||
|
|
@ -255,7 +270,7 @@ export function CommandActionButtons({ roomName, selectedDevices = [] }: Command
|
|||
</Button>
|
||||
<Button
|
||||
onClick={handleConfirmExecute}
|
||||
disabled={isExecuting}
|
||||
disabled={isExecuting || (confirmDialog.isSensitive && !sensitivePassword)}
|
||||
className="gap-2"
|
||||
>
|
||||
{isExecuting ? (
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Monitor, Wifi, WifiOff } from "lucide-react";
|
||||
import { Monitor, Wifi, WifiOff, Loader2 } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { FolderStatusPopover } from "../folder-status-popover";
|
||||
import { useGetClientFolderStatusForDevice } from "@/hooks/queries";
|
||||
import type { ClientFolderStatus } from "@/types/folder";
|
||||
export function ComputerCard({
|
||||
device,
|
||||
|
|
@ -31,6 +33,62 @@ export function ComputerCard({
|
|||
const firstNetworkInfo = device.networkInfos?.[0];
|
||||
const agentVersion = device.version;
|
||||
|
||||
function DeviceFolderCheck() {
|
||||
const deviceId = device.id;
|
||||
const room = device.room;
|
||||
const [checking, setChecking] = useState(false);
|
||||
|
||||
const { data: status, isLoading } = useGetClientFolderStatusForDevice(
|
||||
deviceId,
|
||||
room,
|
||||
checking
|
||||
);
|
||||
|
||||
const handleCheck = () => setChecking((s) => !s);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
onClick={handleCheck}
|
||||
className="inline-flex items-center gap-2 px-3 py-1 rounded border bg-background text-sm"
|
||||
>
|
||||
{isLoading ? <Loader2 className="h-4 w-4 animate-spin" /> : null}
|
||||
Kiểm tra thư mục Setup
|
||||
</button>
|
||||
|
||||
{checking && isLoading && (
|
||||
<div className="text-xs text-muted-foreground mt-2">Đang kiểm tra...</div>
|
||||
)}
|
||||
|
||||
{checking && !isLoading && status && (
|
||||
<div className="text-xs mt-2">
|
||||
<div className="font-medium">Các file trong thư mục Setup({status.currentFiles?.length ?? 0})</div>
|
||||
<div className="mt-1 max-h-36 overflow-auto space-y-1">
|
||||
{(status.currentFiles ?? []).length === 0 ? (
|
||||
<div className="text-muted-foreground">Không có file hiện tại</div>
|
||||
) : (
|
||||
(status.currentFiles ?? []).map((f: any) => (
|
||||
<div key={f.fileName} className="font-mono text-xs">
|
||||
<div className="truncate">{f.fileName}</div>
|
||||
{f.lastModified && (
|
||||
<div className="text-muted-foreground text-[10px]">
|
||||
{new Date(f.lastModified).toLocaleString()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{checking && !isLoading && !status && (
|
||||
<div className="text-xs text-muted-foreground mt-2">Không có dữ liệu</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const DeviceInfo = () => (
|
||||
<div className="space-y-3 min-w-[280px]">
|
||||
<div>
|
||||
|
|
@ -69,6 +127,11 @@ export function ComputerCard({
|
|||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<div className="text-xs text-muted-foreground mb-1">Kiểm tra thư mục</div>
|
||||
<DeviceFolderCheck />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="text-xs text-muted-foreground mb-1">Trạng thái</div>
|
||||
<Badge
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ export const API_ENDPOINTS = {
|
|||
GET_COMMAND_BY_TYPES: (types: string) => `${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`,
|
||||
|
|
|
|||
|
|
@ -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"],
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -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<ClientFolderStatus | undefined>({
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,6 @@ function AgentsPage() {
|
|||
const columns: ColumnDef<Version>[] = [
|
||||
{ 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",
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ function AppsComponent() {
|
|||
const columns: ColumnDef<Version>[] = [
|
||||
{ accessorKey: "version", header: "Phiên bản" },
|
||||
{ accessorKey: "fileName", header: "Tên file" },
|
||||
{ accessorKey: "folderPath", header: "Đường dẫn" },
|
||||
{
|
||||
accessorKey: "updatedAt",
|
||||
header: () => <div className="whitespace-normal max-w-xs">Thời gian cập nhật</div>,
|
||||
|
|
|
|||
|
|
@ -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 }) => (
|
||||
cell: ({ getValue, row }) => {
|
||||
const full = (getValue() as string) || row.original.commandName || "";
|
||||
return (
|
||||
<div className="max-w-[100px]">
|
||||
<span className="font-semibold truncate block">{getValue() as string}</span>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="font-semibold truncate block cursor-help">{full}</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">{full}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
),
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "commandType",
|
||||
|
|
@ -93,18 +102,6 @@ function CommandPage() {
|
|||
return <span>{typeMap[type] || "UNKNOWN"}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "description",
|
||||
header: "Mô tả",
|
||||
size: 120,
|
||||
cell: ({ getValue }) => (
|
||||
<div className="max-w-[120px]">
|
||||
<span className="text-sm text-muted-foreground truncate block">
|
||||
{(getValue() as string) || "-"}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "commandContent",
|
||||
header: "Nội dung lệnh",
|
||||
|
|
@ -153,7 +150,7 @@ function CommandPage() {
|
|||
},
|
||||
{
|
||||
id: "select",
|
||||
header: () => <div className="text-center text-xs">Chọn để thực thi</div>,
|
||||
header: () => <div className="text-center text-xs">Thực thi</div>,
|
||||
cell: ({ row }) => (
|
||||
<input
|
||||
type="checkbox"
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ function RoomDetailPage() {
|
|||
Thực thi lệnh
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 flex-wrap justify-end">
|
||||
<div className="flex items-center gap-3 justify-end">
|
||||
{/* Command Action Buttons */}
|
||||
{devices.length > 0 && (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -51,3 +51,22 @@ export async function deleteCommand(commandId: number): Promise<any> {
|
|||
);
|
||||
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<any[]> {
|
||||
const response = await axios.get<any[]>(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<any> {
|
||||
const response = await axios.post(API_ENDPOINTS.COMMAND.REQUEST_SEND_SENSITIVE_COMMAND, data);
|
||||
return response.data;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -15,4 +15,5 @@ export enum CommandType {
|
|||
SHUTDOWN = 2,
|
||||
TASKKILL = 3,
|
||||
BLOCK = 4,
|
||||
RESET = 5,
|
||||
}
|
||||
|
|
|
|||
9
src/types/sensitive-command.ts
Normal file
9
src/types/sensitive-command.ts
Normal file
|
|
@ -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;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user