diff --git a/BACKEND/app/services/line_connection.ts b/BACKEND/app/services/line_connection.ts index 9ff2033..250ed82 100644 --- a/BACKEND/app/services/line_connection.ts +++ b/BACKEND/app/services/line_connection.ts @@ -35,6 +35,7 @@ interface LineConfig { output: string textfsm: string }[] + commands: string[] } interface User { @@ -52,6 +53,7 @@ export default class LineConnection { private waitingScenario: boolean private outputInventory: string private outputScenario: string + private bufferCommand: string constructor(config: LineConfig, socketIO: any) { this.config = config @@ -63,6 +65,7 @@ export default class LineConnection { this.waitingScenario = false this.outputInventory = '' this.outputScenario = '' + this.bufferCommand = '' } connect(timeoutMs = 5000) { @@ -120,6 +123,7 @@ export default class LineConnection { stationId, lineId: id, data: message, + commands: this.config.commands, }) if (!this.config.inventory) { setTimeout(() => { @@ -183,13 +187,26 @@ export default class LineConnection { }) } - writeCommand(cmd: string) { + writeCommand(cmd: string, isWrite = false) { if (this.client.destroyed) { console.log(`⚠️ Cannot send, line ${this.config.lineNumber} is closed`) return } this.client.write(`${cmd}`) + if (isWrite) { + const command = cmd.toString() + for (const char of command) { + if (char === '\x7F') this.bufferCommand = this.bufferCommand.slice(0, -1) + else if (char === '\r' && cleanData(this.bufferCommand).length > 0) { + this.config.commands = [ + cleanData(this.bufferCommand), + ...this.config.commands.filter((el) => el !== cleanData(this.bufferCommand)), + ].slice(0, 10) + this.bufferCommand = '' + } else this.bufferCommand += char + } + } } disconnect() { diff --git a/BACKEND/providers/socket_io_provider.ts b/BACKEND/providers/socket_io_provider.ts index 1c0338a..30548b3 100644 --- a/BACKEND/providers/socket_io_provider.ts +++ b/BACKEND/providers/socket_io_provider.ts @@ -115,7 +115,7 @@ export class WebSocketIo { listLineS.forEach((el) => { if (el?.userOpenCLI === userName) { const line = this.lineMap.get(el.id) - if (line) line.userCloseCLI() + if (line && line?.userCloseCLI()) line?.userCloseCLI() } }) setTimeout(() => { @@ -136,7 +136,7 @@ export class WebSocketIo { io, stationId, lineIds, - async (line) => line.writeCommand(command), + async (line) => line.writeCommand(command, true), { command, timeout: 120000 } ) }) @@ -167,7 +167,13 @@ export class WebSocketIo { const stationData = await Station.findBy('id', stationId) if (linesData && stationData) { this.lineConnecting.push(lineId) - await this.connectLine(io, [linesData], stationData, line?.config?.output || '') + await this.connectLine( + io, + [linesData], + stationData, + line?.config?.output || '', + line?.config?.commands || [] + ) const lineReconnect = this.lineMap.get(lineId) if (lineReconnect) { lineReconnect.userOpenCLI({ userEmail, userName: name }) @@ -187,7 +193,13 @@ export class WebSocketIo { const stationData = await Station.findBy('id', stationId) if (linesData && stationData) { this.lineConnecting.push(lineId) - await this.connectLine(io, [linesData], stationData, line?.config?.output || '') + await this.connectLine( + io, + [linesData], + stationData, + line?.config?.output || '', + line?.config?.commands || [] + ) const lineReconnect = this.lineMap.get(lineId) if (lineReconnect) { lineReconnect.userCloseCLI() @@ -422,7 +434,13 @@ export class WebSocketIo { return io } - private async connectLine(socket: any, lines: Line[], station: Station, output = '') { + private async connectLine( + socket: any, + lines: Line[], + station: Station, + output = '', + commands: string[] = [] + ) { try { this.stationMap.set(station.id, station) for (const line of lines) { @@ -441,6 +459,7 @@ export class WebSocketIo { userEmailOpenCLI: '', userOpenCLI: '', data: [], + commands: commands, }, socket ) @@ -505,7 +524,13 @@ export class WebSocketIo { if (linesData && stationData) { this.lineConnecting.push(lineId) - await this.connectLine(io, [linesData], stationData, line?.config?.output || '') + await this.connectLine( + io, + [linesData], + stationData, + line?.config?.output || '', + line?.config?.commands || [] + ) this.lineConnecting = this.lineConnecting.filter((el) => el !== lineId) const lineReconnect = this.lineMap.get(lineId) diff --git a/FRONTEND/src/App.tsx b/FRONTEND/src/App.tsx index 559912a..169f31d 100644 --- a/FRONTEND/src/App.tsx +++ b/FRONTEND/src/App.tsx @@ -91,8 +91,6 @@ function App() { if (response.status) { if (Array.isArray(response.data)) { setStations(response.data); - // if (response.data?.length > 0) - // setActiveTab(response.data[0]?.id.toString()); } } } catch (error) { @@ -141,7 +139,7 @@ function App() { socket?.on("line_output", (data) => { updateValueLineStation( data?.lineId, - { netOutput: data.data }, + { netOutput: data.data, commands: data.commands || [] }, data?.stationId ); }); diff --git a/FRONTEND/src/components/CardLine.tsx b/FRONTEND/src/components/CardLine.tsx index 5d52397..fae89d9 100644 --- a/FRONTEND/src/components/CardLine.tsx +++ b/FRONTEND/src/components/CardLine.tsx @@ -118,6 +118,7 @@ const CardLine = ({ initContent={line?.netOutput ?? ""} loadingContent={line?.loadingOutput} line_id={Number(line?.id)} + line={line} station_id={Number(stationItem.id)} isDisabled={ typeof line?.userOpenCLI !== "undefined" && diff --git a/FRONTEND/src/components/ModalTerminal.tsx b/FRONTEND/src/components/ModalTerminal.tsx index c8d66fe..b5e536a 100644 --- a/FRONTEND/src/components/ModalTerminal.tsx +++ b/FRONTEND/src/components/ModalTerminal.tsx @@ -170,6 +170,7 @@ const ModalTerminal = ({ initContent={line?.netOutput ?? ""} loadingContent={line?.loadingOutput} line_id={Number(line?.id)} + line={line} station_id={Number(stationItem?.id)} isDisabled={ typeof line?.userOpenCLI !== "undefined" && diff --git a/FRONTEND/src/components/TerminalXTerm.tsx b/FRONTEND/src/components/TerminalXTerm.tsx index 0295454..8addd36 100644 --- a/FRONTEND/src/components/TerminalXTerm.tsx +++ b/FRONTEND/src/components/TerminalXTerm.tsx @@ -3,12 +3,14 @@ import { Terminal } from "xterm"; import "xterm/css/xterm.css"; import { FitAddon } from "@xterm/addon-fit"; import type { Socket } from "socket.io-client"; +import type { TLine } from "../untils/types"; interface TerminalCLIProps { socket: Socket | null; content?: string; initContent?: string; line_id: number; + line: TLine | undefined; line_status: string; station_id: number; cliOpened: boolean; @@ -44,6 +46,7 @@ const TerminalCLI: React.FC = ({ loadingContent = false, onFocus, onBlur, + line, }) => { const xtermRef = useRef(null); const terminal = useRef(null); @@ -103,7 +106,8 @@ const TerminalCLI: React.FC = ({ if (e.ctrlKey && e.key.toLowerCase() === "v") return false; // Handle Esc if (e.key === "Escape") return false; - + // Handle Enter + // if (e.key === "ArrowUp") handleArrowUp(); return true; // allow all other keys through } ); diff --git a/FRONTEND/src/untils/helper.ts b/FRONTEND/src/untils/helper.ts index a2ade8b..e72a155 100644 --- a/FRONTEND/src/untils/helper.ts +++ b/FRONTEND/src/untils/helper.ts @@ -62,3 +62,20 @@ export const useDebounce = void>( return debouncedFn; }; + +export const handleStorageCommand = (command: string, lineId: number) => { + const storage = localStorage.getItem("history_commands"); + const history: Record = storage ? JSON.parse(storage) : {}; + let commands = history[lineId] || []; + commands = [command, ...commands.filter((cmd) => cmd !== command)]; + commands = commands.slice(0, 10); + history[lineId] = commands; + + localStorage.setItem("history_commands", JSON.stringify(history)); +}; + +export const handleGetStorageCommand = (lineId: number): string[] => { + const storage = localStorage.getItem("history_commands"); + const history: Record = storage ? JSON.parse(storage) : {}; + return history[lineId] || []; +}; diff --git a/FRONTEND/src/untils/types.ts b/FRONTEND/src/untils/types.ts index d5e1cf1..cfdd7b8 100644 --- a/FRONTEND/src/untils/types.ts +++ b/FRONTEND/src/untils/types.ts @@ -92,6 +92,7 @@ export type TLine = { name: string; time: number; }; + commands?: string[]; }; export type TUser = {