diff --git a/BACKEND/app/services/apc_connection.ts b/BACKEND/app/services/apc_connection.ts index 896236d..96133cc 100644 --- a/BACKEND/app/services/apc_connection.ts +++ b/BACKEND/app/services/apc_connection.ts @@ -17,8 +17,8 @@ interface PromptCallback { } class APCController { - private apc_number?: number - private apc_ip: string + public apc_number?: number + public apc_ip: string private apc_port: number private apc_username: string private apc_password: string @@ -100,7 +100,7 @@ class APCController { this.buffer = '' } } - // appendLog(data, 0, 0, this.apc_number || 0) + appendLog(data, 0, 0, this.apc_number || 0) } private _handleClose(): void { @@ -130,7 +130,7 @@ class APCController { setTimeout(() => { console.log('[ECONNRESET] Trying reconnect apc:', this.apc_ip) this.reconnect() - }, 10000) + }, 15000) } } diff --git a/BACKEND/app/services/line_connection.ts b/BACKEND/app/services/line_connection.ts index b3838ec..0ab2b3b 100644 --- a/BACKEND/app/services/line_connection.ts +++ b/BACKEND/app/services/line_connection.ts @@ -187,7 +187,7 @@ export default class LineConnection { }) } - writeCommand(cmd: string, isWrite = false) { + writeCommand(cmd: string | Buffer, isWrite = false) { if (this.client.destroyed) { console.log(`⚠️ Cannot send, line ${this.config.lineNumber} is closed`) return @@ -513,4 +513,17 @@ export default class LineConnection { console.log(error) } } + + // Gửi nhiều ký tự ESC để vào ROMMON + breakSpam() { + let count = 0 + const escInterval = setInterval(() => { + if (count >= 100) { + clearInterval(escInterval) + return + } + this.writeCommand(Buffer.from([0xff, 0xf3])) // Ctrl + Break + count++ + }, 1) + } } diff --git a/BACKEND/app/services/switch_connection.ts b/BACKEND/app/services/switch_connection.ts index 7013492..02dc7f0 100644 --- a/BACKEND/app/services/switch_connection.ts +++ b/BACKEND/app/services/switch_connection.ts @@ -83,7 +83,7 @@ export default class SwitchController { private _waitFor(prompt: string, timeout = 5000): Promise { return new Promise((resolve, reject) => { const timer = setTimeout(() => { - reject(new Error(`Timeout waiting for: ${prompt}`)) + resolve(`Timeout waiting for: ${prompt}`) }, timeout) this.promptCallbacks.push({ @@ -188,58 +188,74 @@ export default class SwitchController { public async turnPortOff(port: string) { await this.enterEnableMode() this._send(`configure terminal`) - await this._waitFor('(config)#') + // await this._waitFor('(config)#') + await this.sleep(500) this._send(`interface ${port}`) - await this._waitFor('(config-if)#') + // await this._waitFor('(config-if)#') + await this.sleep(500) this._send(`shutdown`) - await this._waitFor('(config-if)#') + // await this._waitFor('(config-if)#') + await this.sleep(500) this._send(`end`) } public async turnPortOn(port: string) { await this.enterEnableMode() this._send(`configure terminal`) - await this._waitFor('(config)#') + // await this._waitFor('(config)#') + await this.sleep(500) this._send(`interface ${port}`) - await this._waitFor('(config-if)#') + // await this._waitFor('(config-if)#') + await this.sleep(500) this._send(`no shutdown`) - await this._waitFor('(config-if)#') + // await this._waitFor('(config-if)#') + await this.sleep(500) this._send(`end`) } public async restartPort(port: string) { await this.enterEnableMode() this._send(`configure terminal`) - await this._waitFor('(config)#') + // await this._waitFor('(config)#') + await this.sleep(500) this._send(`interface ${port}`) - await this._waitFor('(config-if)#') + // await this._waitFor('(config-if)#') + await this.sleep(500) this._send(`shutdown`) - await this._waitFor('(config-if)#') + // await this._waitFor('(config-if)#') + await this.sleep(500) await this.sleep(2000) this._send(`no shutdown`) - await this._waitFor('(config-if)#') + // await this._waitFor('(config-if)#') + await this.sleep(500) this._send(`end`) } public async disablePoE(port: string) { await this.enterEnableMode() this._send(`configure terminal`) - await this._waitFor('(config)#') + // await this._waitFor('(config)#') + await this.sleep(500) this._send(`interface ${port}`) - await this._waitFor('(config-if)#') + // await this._waitFor('(config-if)#') + await this.sleep(500) this._send(`power inline never`) - await this._waitFor('(config-if)#') + // await this._waitFor('(config-if)#') + await this.sleep(500) this._send(`end`) } public async enablePoE(port: string) { await this.enterEnableMode() this._send(`configure terminal`) - await this._waitFor('(config)#') + // await this._waitFor('(config)#') + await this.sleep(500) this._send(`interface ${port}`) - await this._waitFor('(config-if)#') + // await this._waitFor('(config-if)#') + await this.sleep(500) this._send(`power inline auto`) - await this._waitFor('(config-if)#') + // await this._waitFor('(config-if)#') + await this.sleep(500) this._send(`end`) } diff --git a/BACKEND/providers/socket_io_provider.ts b/BACKEND/providers/socket_io_provider.ts index 6dea448..0937e7e 100644 --- a/BACKEND/providers/socket_io_provider.ts +++ b/BACKEND/providers/socket_io_provider.ts @@ -63,12 +63,12 @@ export default class SocketIoProvider { export class WebSocketIo { intervalMap: { [key: string]: NodeJS.Timeout } = {} - stationMap: Map = new Map() lineMap: Map = new Map() // key = lineId userConnecting: Map = new Map() apcsControl: Map = new Map() switchControl: Map = new Map() lineConnecting: number[] = [] // key = lineId + intervalKeepConnect: { [key: string]: NodeJS.Timeout } = {} constructor(protected app: ApplicationService) {} @@ -115,7 +115,16 @@ export class WebSocketIo { listLineS.forEach((el) => { if (el?.userOpenCLI === userName) { const line = this.lineMap.get(el.id) - if (line && line?.userCloseCLI()) line?.userCloseCLI() + if (line) { + line.config.openCLI = false + line.config.userEmailOpenCLI = '' + line.config.userOpenCLI = '' + io.emit('user_close_cli', { + stationId: line.config.stationId, + lineId: line.config.id, + userEmailOpenCLI: '', + }) + } } }) setTimeout(() => { @@ -136,7 +145,8 @@ export class WebSocketIo { io, stationId, lineIds, - async (line) => line.writeCommand(command, true), + async (line) => + command === 'spam_break' ? line.breakSpam() : line.writeCommand(command, true), { command, timeout: 120000 } ) }) @@ -159,21 +169,26 @@ export class WebSocketIo { socket.on('open_cli', async (data) => { const { lineId, userEmail, userName: name, stationId } = data const line = this.lineMap.get(lineId) - if (line && line?.userOpenCLI) { - line?.userOpenCLI({ userEmail, userName: name }) + if (line) { + if (line?.userOpenCLI) line?.userOpenCLI({ userEmail, userName: name }) + else { + line.config.openCLI = true + line.config.userEmailOpenCLI = userEmail + line.config.userOpenCLI = userName + io.emit('user_open_cli', { + stationId: line.config.stationId, + lineId: line.config.id, + userEmailOpenCLI: userEmail, + userOpenCLI: userName, + }) + } } else { if (this.lineConnecting.includes(lineId)) return const linesData = await Line.findBy('id', lineId) const stationData = await Station.findBy('id', stationId) if (linesData && stationData) { this.lineConnecting.push(lineId) - await this.connectLine( - io, - [linesData], - stationData, - line?.config?.output || '', - line?.config?.commands || [] - ) + await this.connectLine(io, [linesData], stationData) const lineReconnect = this.lineMap.get(lineId) if (lineReconnect) { lineReconnect.userOpenCLI({ userEmail, userName: name }) @@ -185,21 +200,25 @@ export class WebSocketIo { socket.on('close_cli', async (data) => { const { lineId, stationId } = data const line = this.lineMap.get(lineId) - if (line && line?.userCloseCLI) { - line?.userCloseCLI() + if (line) { + if (line?.userCloseCLI) line?.userCloseCLI() + else { + line.config.openCLI = false + line.config.userEmailOpenCLI = '' + line.config.userOpenCLI = '' + io.emit('user_close_cli', { + stationId: line.config.stationId, + lineId: line.config.id, + userEmailOpenCLI: '', + }) + } } else { if (this.lineConnecting.includes(lineId)) return const linesData = await Line.findBy('id', lineId) const stationData = await Station.findBy('id', stationId) if (linesData && stationData) { this.lineConnecting.push(lineId) - await this.connectLine( - io, - [linesData], - stationData, - line?.config?.output || '', - line?.config?.commands || [] - ) + await this.connectLine(io, [linesData], stationData) const lineReconnect = this.lineMap.get(lineId) if (lineReconnect) { lineReconnect.userCloseCLI() @@ -265,7 +284,10 @@ export class WebSocketIo { if (!station) return const apcIp = (station as any)[`${apcName}_ip`] as string const apc = this.apcsControl.get(apcIp) - if (apc) await apc.reconnect() + if (apc) { + await apc.reconnect() + this.keepConnectAPC(apcIp, io) + } else await this.connectApc(io, apcName, station) } else { for (const outletNumber of outletNumbers) { if (!outletNumber || outletNumber < 0) return @@ -282,7 +304,11 @@ export class WebSocketIo { const apcIp = (station as any)[`${apcName}_ip`] as string const apc = this.apcsControl.get(apcIp) - if (apc && apc.status === 'CONNECTED') { + if (apc && apc.status !== 'CONNECTED') { + await apc.reconnect() + this.keepConnectAPC(apcIp, io) + } + if (apc) { switch (action) { case 'on': await apc?.turnOnOutlet(outletNumber) @@ -302,24 +328,6 @@ export class WebSocketIo { setTimeout(() => { apc?.navigateToOutlets() }, 10000) - } else if (apc && apc.status !== 'CONNECTED') { - await apc.reconnect() - switch (action) { - case 'on': - await apc?.turnOnOutlet(outletNumber) - break - case 'off': - await apc?.turnOffOutlet(outletNumber) - break - case 'restart': - await apc?.restartOutlet(outletNumber) - break - default: - break - } - setTimeout(() => { - apc?.navigateToOutlets() - }, 10000) } } } @@ -336,8 +344,11 @@ export class WebSocketIo { data: apc.output, status: apc.status, }) + this.keepConnectAPC(apcIp, io) } else if (apc && apc.status !== 'CONNECTED') { await apc.reconnect() + this.apcsControl.set(apcIp, apc) + this.keepConnectAPC(apcIp, io) } else await this.connectApc(io, apcName, station) } catch (error) { console.log(error) @@ -442,7 +453,6 @@ export class WebSocketIo { commands: string[] = [] ) { try { - this.stationMap.set(station.id, station) for (const line of lines) { const lineConn = new LineConnection( { @@ -584,6 +594,7 @@ export class WebSocketIo { await apc.connect() await apc.login() this.apcsControl.set(ip, apc) + this.keepConnectAPC(ip, socket) } catch (error) { console.log(error) } @@ -603,7 +614,7 @@ export class WebSocketIo { status: 'DISCONNECTED', message: `Missing Switch configuration`, }) - throw new Error(`Missing Switch configuration`) + return } // Tạo APC Controller instance @@ -633,7 +644,10 @@ export class WebSocketIo { private async clearLineBeforeConnect(stationId: number, clearLine: number) { const station = await Station.find(stationId) - if (!station) throw new Error(`Station ${stationId} not found`) + if (!station) { + console.log('[ERROR connect station] Not found!') + return + } // Kết nối tới station qua Telnet / Socket const client = new net.Socket() @@ -679,7 +693,9 @@ export class WebSocketIo { const newMap = new Map() this.lineMap.forEach((line, id) => { if (line && line.config) { - newMap.set(id, { config: { ...line.config, status: 'disconnected' } } as LineConnection) + newMap.set(id, { + config: { ...line.config, status: 'disconnected' }, + } as LineConnection) } }) @@ -695,4 +711,19 @@ export class WebSocketIo { const parsed = JSON.parse(raw) this.lineMap = new Map(parsed.lineMap) } + + private keepConnectAPC = (ip: string, io: any) => { + if (this.intervalKeepConnect[`${ip}`]) { + clearInterval(this.intervalKeepConnect[`${ip}`]) + delete this.intervalKeepConnect[`${ip}`] + } + const interval = setInterval(() => { + const apcConnect = this.apcsControl.get(ip) + if (apcConnect && apcConnect.status === 'CONNECTED') { + apcConnect._send('ENTER') + } + }, 40000) + + this.intervalKeepConnect[`${ip}`] = interval + } } diff --git a/FRONTEND/src/App.module.css b/FRONTEND/src/App.module.css index 821b93d..8dd5e6e 100644 --- a/FRONTEND/src/App.module.css +++ b/FRONTEND/src/App.module.css @@ -70,3 +70,10 @@ body { margin: 0.1rem auto 0; border-radius: 3px; } + +.containerMain { + display: flex; + flex-direction: column; + justify-content: space-between; + height: 88vh; +} diff --git a/FRONTEND/src/App.tsx b/FRONTEND/src/App.tsx index 169f31d..38c8d20 100644 --- a/FRONTEND/src/App.tsx +++ b/FRONTEND/src/App.tsx @@ -27,22 +27,22 @@ import type { import axios from "axios"; import CardLine from "./components/CardLine"; import { SocketProvider, useSocket } from "./context/SocketContext"; -import { - // ButtonConnect, - ButtonControlApc, - ButtonCopy, - ButtonDPELP, - ButtonScenario, - ButtonSelect, -} from "./components/ButtonAction"; +import // ButtonConnect, +// ButtonControlApc, +// ButtonCopy, +// ButtonDPELP, +// ButtonScenario, +// ButtonSelect, +"./components/ButtonAction"; import StationSetting from "./components/FormAddEdit"; -import DrawerScenario from "./components/DrawerScenario"; +// import DrawerScenario from "./components/DrawerScenario"; import { Notifications } from "@mantine/notifications"; import ModalTerminal from "./components/ModalTerminal"; import PageLogin from "./components/Authentication/LoginPage"; -import DrawerLogs from "./components/DrawerLogs"; +// import DrawerLogs from "./components/DrawerLogs"; import DraggableTabs from "./components/DragTabs"; import { isJsonString } from "./untils/helper"; +import BottomToolBar from "./components/BottomToolBar"; const apiUrl = import.meta.env.VITE_BACKEND_URL; @@ -67,7 +67,6 @@ function App() { const [stations, setStations] = useState([]); const [selectedLines, setSelectedLines] = useState([]); const [activeTab, setActiveTab] = useState("0"); - const [showBottomShadow, setShowBottomShadow] = useState(false); const [isDisable, setIsDisable] = useState(false); const [isOpenAddStation, setIsOpenAddStation] = useState(false); const [isEditStation, setIsEditStation] = useState(false); @@ -84,6 +83,29 @@ function App() { const [testLogContent, setTestLogContent] = useState(""); const [isLogModalOpen, setIsLogModalOpen] = useState(false); + const connectApcSwitch = (station: TStation) => { + if (station?.apc_1_ip && station?.apc_1_port) { + socket?.emit("connect_apc", { + station: station, + apcIp: station?.apc_1_ip, + apcName: "apc_1", + }); + } + if (station?.apc_2_ip && station?.apc_2_port) { + socket?.emit("connect_apc", { + station: station, + apcIp: station?.apc_2_ip, + apcName: "apc_2", + }); + } + if (station?.switch_control_ip && station?.switch_control_port) { + socket?.emit("connect_switch", { + station: station, + ip: station?.switch_control_ip, + }); + } + }; + // function get list station const getStation = async () => { try { @@ -91,6 +113,9 @@ function App() { if (response.status) { if (Array.isArray(response.data)) { setStations(response.data); + response.data.forEach((station) => { + connectApcSwitch(station); + }); } } } catch (error) { @@ -113,9 +138,10 @@ function App() { }; useEffect(() => { + if (!socket) return; getStation(); getScenarios(); - }, []); + }, [socket]); useEffect(() => { if (!socket || !stations?.length) return; @@ -363,16 +389,6 @@ function App() { setSelectedLine(data); }; - useEffect(() => { - return () => { - if (selectedLine) - socket?.emit("close_cli", { - lineId: selectedLine?.id, - stationId: selectedLine?.station_id, - }); - }; - }, []); - return ( - - - { - const el = document.querySelector( - ".mantine-ScrollArea-viewport" - ); - if (!el) return; - const maxScroll = el.scrollHeight - el.clientHeight; - setShowBottomShadow(y < maxScroll - 2); + + + - {station.lines.length > 8 ? ( - - - - {station.lines.slice(0, 8).map((line, i) => ( - - ))} - - - - - {station.lines - .slice(8, station.lines.length) - .map((line, i) => ( + + {station.lines.length > 8 ? ( + + + + {station.lines.slice(0, 8).map((line, i) => ( ))} - - - - ) : station.lines.length <= 8 && station.lines.length > 0 ? ( - - {station.lines.map((line, i) => ( - - ))} - - ) : ( - - No lines configured - - )} - - - - - - - {/* */} - - - -
- -
- { - setSelectedLines([]); - setIsDisable(true); - setTimeout(() => { - setIsDisable(false); - }, 5000); - }} - /> - - - {scenarios.map((el, i) => ( - +
+ + + {station.lines + .slice(8, station.lines.length) + .map((line, i) => ( + + ))} + + +
+ ) : station.lines.length <= 8 && + station.lines.length > 0 ? ( + + {station.lines.map((line, i) => ( + - !el?.userEmailOpenCLI || - el?.userEmailOpenCLI === user?.email - )} - isDisable={ - isDisable || - selectedLines.filter( - (el) => - !el?.userEmailOpenCLI || - el?.userEmailOpenCLI === user?.email - ).length === 0 + stationItem={station} + line={line} + selectedLines={selectedLines} + setSelectedLines={setSelectedLines} + openTerminal={openTerminal} + loadTerminal={ + loadingTerminal && + Number(station.id) === Number(activeTab) } - onClick={() => { - setSelectedLines([]); - setIsDisable(true); - setTimeout(() => { - setIsDisable(false); - }, 5000); - }} - scenario={el} /> ))} -
- - - -
-
+ ) : ( + + No lines configured + + )} + + + + + ))} onChange={(id) => { diff --git a/FRONTEND/src/components/BottomToolBar.tsx b/FRONTEND/src/components/BottomToolBar.tsx new file mode 100644 index 0000000..e467e27 --- /dev/null +++ b/FRONTEND/src/components/BottomToolBar.tsx @@ -0,0 +1,278 @@ +import { + Box, + Button, + CloseButton, + Flex, + Input, + ScrollArea, + Tabs, + Text, +} from "@mantine/core"; +import { useState } from "react"; +import classes from "./Component.module.css"; +import type { TLine, TStation } from "../untils/types"; +import type { Socket } from "socket.io-client"; +import { ButtonDPELP, ButtonSelect } from "./ButtonAction"; +import DrawerLogs from "./DrawerLogs"; +import { DrawerAPCControl, DrawerSwitchControl } from "./DrawerControl"; + +interface TabsProps { + selectedLines: TLine[]; + socket: Socket | null; + setSelectedLines: (lines: React.SetStateAction) => void; + isDisable: boolean; + station: TStation; + setIsDisable: (lines: React.SetStateAction) => void; + testLogContent: string; + isLogModalOpen: boolean; + setIsLogModalOpen: (lines: React.SetStateAction) => void; + setTestLogContent: (lines: React.SetStateAction) => void; +} + +const BottomToolBar = ({ + selectedLines, + socket, + setSelectedLines, + isDisable, + station, + setIsDisable, + testLogContent, + isLogModalOpen, + setIsLogModalOpen, + setTestLogContent, +}: TabsProps) => { + const [valueInput, setValueInput] = useState(""); + const [activeTabBottom, setActiveBottom] = useState("command"); + + return ( + { + setActiveBottom(val || "command"); + }} + className={classes.containerBottom} + > + + + Command Line + + + APC + + + Switch + + + + + + + + {selectedLines.map((el) => ( + + + Line {el.lineNumber} + + setSelectedLines( + selectedLines.filter((line) => line.id !== el.id) + ) + } + /> + + + ))} + + + + + + + + + { + const newValue = event.currentTarget.value; + setValueInput(newValue); + }} + onKeyDown={(event) => { + if (event.key === "Enter") { + const listLine = selectedLines.length + ? selectedLines + : station?.lines; + if (listLine?.length) { + socket?.emit("write_command_line_from_web", { + lineIds: listLine.map((line) => line.id), + stationId: station.id, + command: valueInput + "\n", + }); + setTimeout(() => { + socket?.emit("write_command_line_from_web", { + lineIds: listLine.map((line) => line.id), + stationId: station.id, + command: " \n", + }); + }, 1000); + } + setValueInput(""); + } + }} + rightSectionPointerEvents="all" + rightSection={ + setValueInput("")} + style={{ + display: valueInput ? undefined : "none", + }} + /> + } + /> + + + + + + { + setSelectedLines([]); + setIsDisable(true); + setTimeout(() => { + setIsDisable(false); + }, 5000); + }} + /> + + {/* + {scenarios.map((el, i) => ( + + !el?.userEmailOpenCLI || + el?.userEmailOpenCLI === user?.email + )} + isDisable={ + isDisable || + selectedLines.filter( + (el) => + !el?.userEmailOpenCLI || + el?.userEmailOpenCLI === user?.email + ).length === 0 + } + onClick={() => { + setSelectedLines([]); + setIsDisable(true); + setTimeout(() => { + setIsDisable(false); + }, 5000); + }} + scenario={el} + /> + ))} + */} + + + + + + + + + + + + + ); +}; + +export default BottomToolBar; diff --git a/FRONTEND/src/components/Component.module.css b/FRONTEND/src/components/Component.module.css index 5378881..75af451 100644 --- a/FRONTEND/src/components/Component.module.css +++ b/FRONTEND/src/components/Component.module.css @@ -111,3 +111,11 @@ .hideScrollBar::-webkit-scrollbar { display: none; } + +.containerBottom { + height: 22vh; + padding: 8px; + border: 1px solid #ccc; + border-radius: 8px; + background-color: #f3f3f38c; +} diff --git a/FRONTEND/src/components/DragTabs.tsx b/FRONTEND/src/components/DragTabs.tsx index 5453de1..37855f3 100644 --- a/FRONTEND/src/components/DragTabs.tsx +++ b/FRONTEND/src/components/DragTabs.tsx @@ -2,11 +2,8 @@ import { ActionIcon, Avatar, Box, - Button, - CloseButton, Flex, Group, - Input, Menu, Tabs, Text, @@ -39,7 +36,6 @@ import { import classes from "./Component.module.css"; import type { TStation, TUser } from "../untils/types"; import type { Socket } from "socket.io-client"; -import { DrawerAPCControl, DrawerSwitchControl } from "./DrawerControl"; interface DraggableTabsProps { tabsData: TStation[]; @@ -87,6 +83,7 @@ function SortableTab({ transition, cursor: "grab", userSelect: "none", + backgroundColor: active === tab.id.toString() ? "#deffde" : "", }} color={active === tab.id.toString() ? "green" : ""} fw={600} @@ -116,7 +113,6 @@ export default function DraggableTabs({ setStationEdit, active, setActive, - onSendCommand, }: DraggableTabsProps) { const user = useMemo(() => { return localStorage.getItem("user") && @@ -127,12 +123,6 @@ export default function DraggableTabs({ const [tabs, setTabs] = useState(tabsData); const [isChangeTab, setIsChangeTab] = useState(false); const [isSetActive, setIsSetActive] = useState(false); - const [valueInput, setValueInput] = useState(""); - const [openedAPC, setOpenedAPC] = useState(false); - const [openedSwitch, setOpenedSwitch] = useState(false); - // const [active, setActive] = useState( - // tabsData?.length > 0 ? tabsData[0]?.id.toString() : null - // ); const sensors = useSensors(useSensor(PointerSensor)); @@ -220,29 +210,6 @@ export default function DraggableTabs({ }; }, []); - const connectApcSwitch = (station: TStation) => { - if (station?.apc_1_ip && station?.apc_1_port) { - socket?.emit("connect_apc", { - station: station, - apcIp: station?.apc_1_ip, - apcName: "apc_1", - }); - } - if (station?.apc_2_ip && station?.apc_2_port) { - socket?.emit("connect_apc", { - station: station, - apcIp: station?.apc_2_ip, - apcName: "apc_2", - }); - } - if (station?.switch_control_ip && station?.switch_control_port) { - socket?.emit("connect_switch", { - station: station, - ip: station?.switch_control_ip, - }); - } - }; - return ( - - { - const newValue = event.currentTarget.value; - setValueInput(newValue); - }} - onKeyDown={(event) => { - if (event.key === "Enter") { - onSendCommand(valueInput); - setValueInput(""); - } - }} - rightSectionPointerEvents="all" - rightSection={ - setValueInput("")} - style={{ display: valueInput ? undefined : "none" }} - /> - } - /> - - - - - + - - {tabs.find((el) => el.id.toString() === active) && ( - { - setOpenedAPC(false); - }} - socket={socket} - stationAPI={tabs.find((el) => el.id.toString() === active) || tabs[0]} - openedSwitch={() => { - setOpenedAPC(false); - setOpenedSwitch(true); - }} - /> - )} - - {tabs.find((el) => el.id.toString() === active) && ( - { - setOpenedSwitch(false); - }} - socket={socket} - stationAPI={tabs.find((el) => el.id.toString() === active) || tabs[0]} - openedAPC={() => { - setOpenedSwitch(false); - setOpenedAPC(true); - }} - /> - )} ); } diff --git a/FRONTEND/src/components/DrawerControl.tsx b/FRONTEND/src/components/DrawerControl.tsx index 003ca00..8f3b5ae 100644 --- a/FRONTEND/src/components/DrawerControl.tsx +++ b/FRONTEND/src/components/DrawerControl.tsx @@ -1,4 +1,4 @@ -import { Box, Button, Card, Drawer, Grid, Loader, Text } from "@mantine/core"; +import { Box, Button, Card, Grid, Loader, Text } from "@mantine/core"; import { IconRepeat, IconSection } from "@tabler/icons-react"; import { useEffect, useState } from "react"; import classes from "./Component.module.css"; @@ -8,10 +8,6 @@ import type { Socket } from "socket.io-client"; interface DrawerProps { stationAPI: TStation; socket: Socket | null; - open: boolean; - onClose: () => void; - openedSwitch?: () => void; - openedAPC?: () => void; } type TSelectedOutlet = { @@ -24,9 +20,6 @@ type TSelectedOutlet = { export const DrawerAPCControl: React.FC = ({ stationAPI, socket, - open, - onClose, - openedSwitch, }) => { const findLineByOutlet = (outlet: TSelectedOutlet) => { return stationAPI.lines.find( @@ -58,12 +51,6 @@ export const DrawerAPCControl: React.FC = ({ >([]); const [isSubmit, setIsSubmit] = useState(true); - useEffect(() => { - if (!open) { - setListOutletSelected([]); - } - }, [open]); - const detectOutlet = ( apc: APCProps, lines: string[], @@ -195,12 +182,12 @@ export const DrawerAPCControl: React.FC = ({ }; const RenderAPCStatus = (apc: APCProps) => { - switch (apc.status) { + switch (apc?.status) { case "CONNECTED": return ( <> - - {apc.status} + + {apc?.status} ); @@ -208,219 +195,311 @@ export const DrawerAPCControl: React.FC = ({ case "DISCONNECTED": return ( <> -
- - - {apc.status} + + {apc?.status} ); case "TIMEOUT": return ( <> - - {apc.status} + + {apc?.status} ); default: return ( <> - {/* - {line.status} - */} + {/* + WRONG CONFIG + */} ); } }; return ( - - APC Control -
- + ) : ( +
+ )} +
+ + + + {listOutlet + .filter((el) => el.apc === 1) + .map((outlet, i) => ( + el.name === outlet.name && el.apc === outlet.apc + )?.name + ? "1px solid #0018ff" + : "", + }} + onClick={() => { + toggleSelect(outlet, i + 1); + }} + > + + {findLineByOutlet(outlet) + ? "Line " + findLineByOutlet(outlet)?.lineNumber + : outlet.name} + + + ))} + +
- Switch Control - -
- - } - size="xs" - position="bottom" - > - - -
- -
- - APC 1 - - {dataStation?.apc1?.status && - RenderAPCStatus(dataStation?.apc1)} - {dataStation?.apc1?.status === "DISCONNECTED" || - dataStation?.apc1?.status === "TIMEOUT" ? ( - - ) : ( -
- )} -
-
- - - {listOutlet - .filter((el) => el.apc === 1) - .map((outlet, i) => ( - - el.name === outlet.name && el.apc === outlet.apc - )?.name - ? "1px solid #0018ff" - : "", - }} - onClick={() => { - toggleSelect(outlet, i + 1); - }} - > - - {findLineByOutlet(outlet) - ? "Line " + findLineByOutlet(outlet)?.lineNumber - : outlet.name} - - - ))} - -
el.apc === 1).length === + listOutlet.length + ? "Deselect All" + : "Select All" + } + // mt={'xs'} + miw={"80px"} + size="xs" + fz={"xs"} + variant="filled" + onClick={() => { + if ( + listOutletSelected.filter((el) => el.apc === 1).length === + listOutlet.filter((el) => el.apc === 1).length + ) { + setListOutletSelected([]); + } else { + setListOutletSelected( + listOutlet + .filter((el) => el.apc === 1) + .map((el) => ({ ...el, apc: 1 })) + ); + } }} > + {listOutletSelected.filter((el) => el.apc === 1).length === + listOutlet.filter((el) => el.apc === 1).length + ? "Deselect All" + : "Select All"}{" "} + {"(" + listOutlet.filter((el) => el.apc === 1).length + ")"} + + + + +
+
+
+
+ +
+ +
+ + APC 2 + + {RenderAPCStatus(dataStation?.apc2)} + {dataStation?.apc2?.status === "DISCONNECTED" || + dataStation?.apc2?.status === "TIMEOUT" ? ( - - - -
- -
-
- -
- -
- - APC 2 - - {dataStation?.apc2?.status && - RenderAPCStatus(dataStation?.apc2)} - {dataStation?.apc2?.status === "DISCONNECTED" || - dataStation?.apc2?.status === "TIMEOUT" ? ( -
+ )} + +
+ + + {listOutlet + .filter((el) => el.apc === 2) + .map((outlet, i) => ( + el.name === outlet.name && el.apc === outlet.apc + )?.name + ? "1px solid #0018ff" + : "", + }} onClick={() => { - socket?.emit("control_apc", { - outletNumbers: [], - station: stationAPI, - action: "reconnect", - apcName: "apc_2", - }); - setListOutletSelected([]); - setIsSubmit(true); - setTimeout(() => { - setIsSubmit(false); - }, 5000); + toggleSelect(outlet, i + 1); }} > - - - ) : ( -
- )} - - - - - {listOutlet - .filter((el) => el.apc === 2) - .map((outlet, i) => ( - - el.name === outlet.name && el.apc === outlet.apc - )?.name - ? "1px solid #0018ff" - : "", - }} - onClick={() => { - toggleSelect(outlet, i + 1); + color: outlet.status === "ON" ? "#40c057" : "#f03e3e", }} > - - {findLineByOutlet(outlet) - ? "Line " + findLineByOutlet(outlet)?.lineNumber - : outlet.name} - - - ))} - -
+ + ))} + +
+ - - + + - + -
- -
-
-
-
+ : "Turn Off Selected" + } + // mt={'xs'} + miw={"80px"} + size="xs" + fz={"xs"} + variant="filled" + color="red" + onClick={() => { + socket?.emit("control_apc", { + outletNumbers: listOutletSelected + .filter((el) => el.apc === 2) + .map((el) => el.number), + station: stationAPI, + action: "off", + apcName: "apc_2", + }); + setListOutletSelected([]); + setIsSubmit(true); + setTimeout(() => { + setIsSubmit(false); + }, 5000); + }} + > + {listOutletSelected.filter((el) => el.apc === 2).length === + listOutlet.filter((el) => el.apc === 2).length || + listOutletSelected.filter((el) => el.apc === 2).length === 0 + ? "Turn Off All" + : "Turn Off Selected"} + + + + + + ); }; export const DrawerSwitchControl: React.FC = ({ stationAPI, socket, - open, - onClose, - openedAPC, }) => { const [listPorts, setListPorts] = useState([]); const [dataStation, setDataStation] = useState(stationAPI); @@ -903,438 +848,336 @@ export const DrawerSwitchControl: React.FC = ({ }); }; - return ( - - - Switch Control - - {dataStation?.switch?.status && - RenderAPCStatus(dataStation?.switch)} - {dataStation?.switch?.status === "DISCONNECTED" || - dataStation?.switch?.status === "TIMEOUT" ? ( - - ) : ( - "" - )} -
- -
- - ) : ( -
- Switch Control -
- -
-
- ) - } - size="sm" - position="bottom" + return loading ? ( + - {loading ? ( - + + ) : ( + +
+
- - - ) : ( - - - {listPorts?.length > 0 && ( - - - {listPorts?.map((group, key) => ( - 20 - ? 11 - : group?.length > 0 && group?.length < 4 - ? 1 - : 12 - } - > -
- - - {group[0]?.name.substring(0, 2) || ""} - - - - {group?.length > 0 && - sortedPorts(group)?.map((port, i) => ( - el.name === port.name - )?.name - ? "1px solid #0018ff" - : "", - }} - className={`${ - isSubmit ? classes.isDisabled : "" - }`} - onClick={() => { - toggleSelect(port); - }} - > - - - - {port.name} - - - - ))} - -
-
- ))} -
-
- )} -
-
+ {dataStation?.switch ? RenderAPCStatus(dataStation?.switch) : ""} + + + + + +
+
+ + + {listPorts?.length > 0 && ( + + + {listPorts?.map((group, key) => ( + 20 + ? 11 + : group?.length > 0 && group?.length < 4 + ? 1 + : 12 } - }} - > - {listPortsSelected.length === listPorts.flat().length - ? "Deselect All" - : "Select All"}{" "} - {"(" + listPorts.flat().length + ")"} - - - - -
- -
- )} -
+ > +
+ + + {group[0]?.name.substring(0, 2) || ""} + + + + {group?.length > 0 && + sortedPorts(group)?.map((port, i) => ( + el.name === port.name + )?.name + ? "1px solid #0018ff" + : "", + }} + className={`${isSubmit ? classes.isDisabled : ""}`} + onClick={() => { + toggleSelect(port); + }} + > + + + + {port.name} + + + + ))} + +
+ + ))} + + + )} + + ); }; diff --git a/FRONTEND/src/components/DrawerLogs.tsx b/FRONTEND/src/components/DrawerLogs.tsx index ca342ad..26b1535 100644 --- a/FRONTEND/src/components/DrawerLogs.tsx +++ b/FRONTEND/src/components/DrawerLogs.tsx @@ -177,6 +177,7 @@ function DrawerLogs({