diff --git a/BACKEND/app/services/apc_connection.ts b/BACKEND/app/services/apc_connection.ts index c4375eb..c48d8a3 100644 --- a/BACKEND/app/services/apc_connection.ts +++ b/BACKEND/app/services/apc_connection.ts @@ -123,14 +123,17 @@ class APCController { } } - private _handleError(err: NodeJS.ErrnoException): void { + private async _handleError(err: NodeJS.ErrnoException): Promise { this.output += `\r\n\r\n[ERROR] ${err.message}` this.onData(this.output, this.status) if (err.code === 'ECONNRESET') { - setTimeout(() => { - console.log('[ECONNRESET] Trying reconnect apc:', this.apc_ip) - this.reconnect() - }, 15000) + console.log('[ECONNRESET] Trying reconnect apc:', this.apc_ip) + if (this.retryConnect <= 5) { + await this.sleep(15000) + console.log('Retry connect times', this.retryConnect) + this.retryConnect += 1 + await this.reconnect() + } } } diff --git a/BACKEND/app/services/line_connection.ts b/BACKEND/app/services/line_connection.ts index 5041d30..40f45a3 100644 --- a/BACKEND/app/services/line_connection.ts +++ b/BACKEND/app/services/line_connection.ts @@ -147,7 +147,7 @@ export default class LineConnection { this.socketIO.emit('line_error', { stationId, lineId: id, - error: '\n' + err.message + '\n', + error: '\r\n' + err.message + '\r\n', }) resolve() }) diff --git a/BACKEND/app/services/switch_connection.ts b/BACKEND/app/services/switch_connection.ts index 02dc7f0..da70e27 100644 --- a/BACKEND/app/services/switch_connection.ts +++ b/BACKEND/app/services/switch_connection.ts @@ -214,21 +214,26 @@ export default class SwitchController { } public async restartPort(port: string) { - await this.enterEnableMode() - this._send(`configure terminal`) - // await this._waitFor('(config)#') - await this.sleep(500) - this._send(`interface ${port}`) - // await this._waitFor('(config-if)#') - await this.sleep(500) - this._send(`shutdown`) - // await this._waitFor('(config-if)#') - await this.sleep(500) - await this.sleep(2000) - this._send(`no shutdown`) - // await this._waitFor('(config-if)#') - await this.sleep(500) - this._send(`end`) + // await this.enterEnableMode() + // this._send(`configure terminal`) + // // await this._waitFor('(config)#') + // await this.sleep(500) + // this._send(`interface ${port}`) + // // await this._waitFor('(config-if)#') + // await this.sleep(500) + // this._send(`shutdown`) + // // await this._waitFor('(config-if)#') + // await this.sleep(500) + // await this.sleep(2000) + // this._send(`no shutdown`) + // // await this._waitFor('(config-if)#') + // await this.sleep(500) + // this._send(`end`) + await this.turnPortOff(port) + await this.sleep(300) + await this.getPorts() + await this.sleep(300) + await this.turnPortOn(port) } public async disablePoE(port: string) { diff --git a/BACKEND/providers/socket_io_provider.ts b/BACKEND/providers/socket_io_provider.ts index c708d8a..a6d2328 100644 --- a/BACKEND/providers/socket_io_provider.ts +++ b/BACKEND/providers/socket_io_provider.ts @@ -380,6 +380,13 @@ export class WebSocketIo { portGroups: element.portGroups, status: element.status, }) + socket.emit('switch_ports_status', { + stationId: station.id, + ports: + Array.isArray(element.portGroups) && element.portGroups?.length > 0 + ? element.portGroups?.flat() + : [], + }) } else if (element && element.status !== 'CONNECTED') { await element.reconnect() } else await this.connectSwitch(io, station) @@ -460,6 +467,11 @@ export class WebSocketIo { line.config = { ...line.config, ...update } } }) + + socket.on('clear_line', async (data) => { + const { stationId, lineClear } = data + await this.clearLineBeforeConnect(stationId, lineClear) + }) }) socketServer.listen(SOCKET_IO_PORT, () => { @@ -659,6 +671,10 @@ export class WebSocketIo { portGroups: ports, status, }) + socket.emit('switch_ports_status', { + stationId: station.id, + ports: Array.isArray(ports) && ports?.length > 0 ? ports?.flat() : [], + }) }, }) // Connect và login diff --git a/FRONTEND/src/App.tsx b/FRONTEND/src/App.tsx index 9850c78..c850703 100644 --- a/FRONTEND/src/App.tsx +++ b/FRONTEND/src/App.tsx @@ -411,7 +411,7 @@ function App() { borderRadius: 8, }} > - + {station.lines.length > 8 ? ( { if (selectedLines.length > 0) { selectedLines.forEach((el) => { - if ( - el?.userOpenCLI === user?.userName && - !selectedLines.find((value) => value.id === el?.id) - ) + console.log(el?.userOpenCLI, user?.userName); + if (el?.userOpenCLI === user?.userName) socket?.emit("close_cli", { lineId: el?.id, - stationId: el?.station_id, + stationId: Number(activeTab), }); }); } @@ -532,7 +530,7 @@ function App() { setLoadingTerminal(false); setTimeout(() => { setLoadingTerminal(true); - }, 100); + }, 500); }} setActive={setActiveTab} active={activeTab} diff --git a/FRONTEND/src/components/BottomToolBar.tsx b/FRONTEND/src/components/BottomToolBar.tsx index d44b370..6ad49ed 100644 --- a/FRONTEND/src/components/BottomToolBar.tsx +++ b/FRONTEND/src/components/BottomToolBar.tsx @@ -69,7 +69,7 @@ const BottomToolBar = ({ - + {selectedLines.map((el) => ( - + + + + - + {listOutlet .filter((el) => el.apc === 1) .map((outlet, i) => ( @@ -315,182 +451,6 @@ export const DrawerAPCControl: React.FC = ({ ))} -
- - - - -
@@ -512,7 +472,7 @@ export const DrawerAPCControl: React.FC = ({ onClick={() => { socket?.emit("control_apc", { outletNumbers: [], - station: stationAPI, + station: { ...stationAPI, lines: [] }, action: "reconnect", apcName: "apc_2", }); @@ -528,10 +488,145 @@ export const DrawerAPCControl: React.FC = ({ ) : (
)} + + + + - + {listOutlet .filter((el) => el.apc === 2) .map((outlet, i) => ( @@ -573,183 +668,6 @@ export const DrawerAPCControl: React.FC = ({ ))} -
- - - - -
@@ -768,6 +686,7 @@ export const DrawerSwitchControl: React.FC = ({ >([]); const [isSubmit, setIsSubmit] = useState(false); const [loading, setLoading] = useState(true); + const [checkedActive, setCheckedActive] = useState("all"); useEffect(() => { if (!open) { @@ -776,6 +695,13 @@ export const DrawerSwitchControl: React.FC = ({ } }, [open]); + useEffect(() => { + const value = localStorage.getItem("show-switch-port"); + if (value) { + setCheckedActive(value); + } + }, []); + useEffect(() => { if (loading) setTimeout(() => { @@ -881,6 +807,11 @@ export const DrawerSwitchControl: React.FC = ({ return `${type}${last}`; }; + const changeShowPort = (status: string) => { + localStorage.setItem("show-switch-port", status); + setCheckedActive(status); + }; + return loading ? ( = ({ socket?.emit("control_switch", { ports: [], command: "reconnect", - station: stationAPI, + station: { ...stationAPI, lines: [] }, ip: stationAPI?.switch_control_ip, }); setIsSubmit(true); @@ -1002,7 +933,7 @@ export const DrawerSwitchControl: React.FC = ({ .filter((el) => el.poe !== "ON") .map((el) => el.name), command: "restart", - station: stationAPI, + station: { ...stationAPI, lines: [] }, ip: stationAPI?.switch_control_ip, }); if (listPortsRestart.filter((el) => el.poe === "ON").length > 0) @@ -1011,7 +942,7 @@ export const DrawerSwitchControl: React.FC = ({ .filter((el) => el.poe === "ON") .map((el) => el.name), command: "restart-poe", - station: stationAPI, + station: { ...stationAPI, lines: [] }, ip: stationAPI?.switch_control_ip, }); setListPortsSelected([]); @@ -1058,7 +989,7 @@ export const DrawerSwitchControl: React.FC = ({ .filter((el) => el.poe !== "ON") .map((el) => el.name), command: "on", - station: stationAPI, + station: { ...stationAPI, lines: [] }, ip: stationAPI?.switch_control_ip, }); if (listPortsRestart.filter((el) => el.poe === "ON").length > 0) @@ -1067,7 +998,7 @@ export const DrawerSwitchControl: React.FC = ({ .filter((el) => el.poe === "ON") .map((el) => el.name), command: "on-poe", - station: stationAPI, + station: { ...stationAPI, lines: [] }, ip: stationAPI?.switch_control_ip, }); setListPortsSelected([]); @@ -1114,7 +1045,7 @@ export const DrawerSwitchControl: React.FC = ({ .filter((el) => el.poe !== "ON") .map((el) => el.name), command: "off", - station: stationAPI, + station: { ...stationAPI, lines: [] }, ip: stationAPI?.switch_control_ip, }); if (listPortsRestart.filter((el) => el.poe === "ON").length > 0) @@ -1123,7 +1054,7 @@ export const DrawerSwitchControl: React.FC = ({ .filter((el) => el.poe === "ON") .map((el) => el.name), command: "off-poe", - station: stationAPI, + station: { ...stationAPI, lines: [] }, ip: stationAPI?.switch_control_ip, }); setListPortsSelected([]); @@ -1138,6 +1069,26 @@ export const DrawerSwitchControl: React.FC = ({ ? "Turn Off All" : "Turn Off Selected"} + + changeShowPort("all")} + /> + changeShowPort("on")} + /> + changeShowPort("off")} + /> + @@ -1155,10 +1106,17 @@ export const DrawerSwitchControl: React.FC = ({ span={isLarge ? 11 : isMini ? 1 : 12} > {isLarge ? ( - + {sortedPorts(group) .slice(0, sortedPorts(group).length / 2) + .filter((el) => { + if (checkedActive === "all") return true; + if (checkedActive === "on") + return el.status === "ON"; + if (checkedActive === "off") + return el.status === "OFF"; + }) ?.map((port, i) => ( = ({ sortedPorts(group).length / 2, sortedPorts(group).length ) + .filter((el) => { + if (checkedActive === "all") return true; + if (checkedActive === "on") + return el.status === "ON"; + if (checkedActive === "off") + return el.status === "OFF"; + }) ?.map((port, i) => ( = ({ justifyContent: "center", gap: "10px", overflow: "auto", - maxHeight: "7vh", + maxHeight: "12vh", maxWidth: "70vw", borderLeft: "1px solid #dedede", }} > - {sortedPorts(group)?.map((port, i) => ( - el.name === port.name - )?.name - ? "1px solid #0018ff" - : "", - }} - className={`${isSubmit ? classes.isDisabled : ""}`} - onClick={() => { - toggleSelect(port); - }} - > - { + if (checkedActive === "all") return true; + if (checkedActive === "on") + return el.status === "ON"; + if (checkedActive === "off") + return el.status === "OFF"; + }) + ?.map((port, i) => ( + el.name === port.name + )?.name + ? "1px solid #0018ff" + : "", + }} + className={`${ + isSubmit ? classes.isDisabled : "" + }`} + onClick={() => { + toggleSelect(port); }} > - {/* + {/* = ({ : "#b8b8b8" } /> */} - - {normalizePortName(port.name)} - - - - ))} + + {normalizePortName(port.name)} + + + + ))}
)} diff --git a/FRONTEND/src/components/ModalTerminal.tsx b/FRONTEND/src/components/ModalTerminal.tsx index 59893c8..05d6143 100644 --- a/FRONTEND/src/components/ModalTerminal.tsx +++ b/FRONTEND/src/components/ModalTerminal.tsx @@ -13,6 +13,7 @@ import { } from "@mantine/core"; import type { IScenario, + SwitchPortsProps, TDataTicket, THistoryTicket, TLine, @@ -32,6 +33,15 @@ import axios from "axios"; import { notifications } from "@mantine/notifications"; const apiUrl = import.meta.env.VITE_BACKEND_URL; +const INIT_TICKET = { + description: "", + sn: "", + model: "", + station_id: 0, + history: "", + status: "open", +}; + const ModalTerminal = ({ opened, onClose, @@ -57,31 +67,32 @@ const ModalTerminal = ({ }, []); const [isDisable, setIsDisable] = useState(false); const [isDisableTicket, setIsDisableTicket] = useState(false); - const [latestTicket, setLatestTicket] = useState({ - description: "", - sn: "", - model: "", - station_id: 0, - history: "", - status: "open", - }); - const [dataTicket, setDataTicket] = useState({ - description: "", - sn: "", - model: "", - station_id: 0, - history: "", - status: "open", - }); + const [listPorts, setListPorts] = useState([]); + const [latestTicket, setLatestTicket] = useState(INIT_TICKET); + const [dataTicket, setDataTicket] = useState(INIT_TICKET); useEffect(() => { if (opened && line?.tickets && line?.tickets?.length > 0) { const data = line?.tickets[0]; setLatestTicket(data); setDataTicket({ ...data, description: "" }); + } else { + setLatestTicket(INIT_TICKET); + setDataTicket(INIT_TICKET); } }, [opened, line?.tickets]); + useEffect(() => { + socket?.on("switch_ports_status", (data) => { + if (data.stationId !== stationItem?.id) return; + if (data?.ports && data?.ports.length > 0) + setListPorts(data?.ports || []); + }); + return () => { + socket?.off("switch_ports_status"); + }; + }, [socket, stationItem]); + const renderHistory = (data: TDataTicket) => { const latest = JSON.parse(latestTicket?.history || "[]"); const list = @@ -165,8 +176,8 @@ const ModalTerminal = ({ const payload = { id: dataTicket.id || 0, description: dataTicket.description.trim(), - model: dataTicket.model.trim(), - sn: dataTicket.sn.trim(), + model: line?.inventory?.pid.trim(), + sn: line?.inventory?.sn.trim(), station_id: Number(stationItem?.id), line_id: Number(line?.id), status: "open", @@ -313,6 +324,35 @@ const ModalTerminal = ({ }, 5000); }; + const controlSwitch = (action: string) => { + if (!line?.interface) { + notifications.show({ + title: "Error", + message: "Hasn't config interface", + color: "red", + }); + return; + } + socket?.emit("control_switch", { + ports: [line?.interface], + command: action, + station: { ...stationItem, lines: [] }, + ip: stationItem?.switch_control_ip, + }); + setIsDisable(true); + setTimeout(() => { + setIsDisable(false); + }, 5000); + }; + + const findSwitchPort = (portName: string): SwitchPortsProps | null => { + if (listPorts?.length > 0) { + const port = listPorts.find((el) => el.name === portName); + if (port) return port; + } + return null; + }; + return ( - Warning from test report: + Warning from test report: AI {""} - - - - Internet Connected - - - - - - - +
+ + + + + Internet + + {line?.interface ? ( + findSwitchPort(line?.interface)?.status === "ON" ? ( + + Connected ({line?.interface}) + + ) : ( + + Not Connected ({line?.interface}) + + ) + ) : ( + + Not Connected + + )} + + + + + + + +
- + @@ -505,7 +599,7 @@ const ModalTerminal = ({ } line_status={line?.status || ""} /> - + - + - - - - - + + + + + + + + + + + + @@ -722,7 +831,7 @@ const ModalTerminal = ({ onKeyDown={(event) => { if (event.key === "Enter" && dataTicket.description) { setDataTicket((pre) => ({ ...pre, description: "" })); - if (dataTicket?.status === "closed") { + if (dataTicket?.status === "closed" || !dataTicket.id) { handleCreate(); } else handleUpdate("open"); setIsDisableTicket(true); @@ -747,7 +856,7 @@ const ModalTerminal = ({ - {dataTicket?.status === "closed" ? ( + {dataTicket?.status === "closed" || !dataTicket.id ? (