This commit is contained in:
nguyentrungthat 2025-10-28 16:57:15 +07:00
parent 0a0dd559f0
commit cbc8397ea8
8 changed files with 152 additions and 36 deletions

2
BACKEND/.gitignore vendored
View File

@ -23,3 +23,5 @@ yarn-error.log
# Platform specific
.DS_Store
storage/system_logs

View File

@ -4,9 +4,20 @@ import User from '../models/user.js'
export default class AuthController {
// Đăng ký
async register({ request, response }: HttpContext) {
const data = request.only(['email', 'password', 'full_name'])
const user = await User.create(data)
return response.json({ message: 'User created', user })
try {
const data = request.only(['email', 'password', 'full_name'])
const user = await User.query().where('email', data.email).first()
if (user) {
return response.status(401).json({ status: false, message: 'Email is exist' })
}
const newUser = await User.create(data)
return response.json({ status: true, message: 'User created', user: newUser })
} catch (error) {
return response.status(401).json({ status: false, message: 'Invalid credentials' })
}
}
// Đăng nhập
@ -24,11 +35,9 @@ export default class AuthController {
return response.status(401).json({ message: 'Invalid email or password' })
}
// ✅ Nếu dùng token thủ công:
const token = Math.random().toString(36).substring(2) // hoặc JWT nếu bạn cài auth
return response.json({
message: 'Login successful',
user: { id: user.id, email: user.email, token },
user: { id: user.id, email: user.email, fullName: user.fullName },
})
} catch {
return response.status(401).json({ message: 'Invalid credentials' })

View File

@ -1,8 +1,6 @@
import Scenario from '#models/scenario'
import type { HttpContext } from '@adonisjs/core/http'
import { searchRequest } from '../utils/hasPaginationRequest.js'
import db from '@adonisjs/lucid/services/db'
import UserScenarios from '#models/user_scenario'
export default class ScenariosController {
/**
@ -36,7 +34,6 @@ export default class ScenariosController {
async create({ request, response, auth }: HttpContext) {
try {
const payload = await request.all()
const trx = await db.transaction()
try {
const scenario = await Scenario.create(
@ -44,7 +41,7 @@ export default class ScenariosController {
title: payload.title.trim(),
body: JSON.stringify(payload.body),
timeout: payload.timeout,
isReboot: payload.is_reboot,
isReboot: payload.isReboot,
},
{ client: trx }
)

View File

@ -1,5 +1,5 @@
import net from 'node:net'
import { cleanData, sleep } from '../ultils/helper.js'
import { appendLog, cleanData, sleep } from '../ultils/helper.js'
import Scenario from '#models/scenario'
interface LineConfig {
@ -11,6 +11,14 @@ interface LineConfig {
apcName?: string
output: string
status: string
openCLI: boolean
userEmailOpenCLI: string
userOpenCLI: string
}
interface User {
userEmail: string
userName: string
}
export default class LineConnection {
@ -77,6 +85,7 @@ export default class LineConnection {
lineId: id,
data: message,
})
appendLog(cleanData(message), this.config.stationId, this.config.id)
})
this.client.on('error', (err) => {
@ -149,6 +158,11 @@ export default class LineConnection {
}
this.isRunningScript = true
appendLog(
`\n\n---start-scenarios---${Date.now()}---\n---scenario---${script?.title}---${Date.now()}---\n`,
this.config.stationId,
this.config.id
)
const steps = typeof script?.body === 'string' ? JSON.parse(script?.body) : []
let stepIndex = 0
@ -156,6 +170,13 @@ export default class LineConnection {
const timeoutTimer = setTimeout(() => {
this.isRunningScript = false
this.outputBuffer = ''
this.config.output += 'Timeout run scenario'
this.socketIO.emit('line_output', {
stationId: this.config.stationId,
lineId: this.config.id,
data: 'Timeout run scenario',
})
appendLog(`\n---end-scenarios---${Date.now()}---\n`, this.config.stationId, this.config.id)
// reject(new Error('Script timeout'))
}, script.timeout || 300000)
@ -164,11 +185,21 @@ export default class LineConnection {
clearTimeout(timeoutTimer)
this.isRunningScript = false
this.outputBuffer = ''
appendLog(
`\n---end-scenarios---${Date.now()}---\n`,
this.config.stationId,
this.config.id
)
resolve(true)
return
}
const step = steps[index]
appendLog(
`\n---send-command---"${step?.send ?? ''}"---${Date.now()}---\n`,
this.config.stationId,
this.config.id
)
let repeatCount = Number(step.repeat) || 1
const sendCommand = () => {
if (repeatCount <= 0) {
@ -203,4 +234,27 @@ export default class LineConnection {
runStep(stepIndex)
})
}
userOpenCLI(user: User) {
this.config.openCLI = true
this.config.userEmailOpenCLI = user.userEmail
this.config.userOpenCLI = user.userName
this.socketIO.emit('user_open_cli', {
stationId: this.config.stationId,
lineId: this.config.id,
userEmailOpenCLI: user.userEmail,
userOpenCLI: user.userName,
})
}
userCloseCLI() {
this.config.openCLI = false
this.config.userEmailOpenCLI = ''
this.config.userOpenCLI = ''
this.socketIO.emit('user_close_cli', {
stationId: this.config.stationId,
lineId: this.config.id,
userEmailOpenCLI: '',
})
}
}

View File

@ -1,3 +1,6 @@
import fs from 'node:fs'
import path from 'node:path'
/**
* Function to clean up unwanted characters from the output data.
* @param {string} data - The raw data to be cleaned.
@ -16,3 +19,20 @@ export const cleanData = (data: string) => {
export function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
export function appendLog(output: string, stationId: number, lineId: number) {
const date = new Date().toISOString().slice(0, 10).replace(/-/g, '') // YYYYMMDD
const logDir = path.join('storage', 'system_logs')
const logFile = path.join(logDir, `${date}-Station_${stationId}-Line_${lineId}.log`)
// Ensure folder exists
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true })
}
fs.appendFile(logFile, output, (err) => {
if (err) {
console.error('❌ Failed to write log:', err.message)
}
})
}

View File

@ -9,7 +9,7 @@ export default class extends BaseSchema {
table.string('title').notNullable()
table.text('body').notNullable()
table.integer('timeout').notNullable()
table.boolean('isReboot').defaultTo(false)
table.boolean('is_reboot').defaultTo(false)
table.timestamps()
})
}

View File

@ -51,6 +51,7 @@ export class WebSocketIo {
stationMap: Map<number, Station> = new Map()
lineMap: Map<number, LineConnection> = new Map() // key = lineId
lineConnecting: number[] = [] // key = lineId
userConnecting: Map<number, { userId: number; userName: string }> = new Map()
constructor(protected app: ApplicationService) {}
@ -70,8 +71,14 @@ export class WebSocketIo {
})
io.on('connection', (socket: CustomSocket) => {
const { userId, userName } = socket.handshake.auth
console.log('Socket connected:', socket.id)
socket.connectionTime = new Date()
this.userConnecting.set(userId, { userId, userName })
setTimeout(() => {
io.emit('user_connecting', Array.from(this.userConnecting.values()))
}, 200)
setTimeout(() => {
io.to(socket.id).emit(
@ -82,6 +89,10 @@ export class WebSocketIo {
socket.on('disconnect', () => {
console.log(`FE disconnected: ${socket.id}`)
this.userConnecting.delete(userId)
setTimeout(() => {
io.emit('user_connecting', Array.from(this.userConnecting.values()))
}, 200)
})
// FE gửi yêu cầu connect lines
@ -94,8 +105,8 @@ export class WebSocketIo {
const { lineIds, stationId, command } = data
for (const lineId of lineIds) {
const line = this.lineMap.get(lineId)
if (line) {
this.lineConnecting.filter((el) => el !== lineId)
if (line && line.config.status === 'connected') {
this.lineConnecting = this.lineConnecting.filter((el) => el !== lineId)
this.setTimeoutConnect(lineId, line)
line.writeCommand(command)
} else {
@ -107,7 +118,7 @@ export class WebSocketIo {
await this.connectLine(io, [linesData], stationData)
const lineReconnect = this.lineMap.get(lineId)
if (lineReconnect) {
this.lineConnecting.filter((el) => el !== lineId)
this.lineConnecting = this.lineConnecting.filter((el) => el !== lineId)
this.setTimeoutConnect(lineId, lineReconnect)
lineReconnect.writeCommand(command)
}
@ -127,7 +138,7 @@ export class WebSocketIo {
const lineId = data.id
const scenario = data.scenario
const line = this.lineMap.get(lineId)
if (line) {
if (line && line.config.status === 'connected') {
this.setTimeoutConnect(
lineId,
line,
@ -155,9 +166,44 @@ export class WebSocketIo {
}
})
// FE yêu cầu ngắt kết nối 1 station
socket.on('disconnect_station', (stationId) => {
this.disconnectStation(stationId)
socket.on('open_cli', async (data) => {
const { lineId, userEmail, userName: name, stationId } = data
const line = this.lineMap.get(lineId)
if (line) {
line.userOpenCLI({ userEmail, userName: name })
} 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)
const lineReconnect = this.lineMap.get(lineId)
if (lineReconnect) {
lineReconnect.userOpenCLI({ userEmail, userName: name })
}
}
}
})
socket.on('close_cli', async (data) => {
const { lineId, stationId } = data
const line = this.lineMap.get(lineId)
if (line) {
line.userCloseCLI()
} 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)
const lineReconnect = this.lineMap.get(lineId)
if (lineReconnect) {
lineReconnect.userCloseCLI()
}
}
}
})
})
@ -182,6 +228,9 @@ export class WebSocketIo {
apcName: line.apcName,
output: '',
status: '',
openCLI: false,
userEmailOpenCLI: '',
userOpenCLI: '',
},
socket
)
@ -195,22 +244,6 @@ export class WebSocketIo {
}
}
private disconnectStation(stationId: number) {
const station = this.stationMap.get(stationId)
if (!station) return
for (const line of station.lines) {
const conn = this.lineMap.get(line.id)
if (conn) {
conn.disconnect()
this.lineMap.delete(line.id)
}
}
this.stationMap.delete(stationId)
console.log(`🔻 Station ${station.name} disconnected`)
}
private setTimeoutConnect = (lineId: number, lineConn: LineConnection, timeout = 120000) => {
if (this.intervalMap[`${lineId}`]) {
clearInterval(this.intervalMap[`${lineId}`])
@ -218,7 +251,7 @@ export class WebSocketIo {
}
const interval = setInterval(() => {
lineConn.disconnect()
this.lineMap.delete(lineId)
// this.lineMap.delete(lineId)
if (this.intervalMap[`${lineId}`]) {
clearInterval(this.intervalMap[`${lineId}`])
delete this.intervalMap[`${lineId}`]

View File

@ -67,5 +67,6 @@ router
router
.group(() => {
router.post('/login', '#controllers/auth_controller.login')
router.post('/register', '#controllers/auth_controller.register')
})
.prefix('api/auth')