Update
This commit is contained in:
		
							parent
							
								
									077a2ddc35
								
							
						
					
					
						commit
						ef1d585b61
					
				| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
import net from 'node:net'
 | 
					import net from 'node:net'
 | 
				
			||||||
import { cleanData } from '../ultils/helper.js'
 | 
					import { cleanData } from '../ultils/helper.js'
 | 
				
			||||||
 | 
					import Scenario from '#models/scenario'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface LineConfig {
 | 
					interface LineConfig {
 | 
				
			||||||
  id: number
 | 
					  id: number
 | 
				
			||||||
| 
						 | 
					@ -16,11 +17,17 @@ export default class LineConnection {
 | 
				
			||||||
  public client: net.Socket
 | 
					  public client: net.Socket
 | 
				
			||||||
  public readonly config: LineConfig
 | 
					  public readonly config: LineConfig
 | 
				
			||||||
  public readonly socketIO: any
 | 
					  public readonly socketIO: any
 | 
				
			||||||
 | 
					  private outputBuffer: string
 | 
				
			||||||
 | 
					  private isRunningScript: boolean
 | 
				
			||||||
 | 
					  private connecting: boolean
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(config: LineConfig, socketIO: any) {
 | 
					  constructor(config: LineConfig, socketIO: any) {
 | 
				
			||||||
    this.config = config
 | 
					    this.config = config
 | 
				
			||||||
    this.socketIO = socketIO
 | 
					    this.socketIO = socketIO
 | 
				
			||||||
    this.client = new net.Socket()
 | 
					    this.client = new net.Socket()
 | 
				
			||||||
 | 
					    this.outputBuffer = ''
 | 
				
			||||||
 | 
					    this.isRunningScript = false
 | 
				
			||||||
 | 
					    this.connecting = false
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  connect(timeoutMs = 5000) {
 | 
					  connect(timeoutMs = 5000) {
 | 
				
			||||||
| 
						 | 
					@ -35,7 +42,10 @@ export default class LineConnection {
 | 
				
			||||||
        resolvedOrRejected = true
 | 
					        resolvedOrRejected = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        console.log(`✅ Connected to line ${lineNumber} (${ip}:${port})`)
 | 
					        console.log(`✅ Connected to line ${lineNumber} (${ip}:${port})`)
 | 
				
			||||||
 | 
					        this.connecting = true
 | 
				
			||||||
 | 
					        setTimeout(() => {
 | 
				
			||||||
          this.config.status = 'connected'
 | 
					          this.config.status = 'connected'
 | 
				
			||||||
 | 
					          this.connecting = false
 | 
				
			||||||
          this.socketIO.emit('line_connected', {
 | 
					          this.socketIO.emit('line_connected', {
 | 
				
			||||||
            stationId,
 | 
					            stationId,
 | 
				
			||||||
            lineId: id,
 | 
					            lineId: id,
 | 
				
			||||||
| 
						 | 
					@ -43,10 +53,13 @@ export default class LineConnection {
 | 
				
			||||||
            status: 'connected',
 | 
					            status: 'connected',
 | 
				
			||||||
          })
 | 
					          })
 | 
				
			||||||
          resolve()
 | 
					          resolve()
 | 
				
			||||||
 | 
					        }, 1000)
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.client.on('data', (data) => {
 | 
					      this.client.on('data', (data) => {
 | 
				
			||||||
 | 
					        if (this.connecting) return
 | 
				
			||||||
        let message = data.toString()
 | 
					        let message = data.toString()
 | 
				
			||||||
 | 
					        this.outputBuffer += message
 | 
				
			||||||
        // let output = cleanData(message)
 | 
					        // let output = cleanData(message)
 | 
				
			||||||
        // console.log(`📨 [${this.config.port}] ${message}`)
 | 
					        // console.log(`📨 [${this.config.port}] ${message}`)
 | 
				
			||||||
        // Handle netOutput with backspace support
 | 
					        // Handle netOutput with backspace support
 | 
				
			||||||
| 
						 | 
					@ -74,7 +87,7 @@ export default class LineConnection {
 | 
				
			||||||
        this.socketIO.emit('line_error', {
 | 
					        this.socketIO.emit('line_error', {
 | 
				
			||||||
          stationId,
 | 
					          stationId,
 | 
				
			||||||
          lineId: id,
 | 
					          lineId: id,
 | 
				
			||||||
          error: err.message,
 | 
					          error: err.message + '\r\n',
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        reject(err)
 | 
					        reject(err)
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
| 
						 | 
					@ -101,15 +114,6 @@ export default class LineConnection {
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  sendCommand(cmd: string) {
 | 
					 | 
				
			||||||
    if (this.client.destroyed) {
 | 
					 | 
				
			||||||
      console.log(`⚠️ Cannot send, line ${this.config.lineNumber} is closed`)
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    // console.log(`➡️ [${this.config.apcName}] SEND:`, cmd)
 | 
					 | 
				
			||||||
    this.client.write(`${cmd}\r\n`)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  writeCommand(cmd: string) {
 | 
					  writeCommand(cmd: string) {
 | 
				
			||||||
    if (this.client.destroyed) {
 | 
					    if (this.client.destroyed) {
 | 
				
			||||||
      console.log(`⚠️ Cannot send, line ${this.config.lineNumber} is closed`)
 | 
					      console.log(`⚠️ Cannot send, line ${this.config.lineNumber} is closed`)
 | 
				
			||||||
| 
						 | 
					@ -131,4 +135,75 @@ export default class LineConnection {
 | 
				
			||||||
      console.error('Error closing line:', e)
 | 
					      console.error('Error closing line:', e)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async runScript(script: Scenario) {
 | 
				
			||||||
 | 
					    if (!this.client || this.client.destroyed) {
 | 
				
			||||||
 | 
					      throw new Error('Not connected')
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (this.isRunningScript) {
 | 
				
			||||||
 | 
					      throw new Error('Script already running')
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.isRunningScript = true
 | 
				
			||||||
 | 
					    const steps = typeof script?.body === 'string' ? JSON.parse(script?.body) : []
 | 
				
			||||||
 | 
					    let stepIndex = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
					      const timeoutTimer = setTimeout(() => {
 | 
				
			||||||
 | 
					        this.isRunningScript = false
 | 
				
			||||||
 | 
					        this.outputBuffer = ''
 | 
				
			||||||
 | 
					        reject(new Error('Script timeout'))
 | 
				
			||||||
 | 
					      }, script.timeout || 300000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const runStep = (index: number) => {
 | 
				
			||||||
 | 
					        if (index >= steps.length) {
 | 
				
			||||||
 | 
					          clearTimeout(timeoutTimer)
 | 
				
			||||||
 | 
					          this.isRunningScript = false
 | 
				
			||||||
 | 
					          this.outputBuffer = ''
 | 
				
			||||||
 | 
					          resolve(true)
 | 
				
			||||||
 | 
					          return
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const step = steps[index]
 | 
				
			||||||
 | 
					        let repeatCount = Number(step.repeat) || 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const sendCommand = () => {
 | 
				
			||||||
 | 
					          if (repeatCount <= 0) {
 | 
				
			||||||
 | 
					            // Done → next step
 | 
				
			||||||
 | 
					            this.client.off('data', onOutput)
 | 
				
			||||||
 | 
					            stepIndex++
 | 
				
			||||||
 | 
					            return runStep(stepIndex)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (step.send) {
 | 
				
			||||||
 | 
					            this.writeCommand(step?.send + '\r\n')
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          repeatCount--
 | 
				
			||||||
 | 
					          setTimeout(() => sendCommand(), Number(step?.delay) || 500)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Lắng nghe output cho expect
 | 
				
			||||||
 | 
					        const onOutput = (data: string) => {
 | 
				
			||||||
 | 
					          const output = data.toString()
 | 
				
			||||||
 | 
					          this.outputBuffer += output
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (output.includes(step.expect)) {
 | 
				
			||||||
 | 
					            this.client.off('data', onOutput)
 | 
				
			||||||
 | 
					            setTimeout(() => sendCommand(), Number(step?.delay) || 500)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Nếu expect rỗng → gửi ngay
 | 
				
			||||||
 | 
					        if (!step?.expect || step?.expect.trim() === '') {
 | 
				
			||||||
 | 
					          setTimeout(() => sendCommand(), Number(step?.delay) || 500)
 | 
				
			||||||
 | 
					          return
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.client.on('data', onOutput)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      runStep(stepIndex)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@
 | 
				
			||||||
 * @param {string} data - The raw data to be cleaned.
 | 
					 * @param {string} data - The raw data to be cleaned.
 | 
				
			||||||
 * @returns {string} - The cleaned data.
 | 
					 * @returns {string} - The cleaned data.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export const cleanData = (data) => {
 | 
					export const cleanData = (data: string) => {
 | 
				
			||||||
  return data
 | 
					  return data
 | 
				
			||||||
    .replace(/--More--\s*BS\s*BS\s*BS\s*BS\s*BS\s*BS/g, '')
 | 
					    .replace(/--More--\s*BS\s*BS\s*BS\s*BS\s*BS\s*BS/g, '')
 | 
				
			||||||
    .replace(/\s*--More--\s*/g, '')
 | 
					    .replace(/\s*--More--\s*/g, '')
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,13 +5,7 @@ import { ApplicationService } from '@adonisjs/core/types'
 | 
				
			||||||
import env from '#start/env'
 | 
					import env from '#start/env'
 | 
				
			||||||
import { CustomServer, CustomSocket } from '../app/ultils/types.js'
 | 
					import { CustomServer, CustomSocket } from '../app/ultils/types.js'
 | 
				
			||||||
import Line from '#models/line'
 | 
					import Line from '#models/line'
 | 
				
			||||||
 | 
					import Station from '#models/station'
 | 
				
			||||||
interface Station {
 | 
					 | 
				
			||||||
  id: number
 | 
					 | 
				
			||||||
  name: string
 | 
					 | 
				
			||||||
  ip: string
 | 
					 | 
				
			||||||
  lines: any[]
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class SocketIoProvider {
 | 
					export default class SocketIoProvider {
 | 
				
			||||||
  private static _io: CustomServer
 | 
					  private static _io: CustomServer
 | 
				
			||||||
| 
						 | 
					@ -78,11 +72,12 @@ export class WebSocketIo {
 | 
				
			||||||
      console.log('Socket connected:', socket.id)
 | 
					      console.log('Socket connected:', socket.id)
 | 
				
			||||||
      socket.connectionTime = new Date()
 | 
					      socket.connectionTime = new Date()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const lineConnectionArray: LineConnection[] = Array.from(this.lineMap.values())
 | 
					      setTimeout(() => {
 | 
				
			||||||
        io.to(socket.id).emit(
 | 
					        io.to(socket.id).emit(
 | 
				
			||||||
          'init',
 | 
					          'init',
 | 
				
			||||||
        lineConnectionArray.map((el) => el.config)
 | 
					          Array.from(this.lineMap.values()).map((el) => el.config)
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					      }, 200)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      socket.on('disconnect', () => {
 | 
					      socket.on('disconnect', () => {
 | 
				
			||||||
        console.log(`FE disconnected: ${socket.id}`)
 | 
					        console.log(`FE disconnected: ${socket.id}`)
 | 
				
			||||||
| 
						 | 
					@ -94,13 +89,23 @@ export class WebSocketIo {
 | 
				
			||||||
        await this.connectLine(io, linesData, stationData)
 | 
					        await this.connectLine(io, linesData, stationData)
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      socket.on('write_command_line_from_web', (data) => {
 | 
					      socket.on('write_command_line_from_web', async (data) => {
 | 
				
			||||||
        const { lineIds, stationId, command } = data
 | 
					        const { lineIds, stationId, command } = data
 | 
				
			||||||
        for (const lineId of lineIds) {
 | 
					        for (const lineId of lineIds) {
 | 
				
			||||||
          const line = this.lineMap.get(lineId)
 | 
					          const line = this.lineMap.get(lineId)
 | 
				
			||||||
          if (line) {
 | 
					          if (line) {
 | 
				
			||||||
            this.setTimeoutConnect(lineId, line)
 | 
					            this.setTimeoutConnect(lineId, line)
 | 
				
			||||||
            line.writeCommand(command)
 | 
					            line.writeCommand(command)
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            const linesData = await Line.findBy('id', lineId)
 | 
				
			||||||
 | 
					            const stationData = await Station.findBy('id', stationId)
 | 
				
			||||||
 | 
					            if (linesData && stationData) {
 | 
				
			||||||
 | 
					              await this.connectLine(io, [linesData], stationData)
 | 
				
			||||||
 | 
					              const lineReconnect = this.lineMap.get(lineId)
 | 
				
			||||||
 | 
					              if (lineReconnect) {
 | 
				
			||||||
 | 
					                this.setTimeoutConnect(lineId, lineReconnect)
 | 
				
			||||||
 | 
					                lineReconnect.writeCommand(command)
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
              io.emit('line_disconnected', {
 | 
					              io.emit('line_disconnected', {
 | 
				
			||||||
                stationId,
 | 
					                stationId,
 | 
				
			||||||
| 
						 | 
					@ -110,6 +115,39 @@ export class WebSocketIo {
 | 
				
			||||||
              io.emit('line_error', { lineId, error: 'Line not connected\r\n' })
 | 
					              io.emit('line_error', { lineId, error: 'Line not connected\r\n' })
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      socket.on('run_scenario', async (data) => {
 | 
				
			||||||
 | 
					        const lineId = data.id
 | 
				
			||||||
 | 
					        const scenario = data.scenario
 | 
				
			||||||
 | 
					        const line = this.lineMap.get(lineId)
 | 
				
			||||||
 | 
					        if (line) {
 | 
				
			||||||
 | 
					          this.setTimeoutConnect(
 | 
				
			||||||
 | 
					            lineId,
 | 
				
			||||||
 | 
					            line,
 | 
				
			||||||
 | 
					            scenario?.timeout ? Number(scenario?.timeout) + 120000 : 300000
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					          line.runScript(scenario)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          const linesData = await Line.findBy('id', lineId)
 | 
				
			||||||
 | 
					          const stationData = await Station.findBy('id', data.stationId)
 | 
				
			||||||
 | 
					          if (linesData && stationData) {
 | 
				
			||||||
 | 
					            await this.connectLine(io, [linesData], stationData)
 | 
				
			||||||
 | 
					            const lineReconnect = this.lineMap.get(lineId)
 | 
				
			||||||
 | 
					            if (lineReconnect) {
 | 
				
			||||||
 | 
					              this.setTimeoutConnect(lineId, lineReconnect, 300000)
 | 
				
			||||||
 | 
					              lineReconnect.runScript(scenario)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            io.emit('line_disconnected', {
 | 
				
			||||||
 | 
					              stationId: data.stationId,
 | 
				
			||||||
 | 
					              lineId,
 | 
				
			||||||
 | 
					              status: 'disconnected',
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            io.emit('line_error', { lineId, error: 'Line not connected\r\n' })
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // FE yêu cầu ngắt kết nối 1 station
 | 
					      // FE yêu cầu ngắt kết nối 1 station
 | 
				
			||||||
| 
						 | 
					@ -168,7 +206,7 @@ export class WebSocketIo {
 | 
				
			||||||
    console.log(`🔻 Station ${station.name} disconnected`)
 | 
					    console.log(`🔻 Station ${station.name} disconnected`)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private setTimeoutConnect = (lineId: number, lineConn: LineConnection) => {
 | 
					  private setTimeoutConnect = (lineId: number, lineConn: LineConnection, timeout = 120000) => {
 | 
				
			||||||
    if (this.intervalMap[`${lineId}`]) {
 | 
					    if (this.intervalMap[`${lineId}`]) {
 | 
				
			||||||
      clearInterval(this.intervalMap[`${lineId}`])
 | 
					      clearInterval(this.intervalMap[`${lineId}`])
 | 
				
			||||||
      delete this.intervalMap[`${lineId}`]
 | 
					      delete this.intervalMap[`${lineId}`]
 | 
				
			||||||
| 
						 | 
					@ -176,7 +214,11 @@ export class WebSocketIo {
 | 
				
			||||||
    const interval = setInterval(() => {
 | 
					    const interval = setInterval(() => {
 | 
				
			||||||
      lineConn.disconnect()
 | 
					      lineConn.disconnect()
 | 
				
			||||||
      this.lineMap.delete(lineId)
 | 
					      this.lineMap.delete(lineId)
 | 
				
			||||||
    }, 120000)
 | 
					      if (this.intervalMap[`${lineId}`]) {
 | 
				
			||||||
 | 
					        clearInterval(this.intervalMap[`${lineId}`])
 | 
				
			||||||
 | 
					        delete this.intervalMap[`${lineId}`]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }, timeout)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.intervalMap[`${lineId}`] = interval
 | 
					    this.intervalMap[`${lineId}`] = interval
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,11 +17,12 @@ import {
 | 
				
			||||||
  Button,
 | 
					  Button,
 | 
				
			||||||
  ActionIcon,
 | 
					  ActionIcon,
 | 
				
			||||||
} from "@mantine/core";
 | 
					} from "@mantine/core";
 | 
				
			||||||
import type { TLine, TStation } from "./untils/types";
 | 
					import type { LineConfig, TLine, TStation } from "./untils/types";
 | 
				
			||||||
import axios from "axios";
 | 
					import axios from "axios";
 | 
				
			||||||
import CardLine from "./components/CardLine";
 | 
					import CardLine from "./components/CardLine";
 | 
				
			||||||
import { IconEdit, IconSettingsPlus } from "@tabler/icons-react";
 | 
					import { IconEdit, IconSettingsPlus } from "@tabler/icons-react";
 | 
				
			||||||
import { SocketProvider, useSocket } from "./context/SocketContext";
 | 
					import { SocketProvider, useSocket } from "./context/SocketContext";
 | 
				
			||||||
 | 
					import { ButtonDPELP } from "./components/ButtonAction";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const apiUrl = import.meta.env.VITE_BACKEND_URL;
 | 
					const apiUrl = import.meta.env.VITE_BACKEND_URL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,6 +44,7 @@ export function App() {
 | 
				
			||||||
    setControlsRefs(controlsRefs);
 | 
					    setControlsRefs(controlsRefs);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  const [showBottomShadow, setShowBottomShadow] = useState(false);
 | 
					  const [showBottomShadow, setShowBottomShadow] = useState(false);
 | 
				
			||||||
 | 
					  const [isDisable, setIsDisable] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // function get list station
 | 
					  // function get list station
 | 
				
			||||||
  const getStation = async () => {
 | 
					  const getStation = async () => {
 | 
				
			||||||
| 
						 | 
					@ -67,35 +69,27 @@ export function App() {
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    if (!socket || !stations?.length) return;
 | 
					    if (!socket || !stations?.length) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const updateStatus = (data: any) => {
 | 
					    socket.on("line_connected", updateStatus);
 | 
				
			||||||
 | 
					    socket.on("line_disconnected", updateStatus);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // ✅ cleanup on unmount or when socket changes
 | 
				
			||||||
 | 
					    return () => {
 | 
				
			||||||
 | 
					      socket.off("line_connected");
 | 
				
			||||||
 | 
					      socket.off("line_disconnected");
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }, [socket, stations]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const updateStatus = (data: LineConfig) => {
 | 
				
			||||||
    const line = getLine(data.lineId, data.stationId);
 | 
					    const line = getLine(data.lineId, data.stationId);
 | 
				
			||||||
    if (line) {
 | 
					    if (line) {
 | 
				
			||||||
      updateValueLineStation(line, "status", data.status);
 | 
					      updateValueLineStation(line, "status", data.status);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    socket.on("line_connected", updateStatus);
 | 
					  const updateValueLineStation = <K extends keyof TLine>(
 | 
				
			||||||
    socket.on("line_disconnected", updateStatus);
 | 
					 | 
				
			||||||
    socket?.on("init", (data) => {
 | 
					 | 
				
			||||||
      if (Array.isArray(data)) {
 | 
					 | 
				
			||||||
        data.forEach((value) => {
 | 
					 | 
				
			||||||
          updateStatus(value);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // ✅ cleanup on unmount or when socket changes
 | 
					 | 
				
			||||||
    return () => {
 | 
					 | 
				
			||||||
      socket.off("line_connected");
 | 
					 | 
				
			||||||
      socket.off("line_disconnected");
 | 
					 | 
				
			||||||
      socket.off("line_disconnected");
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }, [socket, stations]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const updateValueLineStation = (
 | 
					 | 
				
			||||||
    currentLine: TLine,
 | 
					    currentLine: TLine,
 | 
				
			||||||
    field: string,
 | 
					    field: K,
 | 
				
			||||||
    value: any
 | 
					    value: TLine[K]
 | 
				
			||||||
  ) => {
 | 
					  ) => {
 | 
				
			||||||
    setStations((el) =>
 | 
					    setStations((el) =>
 | 
				
			||||||
      el?.map((station: TStation) =>
 | 
					      el?.map((station: TStation) =>
 | 
				
			||||||
| 
						 | 
					@ -201,6 +195,7 @@ export function App() {
 | 
				
			||||||
                          line={line}
 | 
					                          line={line}
 | 
				
			||||||
                          selectedLines={selectedLines}
 | 
					                          selectedLines={selectedLines}
 | 
				
			||||||
                          setSelectedLines={setSelectedLines}
 | 
					                          setSelectedLines={setSelectedLines}
 | 
				
			||||||
 | 
					                          updateStatus={updateStatus}
 | 
				
			||||||
                        />
 | 
					                        />
 | 
				
			||||||
                      ))}
 | 
					                      ))}
 | 
				
			||||||
                    </Flex>
 | 
					                    </Flex>
 | 
				
			||||||
| 
						 | 
					@ -223,7 +218,7 @@ export function App() {
 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                  <Button
 | 
					                  <Button
 | 
				
			||||||
                    variant="filled"
 | 
					                    variant="filled"
 | 
				
			||||||
                    style={{ height: "30px", width: "120px" }}
 | 
					                    style={{ height: "30px", width: "100px" }}
 | 
				
			||||||
                    onClick={() => {
 | 
					                    onClick={() => {
 | 
				
			||||||
                      if (selectedLines.length !== station.lines.length)
 | 
					                      if (selectedLines.length !== station.lines.length)
 | 
				
			||||||
                        setSelectedLines(station.lines);
 | 
					                        setSelectedLines(station.lines);
 | 
				
			||||||
| 
						 | 
					@ -240,7 +235,7 @@ export function App() {
 | 
				
			||||||
                        .length === 0
 | 
					                        .length === 0
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    variant="outline"
 | 
					                    variant="outline"
 | 
				
			||||||
                    style={{ height: "30px", width: "120px" }}
 | 
					                    style={{ height: "30px", width: "100px" }}
 | 
				
			||||||
                    onClick={() => {
 | 
					                    onClick={() => {
 | 
				
			||||||
                      const lines = selectedLines.filter(
 | 
					                      const lines = selectedLines.filter(
 | 
				
			||||||
                        (el) => el.status !== "connected"
 | 
					                        (el) => el.status !== "connected"
 | 
				
			||||||
| 
						 | 
					@ -254,6 +249,18 @@ export function App() {
 | 
				
			||||||
                  >
 | 
					                  >
 | 
				
			||||||
                    Connect
 | 
					                    Connect
 | 
				
			||||||
                  </Button>
 | 
					                  </Button>
 | 
				
			||||||
 | 
					                  <ButtonDPELP
 | 
				
			||||||
 | 
					                    socket={socket}
 | 
				
			||||||
 | 
					                    selectedLines={selectedLines}
 | 
				
			||||||
 | 
					                    isDisable={isDisable || selectedLines.length === 0}
 | 
				
			||||||
 | 
					                    onClick={() => {
 | 
				
			||||||
 | 
					                      setSelectedLines([]);
 | 
				
			||||||
 | 
					                      setIsDisable(true);
 | 
				
			||||||
 | 
					                      setTimeout(() => {
 | 
				
			||||||
 | 
					                        setIsDisable(false);
 | 
				
			||||||
 | 
					                      }, 10000);
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                  />
 | 
				
			||||||
                </Flex>
 | 
					                </Flex>
 | 
				
			||||||
              </Grid.Col>
 | 
					              </Grid.Col>
 | 
				
			||||||
            </Grid>
 | 
					            </Grid>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,132 @@
 | 
				
			||||||
 | 
					import type { Socket } from "socket.io-client";
 | 
				
			||||||
 | 
					import type { TLine } from "../untils/types";
 | 
				
			||||||
 | 
					import { Button } from "@mantine/core";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ButtonDPELP = ({
 | 
				
			||||||
 | 
					  socket,
 | 
				
			||||||
 | 
					  isDisable,
 | 
				
			||||||
 | 
					  onClick,
 | 
				
			||||||
 | 
					  selectedLines,
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					  socket: Socket | null;
 | 
				
			||||||
 | 
					  isDisable: boolean;
 | 
				
			||||||
 | 
					  onClick: () => void;
 | 
				
			||||||
 | 
					  selectedLines: TLine[];
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Button
 | 
				
			||||||
 | 
					      disabled={isDisable}
 | 
				
			||||||
 | 
					      miw={"100px"}
 | 
				
			||||||
 | 
					      // radius="lg"
 | 
				
			||||||
 | 
					      h={"24px"}
 | 
				
			||||||
 | 
					      mr={"5px"}
 | 
				
			||||||
 | 
					      variant="filled"
 | 
				
			||||||
 | 
					      color="#00a164"
 | 
				
			||||||
 | 
					      onClick={async () => {
 | 
				
			||||||
 | 
					        onClick();
 | 
				
			||||||
 | 
					        selectedLines?.forEach((el) => {
 | 
				
			||||||
 | 
					          const body = [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              expect: "",
 | 
				
			||||||
 | 
					              send: "     show diag",
 | 
				
			||||||
 | 
					              delay: "1000",
 | 
				
			||||||
 | 
					              repeat: "1",
 | 
				
			||||||
 | 
					              note: "",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              expect: "",
 | 
				
			||||||
 | 
					              send: "       ",
 | 
				
			||||||
 | 
					              delay: "1000",
 | 
				
			||||||
 | 
					              repeat: "1",
 | 
				
			||||||
 | 
					              note: "",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              expect: "",
 | 
				
			||||||
 | 
					              send: "     show post",
 | 
				
			||||||
 | 
					              delay: "1000",
 | 
				
			||||||
 | 
					              repeat: "1",
 | 
				
			||||||
 | 
					              note: "",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              expect: "",
 | 
				
			||||||
 | 
					              send: "       ",
 | 
				
			||||||
 | 
					              delay: "1000",
 | 
				
			||||||
 | 
					              repeat: "1",
 | 
				
			||||||
 | 
					              note: "",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              expect: "",
 | 
				
			||||||
 | 
					              send: "     show env",
 | 
				
			||||||
 | 
					              delay: "1000",
 | 
				
			||||||
 | 
					              repeat: "1",
 | 
				
			||||||
 | 
					              note: "",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              expect: "",
 | 
				
			||||||
 | 
					              send: "       ",
 | 
				
			||||||
 | 
					              delay: "1000",
 | 
				
			||||||
 | 
					              repeat: "1",
 | 
				
			||||||
 | 
					              note: "",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              expect: "",
 | 
				
			||||||
 | 
					              send: "     show license",
 | 
				
			||||||
 | 
					              delay: "1000",
 | 
				
			||||||
 | 
					              repeat: "1",
 | 
				
			||||||
 | 
					              note: "",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              expect: "",
 | 
				
			||||||
 | 
					              send: "       ",
 | 
				
			||||||
 | 
					              delay: "1000",
 | 
				
			||||||
 | 
					              repeat: "1",
 | 
				
			||||||
 | 
					              note: "",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              expect: "",
 | 
				
			||||||
 | 
					              send: "     show log",
 | 
				
			||||||
 | 
					              delay: "1000",
 | 
				
			||||||
 | 
					              repeat: "1",
 | 
				
			||||||
 | 
					              note: "",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              expect: "",
 | 
				
			||||||
 | 
					              send: "       ",
 | 
				
			||||||
 | 
					              delay: "1000",
 | 
				
			||||||
 | 
					              repeat: "2",
 | 
				
			||||||
 | 
					              note: "",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              expect: "",
 | 
				
			||||||
 | 
					              send: "     show platform",
 | 
				
			||||||
 | 
					              delay: "1000",
 | 
				
			||||||
 | 
					              repeat: "1",
 | 
				
			||||||
 | 
					              note: "",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              expect: "",
 | 
				
			||||||
 | 
					              send: "       ",
 | 
				
			||||||
 | 
					              delay: "7000",
 | 
				
			||||||
 | 
					              repeat: "15",
 | 
				
			||||||
 | 
					              note: "",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ];
 | 
				
			||||||
 | 
					          socket?.emit(
 | 
				
			||||||
 | 
					            "run_scenario",
 | 
				
			||||||
 | 
					            Object.assign(el, {
 | 
				
			||||||
 | 
					              scenario: {
 | 
				
			||||||
 | 
					                id: 0,
 | 
				
			||||||
 | 
					                is_reboot: 0,
 | 
				
			||||||
 | 
					                title: "DPELP",
 | 
				
			||||||
 | 
					                timeout: 300000,
 | 
				
			||||||
 | 
					                body: JSON.stringify(body),
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      DPELP
 | 
				
			||||||
 | 
					    </Button>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
import { Card, Text, Box, Flex } from "@mantine/core";
 | 
					import { Card, Text, Box, Flex } from "@mantine/core";
 | 
				
			||||||
import type { TLine, TStation } from "../untils/types";
 | 
					import type { LineConfig, TLine, TStation } from "../untils/types";
 | 
				
			||||||
import classes from "./Component.module.css";
 | 
					import classes from "./Component.module.css";
 | 
				
			||||||
import TerminalCLI from "./TerminalXTerm";
 | 
					import TerminalCLI from "./TerminalXTerm";
 | 
				
			||||||
import type { Socket } from "socket.io-client";
 | 
					import type { Socket } from "socket.io-client";
 | 
				
			||||||
| 
						 | 
					@ -12,12 +12,14 @@ const CardLine = ({
 | 
				
			||||||
  setSelectedLines,
 | 
					  setSelectedLines,
 | 
				
			||||||
  socket,
 | 
					  socket,
 | 
				
			||||||
  stationItem,
 | 
					  stationItem,
 | 
				
			||||||
 | 
					  updateStatus,
 | 
				
			||||||
}: {
 | 
					}: {
 | 
				
			||||||
  line: TLine;
 | 
					  line: TLine;
 | 
				
			||||||
  selectedLines: TLine[];
 | 
					  selectedLines: TLine[];
 | 
				
			||||||
  setSelectedLines: (lines: React.SetStateAction<TLine[]>) => void;
 | 
					  setSelectedLines: (lines: React.SetStateAction<TLine[]>) => void;
 | 
				
			||||||
  socket: Socket | null;
 | 
					  socket: Socket | null;
 | 
				
			||||||
  stationItem: TStation;
 | 
					  stationItem: TStation;
 | 
				
			||||||
 | 
					  updateStatus: (value: LineConfig) => void;
 | 
				
			||||||
}) => {
 | 
					}) => {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Card
 | 
					    <Card
 | 
				
			||||||
| 
						 | 
					@ -81,6 +83,7 @@ const CardLine = ({
 | 
				
			||||||
              paddingBottom: "0px",
 | 
					              paddingBottom: "0px",
 | 
				
			||||||
            }}
 | 
					            }}
 | 
				
			||||||
            onDoubleClick={() => {}}
 | 
					            onDoubleClick={() => {}}
 | 
				
			||||||
 | 
					            updateStatus={updateStatus}
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        </Box>
 | 
					        </Box>
 | 
				
			||||||
      </Flex>
 | 
					      </Flex>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@ import "xterm/css/xterm.css";
 | 
				
			||||||
import { FitAddon } from "@xterm/addon-fit";
 | 
					import { FitAddon } from "@xterm/addon-fit";
 | 
				
			||||||
import { SOCKET_EVENTS } from "../untils/constanst";
 | 
					import { SOCKET_EVENTS } from "../untils/constanst";
 | 
				
			||||||
import type { Socket } from "socket.io-client";
 | 
					import type { Socket } from "socket.io-client";
 | 
				
			||||||
 | 
					import type { LineConfig } from "../untils/types";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface TerminalCLIProps {
 | 
					interface TerminalCLIProps {
 | 
				
			||||||
  socket: Socket | null;
 | 
					  socket: Socket | null;
 | 
				
			||||||
| 
						 | 
					@ -24,6 +25,7 @@ interface TerminalCLIProps {
 | 
				
			||||||
  onDoubleClick?: () => void;
 | 
					  onDoubleClick?: () => void;
 | 
				
			||||||
  fontSize?: number;
 | 
					  fontSize?: number;
 | 
				
			||||||
  miniSize?: boolean;
 | 
					  miniSize?: boolean;
 | 
				
			||||||
 | 
					  updateStatus: (value: LineConfig) => void;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const TerminalCLI: React.FC<TerminalCLIProps> = ({
 | 
					const TerminalCLI: React.FC<TerminalCLIProps> = ({
 | 
				
			||||||
| 
						 | 
					@ -33,11 +35,11 @@ const TerminalCLI: React.FC<TerminalCLIProps> = ({
 | 
				
			||||||
  station_id,
 | 
					  station_id,
 | 
				
			||||||
  cliOpened = false,
 | 
					  cliOpened = false,
 | 
				
			||||||
  isDisabled = false,
 | 
					  isDisabled = false,
 | 
				
			||||||
  line_status = "",
 | 
					 | 
				
			||||||
  customStyle = {},
 | 
					  customStyle = {},
 | 
				
			||||||
  onDoubleClick = () => {},
 | 
					  onDoubleClick = () => {},
 | 
				
			||||||
  fontSize = 14,
 | 
					  fontSize = 14,
 | 
				
			||||||
  miniSize = false,
 | 
					  miniSize = false,
 | 
				
			||||||
 | 
					  updateStatus,
 | 
				
			||||||
}) => {
 | 
					}) => {
 | 
				
			||||||
  const xtermRef = useRef<HTMLDivElement>(null);
 | 
					  const xtermRef = useRef<HTMLDivElement>(null);
 | 
				
			||||||
  const terminal = useRef<Terminal>(null);
 | 
					  const terminal = useRef<Terminal>(null);
 | 
				
			||||||
| 
						 | 
					@ -148,6 +150,7 @@ const TerminalCLI: React.FC<TerminalCLIProps> = ({
 | 
				
			||||||
        data.forEach((value) => {
 | 
					        data.forEach((value) => {
 | 
				
			||||||
          if (value?.id === line_id && terminal.current) {
 | 
					          if (value?.id === line_id && terminal.current) {
 | 
				
			||||||
            terminal.current?.write(value.output);
 | 
					            terminal.current?.write(value.output);
 | 
				
			||||||
 | 
					            updateStatus({ ...value, lineId: value.id });
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -126,3 +126,15 @@ export type SwitchPortsProps = {
 | 
				
			||||||
  status: string;
 | 
					  status: string;
 | 
				
			||||||
  poe: string;
 | 
					  poe: string;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type LineConfig = {
 | 
				
			||||||
 | 
					  id: number;
 | 
				
			||||||
 | 
					  lineId: number;
 | 
				
			||||||
 | 
					  port: number;
 | 
				
			||||||
 | 
					  lineNumber: number;
 | 
				
			||||||
 | 
					  ip: string;
 | 
				
			||||||
 | 
					  stationId: number;
 | 
				
			||||||
 | 
					  apcName?: string;
 | 
				
			||||||
 | 
					  output: string;
 | 
				
			||||||
 | 
					  status: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue