Update FE
This commit is contained in:
parent
ef1d585b61
commit
dea4d2b804
|
|
@ -91,9 +91,10 @@ export default class ScenariosController {
|
||||||
*/
|
*/
|
||||||
async update({ request, response, auth }: HttpContext) {
|
async update({ request, response, auth }: HttpContext) {
|
||||||
try {
|
try {
|
||||||
const scenarioId = request.param('id')
|
const scenarioId = request.body().id
|
||||||
const payload = await request.all()
|
const payload = request.body()
|
||||||
const scenario = await Scenario.findOrFail(scenarioId)
|
const scenario = await Scenario.find(scenarioId)
|
||||||
|
if (!scenario) return response.status(404).json({ message: 'Scenario not found' })
|
||||||
payload.body = JSON.stringify(payload.body)
|
payload.body = JSON.stringify(payload.body)
|
||||||
scenario.merge(payload)
|
scenario.merge(payload)
|
||||||
await scenario.save()
|
await scenario.save()
|
||||||
|
|
@ -113,7 +114,7 @@ export default class ScenariosController {
|
||||||
*/
|
*/
|
||||||
async delete({ request, response }: HttpContext) {
|
async delete({ request, response }: HttpContext) {
|
||||||
try {
|
try {
|
||||||
const scenarioId = request.param('id')
|
const scenarioId = request.body().id
|
||||||
const scenario = await Scenario.findOrFail(scenarioId)
|
const scenario = await Scenario.findOrFail(scenarioId)
|
||||||
|
|
||||||
if (!scenario) {
|
if (!scenario) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import type { HttpContext } from '@adonisjs/core/http'
|
import type { HttpContext } from '@adonisjs/core/http'
|
||||||
import Station from '#models/station'
|
import Station from '#models/station'
|
||||||
|
import Line from '#models/line'
|
||||||
|
|
||||||
export default class StationsController {
|
export default class StationsController {
|
||||||
public async index({}: HttpContext) {
|
public async index({}: HttpContext) {
|
||||||
|
|
@ -8,6 +9,8 @@ export default class StationsController {
|
||||||
|
|
||||||
public async store({ request, response }: HttpContext) {
|
public async store({ request, response }: HttpContext) {
|
||||||
let payload = request.only(Array.from(Station.$columnsDefinitions.keys()))
|
let payload = request.only(Array.from(Station.$columnsDefinitions.keys()))
|
||||||
|
let lines: Line[] = request.body().lines || []
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const stationName = await Station.findBy('name', payload.name)
|
const stationName = await Station.findBy('name', payload.name)
|
||||||
if (stationName) return response.status(400).json({ message: 'Station name exist!' })
|
if (stationName) return response.status(400).json({ message: 'Station name exist!' })
|
||||||
|
|
@ -16,10 +19,33 @@ export default class StationsController {
|
||||||
if (stationIP) return response.status(400).json({ message: 'Ip of station is exist!' })
|
if (stationIP) return response.status(400).json({ message: 'Ip of station is exist!' })
|
||||||
|
|
||||||
const station = await Station.create(payload)
|
const station = await Station.create(payload)
|
||||||
|
|
||||||
|
const newLines: Line[] = []
|
||||||
|
if (lines && Array.isArray(lines)) {
|
||||||
|
lines.forEach(async (line) => {
|
||||||
|
if (line.id) {
|
||||||
|
const value = await Line.find(line.id)
|
||||||
|
if (value) {
|
||||||
|
Object.assign(value, line)
|
||||||
|
await value.save()
|
||||||
|
newLines.push(value)
|
||||||
|
} else {
|
||||||
|
const value1 = await Line.create({ ...line, stationId: station.id })
|
||||||
|
newLines.push(value1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const value2 = await Line.create({ ...line, stationId: station.id })
|
||||||
|
newLines.push(value2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const resStation = await Station.query().where('id', station.id).preload('lines')
|
||||||
|
|
||||||
return response.created({
|
return response.created({
|
||||||
status: true,
|
status: true,
|
||||||
message: 'Station created successfully',
|
message: 'Station created successfully',
|
||||||
data: station,
|
data: resStation.map((el) => ({ ...el.$original, lines: newLines })),
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return response.badRequest({ error: error, message: 'Station create failed', status: false })
|
return response.badRequest({ error: error, message: 'Station create failed', status: false })
|
||||||
|
|
@ -36,6 +62,8 @@ export default class StationsController {
|
||||||
(f) => f !== 'created_at' && f !== 'updated_at'
|
(f) => f !== 'created_at' && f !== 'updated_at'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
let lines: Line[] = request.body().lines || []
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const station = await Station.find(request.body().id)
|
const station = await Station.find(request.body().id)
|
||||||
|
|
||||||
|
|
@ -48,7 +76,21 @@ export default class StationsController {
|
||||||
|
|
||||||
await station.save()
|
await station.save()
|
||||||
|
|
||||||
return response.ok({ status: true, message: 'Station update successfully', data: station })
|
if (lines && Array.isArray(lines)) {
|
||||||
|
lines.forEach(async (line) => {
|
||||||
|
if (line.id) {
|
||||||
|
const value = await Line.find(line.id)
|
||||||
|
if (value) {
|
||||||
|
Object.assign(value, line)
|
||||||
|
await value.save()
|
||||||
|
} else await Line.create({ ...line, stationId: station.id })
|
||||||
|
} else await Line.create({ ...line, stationId: station.id })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const resStation = await Station.query().where('id', station.id).preload('lines')
|
||||||
|
|
||||||
|
return response.ok({ status: true, message: 'Station update successfully', data: resStation })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return response.badRequest({ error: error, message: 'Station update failed', status: false })
|
return response.badRequest({ error: error, message: 'Station update failed', status: false })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import net from 'node:net'
|
import net from 'node:net'
|
||||||
import { cleanData } from '../ultils/helper.js'
|
import { cleanData, sleep } from '../ultils/helper.js'
|
||||||
import Scenario from '#models/scenario'
|
import Scenario from '#models/scenario'
|
||||||
|
|
||||||
interface LineConfig {
|
interface LineConfig {
|
||||||
|
|
@ -59,7 +59,7 @@ export default class LineConnection {
|
||||||
this.client.on('data', (data) => {
|
this.client.on('data', (data) => {
|
||||||
if (this.connecting) return
|
if (this.connecting) return
|
||||||
let message = data.toString()
|
let message = data.toString()
|
||||||
this.outputBuffer += message
|
if (this.isRunningScript) 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
|
||||||
|
|
@ -109,7 +109,7 @@ export default class LineConnection {
|
||||||
|
|
||||||
console.log(`⏳ Connection timeout line ${lineNumber}`)
|
console.log(`⏳ Connection timeout line ${lineNumber}`)
|
||||||
this.client.destroy()
|
this.client.destroy()
|
||||||
reject(new Error('Connection timeout'))
|
// reject(new Error('Connection timeout'))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -138,10 +138,14 @@ export default class LineConnection {
|
||||||
|
|
||||||
async runScript(script: Scenario) {
|
async runScript(script: Scenario) {
|
||||||
if (!this.client || this.client.destroyed) {
|
if (!this.client || this.client.destroyed) {
|
||||||
throw new Error('Not connected')
|
console.log('Not connected')
|
||||||
|
this.isRunningScript = false
|
||||||
|
this.outputBuffer = ''
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if (this.isRunningScript) {
|
if (this.isRunningScript) {
|
||||||
throw new Error('Script already running')
|
console.log('Script already running')
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isRunningScript = true
|
this.isRunningScript = true
|
||||||
|
|
@ -152,10 +156,10 @@ export default class LineConnection {
|
||||||
const timeoutTimer = setTimeout(() => {
|
const timeoutTimer = setTimeout(() => {
|
||||||
this.isRunningScript = false
|
this.isRunningScript = false
|
||||||
this.outputBuffer = ''
|
this.outputBuffer = ''
|
||||||
reject(new Error('Script timeout'))
|
// reject(new Error('Script timeout'))
|
||||||
}, script.timeout || 300000)
|
}, script.timeout || 300000)
|
||||||
|
|
||||||
const runStep = (index: number) => {
|
const runStep = async (index: number) => {
|
||||||
if (index >= steps.length) {
|
if (index >= steps.length) {
|
||||||
clearTimeout(timeoutTimer)
|
clearTimeout(timeoutTimer)
|
||||||
this.isRunningScript = false
|
this.isRunningScript = false
|
||||||
|
|
@ -166,11 +170,9 @@ export default class LineConnection {
|
||||||
|
|
||||||
const step = steps[index]
|
const step = steps[index]
|
||||||
let repeatCount = Number(step.repeat) || 1
|
let repeatCount = Number(step.repeat) || 1
|
||||||
|
|
||||||
const sendCommand = () => {
|
const sendCommand = () => {
|
||||||
if (repeatCount <= 0) {
|
if (repeatCount <= 0) {
|
||||||
// Done → next step
|
// Done → next step
|
||||||
this.client.off('data', onOutput)
|
|
||||||
stepIndex++
|
stepIndex++
|
||||||
return runStep(stepIndex)
|
return runStep(stepIndex)
|
||||||
}
|
}
|
||||||
|
|
@ -183,24 +185,19 @@ export default class LineConnection {
|
||||||
setTimeout(() => sendCommand(), Number(step?.delay) || 500)
|
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
|
// Nếu expect rỗng → gửi ngay
|
||||||
if (!step?.expect || step?.expect.trim() === '') {
|
if (!step?.expect || step?.expect.trim() === '') {
|
||||||
setTimeout(() => sendCommand(), Number(step?.delay) || 500)
|
setTimeout(() => sendCommand(), Number(step?.delay) || 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.client.on('data', onOutput)
|
while (this.outputBuffer) {
|
||||||
|
await sleep(200)
|
||||||
|
if (this.outputBuffer.includes(step.expect)) {
|
||||||
|
this.outputBuffer = ''
|
||||||
|
setTimeout(() => sendCommand(), Number(step?.delay) || 500)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
runStep(stepIndex)
|
runStep(stepIndex)
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,7 @@ export const cleanData = (data: string) => {
|
||||||
.replace(/[^\x20-\x7E\r\n]/g, '') // Remove non-printable characters
|
.replace(/[^\x20-\x7E\r\n]/g, '') // Remove non-printable characters
|
||||||
// .replace(/\r\n/g, '\n')
|
// .replace(/\r\n/g, '\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function sleep(ms: number) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export default class extends BaseSchema {
|
||||||
table.integer('line_clear')
|
table.integer('line_clear')
|
||||||
table.integer('outlet')
|
table.integer('outlet')
|
||||||
table.integer('station_id').unsigned().references('id').inTable('stations')
|
table.integer('station_id').unsigned().references('id').inTable('stations')
|
||||||
table.string('apc_name').notNullable()
|
table.string('apc_name')
|
||||||
table.timestamps()
|
table.timestamps()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ export default class extends BaseSchema {
|
||||||
this.schema.createTable(this.tableName, (table) => {
|
this.schema.createTable(this.tableName, (table) => {
|
||||||
table.increments('id')
|
table.increments('id')
|
||||||
table.string('title').notNullable()
|
table.string('title').notNullable()
|
||||||
table.string('name').notNullable()
|
|
||||||
table.text('body').notNullable()
|
table.text('body').notNullable()
|
||||||
table.integer('timeout').notNullable()
|
table.integer('timeout').notNullable()
|
||||||
table.boolean('isReboot').defaultTo(false)
|
table.boolean('isReboot').defaultTo(false)
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ export class WebSocketIo {
|
||||||
intervalMap: { [key: string]: NodeJS.Timeout } = {}
|
intervalMap: { [key: string]: NodeJS.Timeout } = {}
|
||||||
stationMap: Map<number, Station> = new Map()
|
stationMap: Map<number, Station> = new Map()
|
||||||
lineMap: Map<number, LineConnection> = new Map() // key = lineId
|
lineMap: Map<number, LineConnection> = new Map() // key = lineId
|
||||||
|
lineConnecting: number[] = [] // key = lineId
|
||||||
|
|
||||||
constructor(protected app: ApplicationService) {}
|
constructor(protected app: ApplicationService) {}
|
||||||
|
|
||||||
|
|
@ -94,15 +95,19 @@ export class WebSocketIo {
|
||||||
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.lineConnecting.filter((el) => el !== lineId)
|
||||||
this.setTimeoutConnect(lineId, line)
|
this.setTimeoutConnect(lineId, line)
|
||||||
line.writeCommand(command)
|
line.writeCommand(command)
|
||||||
} else {
|
} else {
|
||||||
|
if (this.lineConnecting.includes(lineId)) continue
|
||||||
const linesData = await Line.findBy('id', lineId)
|
const linesData = await Line.findBy('id', lineId)
|
||||||
const stationData = await Station.findBy('id', stationId)
|
const stationData = await Station.findBy('id', stationId)
|
||||||
if (linesData && stationData) {
|
if (linesData && stationData) {
|
||||||
|
this.lineConnecting.push(lineId)
|
||||||
await this.connectLine(io, [linesData], stationData)
|
await this.connectLine(io, [linesData], stationData)
|
||||||
const lineReconnect = this.lineMap.get(lineId)
|
const lineReconnect = this.lineMap.get(lineId)
|
||||||
if (lineReconnect) {
|
if (lineReconnect) {
|
||||||
|
this.lineConnecting.filter((el) => el !== lineId)
|
||||||
this.setTimeoutConnect(lineId, lineReconnect)
|
this.setTimeoutConnect(lineId, lineReconnect)
|
||||||
lineReconnect.writeCommand(command)
|
lineReconnect.writeCommand(command)
|
||||||
}
|
}
|
||||||
|
|
@ -131,7 +136,7 @@ export class WebSocketIo {
|
||||||
line.runScript(scenario)
|
line.runScript(scenario)
|
||||||
} else {
|
} else {
|
||||||
const linesData = await Line.findBy('id', lineId)
|
const linesData = await Line.findBy('id', lineId)
|
||||||
const stationData = await Station.findBy('id', data.stationId)
|
const stationData = await Station.findBy('id', data.station_id)
|
||||||
if (linesData && stationData) {
|
if (linesData && stationData) {
|
||||||
await this.connectLine(io, [linesData], stationData)
|
await this.connectLine(io, [linesData], stationData)
|
||||||
const lineReconnect = this.lineMap.get(lineId)
|
const lineReconnect = this.lineMap.get(lineId)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="./public/icon-ATC-removebg.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Automation Test</title>
|
<title>Automation Test</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mantine/core": "^8.3.5",
|
"@mantine/core": "^8.3.5",
|
||||||
"@mantine/dates": "^8.3.5",
|
"@mantine/dates": "^8.3.5",
|
||||||
|
"@mantine/form": "^8.3.5",
|
||||||
|
"@mantine/hooks": "^8.3.5",
|
||||||
"@mantine/notifications": "^8.3.5",
|
"@mantine/notifications": "^8.3.5",
|
||||||
"@tabler/icons-react": "^3.35.0",
|
"@tabler/icons-react": "^3.35.0",
|
||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
|
|
@ -1114,12 +1116,24 @@
|
||||||
"react-dom": "^18.x || ^19.x"
|
"react-dom": "^18.x || ^19.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mantine/form": {
|
||||||
|
"version": "8.3.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mantine/form/-/form-8.3.5.tgz",
|
||||||
|
"integrity": "sha512-i9UFiHtO1dlrJXZkquyt+71YcNNxPPSkIcJCRp7k0Tif7bPqWK2xijPDEXzqvA53YvMvEMoqaQCEQLVmH7Esdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"klona": "^2.0.6"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.x || ^19.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@mantine/hooks": {
|
"node_modules/@mantine/hooks": {
|
||||||
"version": "8.3.5",
|
"version": "8.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.3.5.tgz",
|
||||||
"integrity": "sha512-0Wf08eWLKi3WkKlxnV1W5vfuN6wcvAV2VbhQlOy0R9nrWorGTtonQF6qqBE3PnJFYF1/ZE+HkYZQ/Dr7DmYSMQ==",
|
"integrity": "sha512-0Wf08eWLKi3WkKlxnV1W5vfuN6wcvAV2VbhQlOy0R9nrWorGTtonQF6qqBE3PnJFYF1/ZE+HkYZQ/Dr7DmYSMQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^18.x || ^19.x"
|
"react": "^18.x || ^19.x"
|
||||||
}
|
}
|
||||||
|
|
@ -2631,7 +2645,6 @@
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/fast-glob": {
|
"node_modules/fast-glob": {
|
||||||
|
|
@ -3115,6 +3128,15 @@
|
||||||
"json-buffer": "3.0.1"
|
"json-buffer": "3.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/klona": {
|
||||||
|
"version": "2.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz",
|
||||||
|
"integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/levn": {
|
"node_modules/levn": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mantine/core": "^8.3.5",
|
"@mantine/core": "^8.3.5",
|
||||||
"@mantine/dates": "^8.3.5",
|
"@mantine/dates": "^8.3.5",
|
||||||
|
"@mantine/form": "^8.3.5",
|
||||||
|
"@mantine/hooks": "^8.3.5",
|
||||||
"@mantine/notifications": "^8.3.5",
|
"@mantine/notifications": "^8.3.5",
|
||||||
"@tabler/icons-react": "^3.35.0",
|
"@tabler/icons-react": "^3.35.0",
|
||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 85 KiB |
|
|
@ -4,7 +4,7 @@ import "@mantine/notifications/styles.css";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import classes from "./App.module.css";
|
import classes from "./App.module.css";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { Suspense, useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Tabs,
|
Tabs,
|
||||||
Text,
|
Text,
|
||||||
|
|
@ -16,20 +16,24 @@ import {
|
||||||
ScrollArea,
|
ScrollArea,
|
||||||
Button,
|
Button,
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
|
LoadingOverlay,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import type { LineConfig, TLine, TStation } from "./untils/types";
|
import type { IScenario, 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";
|
import { ButtonDPELP, ButtonScenario } from "./components/ButtonAction";
|
||||||
|
import StationSetting from "./components/FormAddEdit";
|
||||||
|
import DrawerScenario from "./components/DrawerScenario";
|
||||||
|
import { Notifications } from "@mantine/notifications";
|
||||||
|
|
||||||
const apiUrl = import.meta.env.VITE_BACKEND_URL;
|
const apiUrl = import.meta.env.VITE_BACKEND_URL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main Component
|
* Main Component
|
||||||
*/
|
*/
|
||||||
export function App() {
|
function App() {
|
||||||
document.title = "Automation Test";
|
document.title = "Automation Test";
|
||||||
const { socket } = useSocket();
|
const { socket } = useSocket();
|
||||||
const [stations, setStations] = useState<TStation[]>([]);
|
const [stations, setStations] = useState<TStation[]>([]);
|
||||||
|
|
@ -45,6 +49,10 @@ export function App() {
|
||||||
};
|
};
|
||||||
const [showBottomShadow, setShowBottomShadow] = useState(false);
|
const [showBottomShadow, setShowBottomShadow] = useState(false);
|
||||||
const [isDisable, setIsDisable] = useState(false);
|
const [isDisable, setIsDisable] = useState(false);
|
||||||
|
const [isOpenAddStation, setIsOpenAddStation] = useState(false);
|
||||||
|
const [isEditStation, setIsEditStation] = useState(false);
|
||||||
|
const [stationEdit, setStationEdit] = useState<TStation | undefined>();
|
||||||
|
const [scenarios, setScenarios] = useState<IScenario[]>([]);
|
||||||
|
|
||||||
// function get list station
|
// function get list station
|
||||||
const getStation = async () => {
|
const getStation = async () => {
|
||||||
|
|
@ -62,8 +70,23 @@ export function App() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// function get list station
|
||||||
|
const getScenarios = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(apiUrl + "api/scenarios");
|
||||||
|
if (response.data.status) {
|
||||||
|
if (Array.isArray(response.data.data.data)) {
|
||||||
|
setScenarios(response.data.data.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error get station", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getStation();
|
getStation();
|
||||||
|
getScenarios();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -121,7 +144,6 @@ export function App() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container py="xl" w={"100%"} style={{ maxWidth: "100%" }}>
|
<Container py="xl" w={"100%"} style={{ maxWidth: "100%" }}>
|
||||||
{/* Tabs (Top Bar) */}
|
|
||||||
<Tabs
|
<Tabs
|
||||||
value={activeTab}
|
value={activeTab}
|
||||||
onChange={(id) => setActiveTab(id?.toString() || "0")}
|
onChange={(id) => setActiveTab(id?.toString() || "0")}
|
||||||
|
|
@ -146,14 +168,35 @@ export function App() {
|
||||||
className={classes.indicator}
|
className={classes.indicator}
|
||||||
/>
|
/>
|
||||||
<Flex gap={"sm"}>
|
<Flex gap={"sm"}>
|
||||||
<ActionIcon title="Add Station" variant="outline" color="green">
|
{Number(activeTab) ? (
|
||||||
<IconSettingsPlus />
|
<ActionIcon
|
||||||
</ActionIcon>
|
title="Edit Station"
|
||||||
{Number(activeTab) && (
|
variant="outline"
|
||||||
<ActionIcon title="Edit Station" variant="outline">
|
onClick={() => {
|
||||||
|
setStationEdit(
|
||||||
|
stations.find((el) => el.id === Number(activeTab))
|
||||||
|
);
|
||||||
|
setIsOpenAddStation(true);
|
||||||
|
setIsEditStation(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<IconEdit />
|
<IconEdit />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
)}
|
)}
|
||||||
|
<ActionIcon
|
||||||
|
title="Add Station"
|
||||||
|
variant="outline"
|
||||||
|
color="green"
|
||||||
|
onClick={() => {
|
||||||
|
setIsOpenAddStation(true);
|
||||||
|
setIsEditStation(false);
|
||||||
|
setStationEdit(undefined);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconSettingsPlus />
|
||||||
|
</ActionIcon>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
|
|
||||||
|
|
@ -249,6 +292,11 @@ export function App() {
|
||||||
>
|
>
|
||||||
Connect
|
Connect
|
||||||
</Button>
|
</Button>
|
||||||
|
<hr style={{ width: "100%" }} />
|
||||||
|
<DrawerScenario
|
||||||
|
scenarios={scenarios}
|
||||||
|
setScenarios={setScenarios}
|
||||||
|
/>
|
||||||
<ButtonDPELP
|
<ButtonDPELP
|
||||||
socket={socket}
|
socket={socket}
|
||||||
selectedLines={selectedLines}
|
selectedLines={selectedLines}
|
||||||
|
|
@ -261,12 +309,42 @@ export function App() {
|
||||||
}, 10000);
|
}, 10000);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{scenarios.map((el) => (
|
||||||
|
<ButtonScenario
|
||||||
|
socket={socket}
|
||||||
|
selectedLines={selectedLines}
|
||||||
|
isDisable={isDisable || selectedLines.length === 0}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedLines([]);
|
||||||
|
setIsDisable(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsDisable(false);
|
||||||
|
}, 10000);
|
||||||
|
}}
|
||||||
|
scenario={el}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
|
<StationSetting
|
||||||
|
dataStation={stationEdit}
|
||||||
|
isOpen={isOpenAddStation}
|
||||||
|
onClose={() => {
|
||||||
|
setIsOpenAddStation(false);
|
||||||
|
setIsEditStation(false);
|
||||||
|
setStationEdit(undefined);
|
||||||
|
}}
|
||||||
|
isEdit={isEditStation}
|
||||||
|
setStations={setStations}
|
||||||
|
setActiveTab={() =>
|
||||||
|
setActiveTab(stations.length ? stations[0]?.id.toString() : "0")
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -275,7 +353,18 @@ export default function Main() {
|
||||||
return (
|
return (
|
||||||
<MantineProvider>
|
<MantineProvider>
|
||||||
<SocketProvider>
|
<SocketProvider>
|
||||||
<App />
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
<LoadingOverlay
|
||||||
|
visible={true}
|
||||||
|
zIndex={1000}
|
||||||
|
overlayProps={{ radius: "sm", blur: 1 }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Notifications position="top-right" autoClose={5000} />
|
||||||
|
<App />
|
||||||
|
</Suspense>
|
||||||
</SocketProvider>
|
</SocketProvider>
|
||||||
</MantineProvider>
|
</MantineProvider>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import type { Socket } from "socket.io-client";
|
import type { Socket } from "socket.io-client";
|
||||||
import type { TLine } from "../untils/types";
|
import type { IScenario, TLine } from "../untils/types";
|
||||||
import { Button } from "@mantine/core";
|
import { Button } from "@mantine/core";
|
||||||
|
import classes from "./Component.module.css";
|
||||||
|
|
||||||
export const ButtonDPELP = ({
|
export const ButtonDPELP = ({
|
||||||
socket,
|
socket,
|
||||||
|
|
@ -18,7 +19,7 @@ export const ButtonDPELP = ({
|
||||||
disabled={isDisable}
|
disabled={isDisable}
|
||||||
miw={"100px"}
|
miw={"100px"}
|
||||||
// radius="lg"
|
// radius="lg"
|
||||||
h={"24px"}
|
h={"28px"}
|
||||||
mr={"5px"}
|
mr={"5px"}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
color="#00a164"
|
color="#00a164"
|
||||||
|
|
@ -130,3 +131,42 @@ export const ButtonDPELP = ({
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ButtonScenario = ({
|
||||||
|
socket,
|
||||||
|
isDisable,
|
||||||
|
onClick,
|
||||||
|
selectedLines,
|
||||||
|
scenario,
|
||||||
|
}: {
|
||||||
|
socket: Socket | null;
|
||||||
|
isDisable: boolean;
|
||||||
|
onClick: () => void;
|
||||||
|
selectedLines: TLine[];
|
||||||
|
scenario: IScenario;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
disabled={isDisable}
|
||||||
|
miw={"100px"}
|
||||||
|
style={{ minHeight: "24px", height: "auto" }}
|
||||||
|
mr={"5px"}
|
||||||
|
variant="outline"
|
||||||
|
color="#00a164"
|
||||||
|
className={classes.buttonScenario}
|
||||||
|
onClick={async () => {
|
||||||
|
onClick();
|
||||||
|
selectedLines?.forEach((el) => {
|
||||||
|
socket?.emit(
|
||||||
|
"run_scenario",
|
||||||
|
Object.assign(el, {
|
||||||
|
scenario: scenario,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{scenario.title}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ const CardLine = ({
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<Text fw={600} style={{ display: "flex", gap: "4px" }}>
|
<Text fw={600} style={{ display: "flex", gap: "4px" }}>
|
||||||
Line {line.lineNumber} - {line.port}{" "}
|
Line: {line.lineNumber || line.line_number} - {line.port}{" "}
|
||||||
{line.status === "connected" && (
|
{line.status === "connected" && (
|
||||||
<IconCircleCheckFilled color="green" />
|
<IconCircleCheckFilled color="green" />
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -14,3 +14,7 @@
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.buttonScenario :global(.mantine-Button-label) {
|
||||||
|
white-space: normal !important;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { Box, Button, Group, Modal, Text } from "@mantine/core";
|
||||||
|
import { useState } from "react";
|
||||||
|
const DialogConfirm = ({
|
||||||
|
opened,
|
||||||
|
close,
|
||||||
|
message,
|
||||||
|
handle,
|
||||||
|
}: {
|
||||||
|
opened: boolean;
|
||||||
|
close: () => void;
|
||||||
|
message: string;
|
||||||
|
handle: () => void;
|
||||||
|
centered?: boolean;
|
||||||
|
bottom?: boolean;
|
||||||
|
}) => {
|
||||||
|
const [disableBtn, setDisableBtn] = useState(false);
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Modal
|
||||||
|
style={{ position: "absolute", left: 0, border: "solid 2px #ff6c6b" }}
|
||||||
|
title="Confirm"
|
||||||
|
opened={opened}
|
||||||
|
centered
|
||||||
|
withCloseButton
|
||||||
|
shadow="md"
|
||||||
|
onClose={close}
|
||||||
|
size="xs"
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
size="sm"
|
||||||
|
mb={"xl"}
|
||||||
|
fw={700}
|
||||||
|
c={"#ff6c6b"}
|
||||||
|
style={{ textAlign: "center", fontSize: "18px" }}
|
||||||
|
>
|
||||||
|
{message}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Group style={{ display: "flex", justifyContent: "center" }}>
|
||||||
|
<Button
|
||||||
|
variant="gradient"
|
||||||
|
gradient={{ from: "pink", to: "red", deg: 90 }}
|
||||||
|
size="xs"
|
||||||
|
disabled={disableBtn}
|
||||||
|
onClick={async () => {
|
||||||
|
setDisableBtn(true);
|
||||||
|
await handle();
|
||||||
|
setDisableBtn(false);
|
||||||
|
close();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Yes
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="gradient"
|
||||||
|
size="xs"
|
||||||
|
onClick={async () => {
|
||||||
|
close();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
No
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Modal>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DialogConfirm;
|
||||||
|
|
@ -0,0 +1,344 @@
|
||||||
|
import { useDisclosure } from "@mantine/hooks";
|
||||||
|
import {
|
||||||
|
Drawer,
|
||||||
|
ActionIcon,
|
||||||
|
Box,
|
||||||
|
ScrollArea,
|
||||||
|
Table,
|
||||||
|
Grid,
|
||||||
|
TextInput,
|
||||||
|
Button,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { IconSettingsPlus } from "@tabler/icons-react";
|
||||||
|
import TableRows from "./Scenario/TableRows";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "@mantine/form";
|
||||||
|
import DialogConfirm from "./DialogConfirm";
|
||||||
|
import type { IBodyScenario, IScenario } from "../untils/types";
|
||||||
|
import classes from "./Component.module.css";
|
||||||
|
import axios from "axios";
|
||||||
|
import { notifications } from "@mantine/notifications";
|
||||||
|
const apiUrl = import.meta.env.VITE_BACKEND_URL;
|
||||||
|
|
||||||
|
function DrawerScenario({
|
||||||
|
scenarios,
|
||||||
|
setScenarios,
|
||||||
|
}: {
|
||||||
|
scenarios: IScenario[];
|
||||||
|
setScenarios: (value: React.SetStateAction<IScenario[]>) => void;
|
||||||
|
}) {
|
||||||
|
const [opened, { open, close }] = useDisclosure(false);
|
||||||
|
const [isEdit, setIsEdit] = useState(false);
|
||||||
|
const [openConfirm, setOpenConfirm] = useState<boolean>(false);
|
||||||
|
const [isSubmit, setIsSubmit] = useState<boolean>(false);
|
||||||
|
const [dataScenario, setDataScenario] = useState<IScenario>();
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
initialValues: {
|
||||||
|
title: "",
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
expect: "",
|
||||||
|
send: "",
|
||||||
|
delay: "0",
|
||||||
|
repeat: "1",
|
||||||
|
note: "",
|
||||||
|
},
|
||||||
|
] as IBodyScenario[],
|
||||||
|
timeout: "30000",
|
||||||
|
is_reboot: false,
|
||||||
|
},
|
||||||
|
validate: {
|
||||||
|
title: (value) => {
|
||||||
|
if (!value) return "Title is required";
|
||||||
|
if (value.length > 100) return "The title cannot exceed 100 characters";
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
body: (value) => {
|
||||||
|
if (value.length === 0) return "The body is required";
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
timeout: (value) => {
|
||||||
|
if (!value) return "Title is required";
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const addRowUnder = (index: number) => {
|
||||||
|
const newBody = [...form.values.body];
|
||||||
|
newBody.splice(index + 1, 0, {
|
||||||
|
expect: "",
|
||||||
|
send: "",
|
||||||
|
delay: "0",
|
||||||
|
repeat: "1",
|
||||||
|
note: "",
|
||||||
|
});
|
||||||
|
form.setFieldValue("body", newBody);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteRow = (index: number) => {
|
||||||
|
const newBody = [...form.values.body];
|
||||||
|
newBody.splice(index, 1);
|
||||||
|
form.setFieldValue("body", newBody);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
setIsSubmit(true);
|
||||||
|
try {
|
||||||
|
const body = form.values.body.map((el: IBodyScenario) => ({
|
||||||
|
...el,
|
||||||
|
delay: Number(el.delay),
|
||||||
|
repeat: Number(el.repeat),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
...form.values,
|
||||||
|
body: body,
|
||||||
|
timeout: Number(form.values.timeout),
|
||||||
|
};
|
||||||
|
const url = isEdit ? "api/scenarios/update" : "api/scenarios/create";
|
||||||
|
const res = await axios.post(
|
||||||
|
apiUrl + url,
|
||||||
|
isEdit ? { ...payload, id: dataScenario?.id } : payload
|
||||||
|
);
|
||||||
|
if (res.data.status === true) {
|
||||||
|
const scenario = res.data.data;
|
||||||
|
setScenarios((pre) =>
|
||||||
|
isEdit
|
||||||
|
? pre.map((el) =>
|
||||||
|
el.id === scenario.id ? { ...el, ...scenario } : el
|
||||||
|
)
|
||||||
|
: [...pre, scenario]
|
||||||
|
);
|
||||||
|
notifications.show({
|
||||||
|
title: "Success",
|
||||||
|
message: res.data.message,
|
||||||
|
color: "green",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
setIsSubmit(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post(apiUrl + "api/scenarios/delete", {
|
||||||
|
id: dataScenario?.id,
|
||||||
|
});
|
||||||
|
if (response.data.status) {
|
||||||
|
setScenarios((pre) => pre.filter((el) => el.id !== dataScenario?.id));
|
||||||
|
notifications.show({
|
||||||
|
title: "Success",
|
||||||
|
message: response.data.message,
|
||||||
|
color: "green",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
notifications.show({
|
||||||
|
title: "Error",
|
||||||
|
message: "Error delete scenario",
|
||||||
|
color: "red",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!opened) {
|
||||||
|
setIsEdit(false);
|
||||||
|
setIsSubmit(false);
|
||||||
|
setDataScenario(undefined);
|
||||||
|
form.reset();
|
||||||
|
}
|
||||||
|
}, [opened]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Drawer
|
||||||
|
size={"70%"}
|
||||||
|
position="right"
|
||||||
|
style={{ position: "absolute", left: 0 }}
|
||||||
|
offset={8}
|
||||||
|
radius="md"
|
||||||
|
opened={opened}
|
||||||
|
onClose={close}
|
||||||
|
title={isEdit ? "Edit Scenarios" : "Add Scenarios"}
|
||||||
|
>
|
||||||
|
<Grid>
|
||||||
|
<Grid.Col span={2} style={{ borderRight: "1px solid #ccc" }}>
|
||||||
|
{scenarios.map((scenario) => (
|
||||||
|
<Button
|
||||||
|
disabled={isSubmit}
|
||||||
|
className={classes.buttonScenario}
|
||||||
|
key={scenario.id}
|
||||||
|
miw={"100px"}
|
||||||
|
mb={"6px"}
|
||||||
|
style={{ minHeight: "24px" }}
|
||||||
|
mr={"5px"}
|
||||||
|
variant={
|
||||||
|
dataScenario && dataScenario?.id === scenario.id
|
||||||
|
? "filled"
|
||||||
|
: "outline"
|
||||||
|
}
|
||||||
|
onClick={async () => {
|
||||||
|
if (dataScenario?.id === scenario.id) {
|
||||||
|
setIsEdit(false);
|
||||||
|
setDataScenario(undefined);
|
||||||
|
form.reset();
|
||||||
|
} else {
|
||||||
|
setIsEdit(true);
|
||||||
|
setDataScenario(scenario);
|
||||||
|
form.setFieldValue("title", scenario.title);
|
||||||
|
form.setFieldValue("timeout", scenario.timeout.toString());
|
||||||
|
form.setFieldValue("body", JSON.parse(scenario.body));
|
||||||
|
form.setFieldValue("is_reboot", scenario.is_reboot);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{scenario.title}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col span={10}>
|
||||||
|
<Box>
|
||||||
|
<Grid>
|
||||||
|
<Grid.Col span={4}>
|
||||||
|
<TextInput
|
||||||
|
label="Title"
|
||||||
|
placeholder="Scenario title"
|
||||||
|
value={form.values.title}
|
||||||
|
error={form.errors.title}
|
||||||
|
onChange={(e) =>
|
||||||
|
form.setFieldValue("title", e.target.value)
|
||||||
|
}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col span={2}>
|
||||||
|
<TextInput
|
||||||
|
label="Timeout (ms)"
|
||||||
|
placeholder="Timeout (ms)"
|
||||||
|
value={form.values.timeout}
|
||||||
|
error={form.errors.timeout}
|
||||||
|
onChange={(e) =>
|
||||||
|
form.setFieldValue("timeout", e.target.value)
|
||||||
|
}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col span={6}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "end",
|
||||||
|
gap: "10px",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
height: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isEdit && (
|
||||||
|
<Button
|
||||||
|
disabled={isSubmit}
|
||||||
|
style={{ height: "30px" }}
|
||||||
|
color="red"
|
||||||
|
onClick={() => {
|
||||||
|
setOpenConfirm(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
disabled={isSubmit}
|
||||||
|
style={{ height: "30px" }}
|
||||||
|
color="green"
|
||||||
|
onClick={() => {
|
||||||
|
handleSave();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Grid.Col>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
<hr style={{ width: "100%" }} />
|
||||||
|
<Box>
|
||||||
|
<ScrollArea h={500} style={{ marginBottom: "20px" }}>
|
||||||
|
<Table
|
||||||
|
stickyHeader
|
||||||
|
stickyHeaderOffset={-1}
|
||||||
|
striped
|
||||||
|
highlightOnHover
|
||||||
|
withRowBorders={true}
|
||||||
|
withTableBorder={true}
|
||||||
|
withColumnBorders={true}
|
||||||
|
>
|
||||||
|
<Table.Thead style={{ zIndex: 100 }}>
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Th>#</Table.Th>
|
||||||
|
<Table.Th>
|
||||||
|
{/* <span style={{ marginLeft: '30px' }}>Expect</span> */}
|
||||||
|
Expect
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th>Send</Table.Th>
|
||||||
|
<Table.Th w={130}>Delay(ms)</Table.Th>
|
||||||
|
<Table.Th w={100}>Repeat</Table.Th>
|
||||||
|
<Table.Th></Table.Th>
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody id="tbody-table">
|
||||||
|
{form.values.body.map(
|
||||||
|
(element: IBodyScenario, i: number) => (
|
||||||
|
<TableRows
|
||||||
|
key={i}
|
||||||
|
addRowUnder={addRowUnder}
|
||||||
|
deleteRow={deleteRow}
|
||||||
|
element={element}
|
||||||
|
form={form}
|
||||||
|
i={i}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
</ScrollArea>
|
||||||
|
</Box>
|
||||||
|
</Grid.Col>
|
||||||
|
</Grid>
|
||||||
|
</Drawer>
|
||||||
|
|
||||||
|
<ActionIcon
|
||||||
|
title="Add Scenario"
|
||||||
|
variant="outline"
|
||||||
|
color="green"
|
||||||
|
onClick={() => {
|
||||||
|
open();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconSettingsPlus />
|
||||||
|
</ActionIcon>
|
||||||
|
|
||||||
|
<DialogConfirm
|
||||||
|
opened={openConfirm}
|
||||||
|
close={() => {
|
||||||
|
setOpenConfirm(false);
|
||||||
|
}}
|
||||||
|
message={"Are you sure delete this station?"}
|
||||||
|
handle={() => {
|
||||||
|
setOpenConfirm(false);
|
||||||
|
handleDelete();
|
||||||
|
close();
|
||||||
|
}}
|
||||||
|
centered={true}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DrawerScenario;
|
||||||
|
|
@ -0,0 +1,663 @@
|
||||||
|
import {
|
||||||
|
ActionIcon,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Flex,
|
||||||
|
Group,
|
||||||
|
Modal,
|
||||||
|
NumberInput,
|
||||||
|
PasswordInput,
|
||||||
|
Select,
|
||||||
|
Table,
|
||||||
|
TextInput,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { useForm } from "@mantine/form";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import type { TLine, TStation } from "../untils/types";
|
||||||
|
import DialogConfirm from "./DialogConfirm";
|
||||||
|
import axios from "axios";
|
||||||
|
import { notifications } from "@mantine/notifications";
|
||||||
|
import { IconInputX } from "@tabler/icons-react";
|
||||||
|
|
||||||
|
const apiUrl = import.meta.env.VITE_BACKEND_URL;
|
||||||
|
|
||||||
|
const lineInit = {
|
||||||
|
port: 0,
|
||||||
|
lineNumber: 0,
|
||||||
|
lineClear: 0,
|
||||||
|
station_id: 0,
|
||||||
|
apc_name: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const StationSetting = ({
|
||||||
|
isOpen,
|
||||||
|
isEdit,
|
||||||
|
onClose,
|
||||||
|
dataStation,
|
||||||
|
setStations,
|
||||||
|
setActiveTab,
|
||||||
|
}: {
|
||||||
|
isOpen: boolean;
|
||||||
|
isEdit: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
dataStation?: TStation;
|
||||||
|
setStations: (value: React.SetStateAction<TStation[]>) => void;
|
||||||
|
setActiveTab: () => void;
|
||||||
|
}) => {
|
||||||
|
const [lines, setLines] = useState<TLine[]>([lineInit]);
|
||||||
|
const [openConfirm, setOpenConfirm] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const ipRegex =
|
||||||
|
/(\b25[0-5]|\b2[0-4][0-9]|\b1[0-9]{2}|\b[1-9]?[0-9])\.(\b25[0-5]|\b2[0-4][0-9]|\b1[0-9]{2}|\b[1-9]?[0-9])\.(\b25[0-5]|\b2[0-4][0-9]|\b1[0-9]{2}|\b[1-9]?[0-9])\.(\b25[0-5]|\b2[0-4][0-9]|\b1[0-9]{2}|\b[1-9]?[0-9])\b/g;
|
||||||
|
const form = useForm<TStation>({
|
||||||
|
initialValues: dataStation,
|
||||||
|
validate: (values: TStation) => ({
|
||||||
|
ip: !ipRegex.test(values.ip) ? "IP address invalid" : null,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (dataStation) {
|
||||||
|
form.setFieldValue("name", dataStation.name);
|
||||||
|
form.setFieldValue("ip", dataStation.ip);
|
||||||
|
form.setFieldValue("port", dataStation.port);
|
||||||
|
form.setFieldValue("netmask", dataStation.netmask);
|
||||||
|
form.setFieldValue("network", dataStation.network);
|
||||||
|
form.setFieldValue("gateway", dataStation.gateway);
|
||||||
|
form.setFieldValue("tftp_ip", dataStation.tftp_ip);
|
||||||
|
form.setFieldValue("apc_1_ip", dataStation.apc_1_ip);
|
||||||
|
form.setFieldValue("apc_1_port", dataStation.apc_1_port);
|
||||||
|
form.setFieldValue("apc_1_username", dataStation.apc_1_username);
|
||||||
|
form.setFieldValue("apc_1_password", dataStation.apc_1_password);
|
||||||
|
form.setFieldValue("apc_2_ip", dataStation.apc_2_ip);
|
||||||
|
form.setFieldValue("apc_2_port", dataStation.apc_2_port);
|
||||||
|
form.setFieldValue("apc_2_username", dataStation.apc_2_username);
|
||||||
|
form.setFieldValue("apc_2_password", dataStation.apc_2_password);
|
||||||
|
form.setFieldValue("switch_control_ip", dataStation.switch_control_ip);
|
||||||
|
form.setFieldValue(
|
||||||
|
"switch_control_port",
|
||||||
|
dataStation.switch_control_port
|
||||||
|
);
|
||||||
|
form.setFieldValue(
|
||||||
|
"switch_control_username",
|
||||||
|
dataStation.switch_control_username
|
||||||
|
);
|
||||||
|
form.setFieldValue(
|
||||||
|
"switch_control_password",
|
||||||
|
dataStation.switch_control_password
|
||||||
|
);
|
||||||
|
|
||||||
|
const dataLine = dataStation.lines.map((value) => ({
|
||||||
|
id: value.id,
|
||||||
|
lineNumber: value.line_number || 0,
|
||||||
|
port: value.port,
|
||||||
|
lineClear: value.line_clear || 0,
|
||||||
|
apc_name: value.apc_name,
|
||||||
|
outlet: value.outlet,
|
||||||
|
station_id: value.station_id,
|
||||||
|
}));
|
||||||
|
setLines(dataLine);
|
||||||
|
}
|
||||||
|
}, [dataStation]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (lines.length > 0) {
|
||||||
|
const lastLine = lines[lines.length - 1];
|
||||||
|
if (lastLine?.lineNumber || lastLine?.port)
|
||||||
|
setLines((pre) => [...pre, lineInit]);
|
||||||
|
}
|
||||||
|
}, [lines]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
setLines([lineInit]);
|
||||||
|
setOpenConfirm(false);
|
||||||
|
form.reset();
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
const renderLinesTable = () => {
|
||||||
|
const rows = lines?.map((row: TLine, index: number) => {
|
||||||
|
return (
|
||||||
|
<Table.Tr key={index}>
|
||||||
|
<Table.Td fz={"sm"} p="3px" ta={"center"} fw={700}>
|
||||||
|
<NumberInput
|
||||||
|
value={row?.lineNumber}
|
||||||
|
onChange={(e) =>
|
||||||
|
setLines((pre) =>
|
||||||
|
pre.map((value, i) =>
|
||||||
|
i === index ? { ...value, lineNumber: Number(e!) } : value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td fz={"sm"} p="3px" ta={"center"} fw={700}>
|
||||||
|
<NumberInput
|
||||||
|
value={row?.port}
|
||||||
|
onChange={(e) =>
|
||||||
|
setLines((pre) =>
|
||||||
|
pre.map((value, i) =>
|
||||||
|
i === index ? { ...value, port: Number(e!) } : value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td fz={"sm"} p="3px" ta={"center"} fw={700}>
|
||||||
|
<NumberInput
|
||||||
|
value={row?.lineClear}
|
||||||
|
onChange={(e) =>
|
||||||
|
setLines((pre) =>
|
||||||
|
pre.map((value, i) =>
|
||||||
|
i === index ? { ...value, lineClear: Number(e!) } : value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td fz={"sm"} p="3px" ta={"center"} fw={700}>
|
||||||
|
<Select
|
||||||
|
data={[
|
||||||
|
{ label: "APC1", value: "apc_1" },
|
||||||
|
{ label: "APC2", value: "apc_2" },
|
||||||
|
]}
|
||||||
|
value={row?.apc_name}
|
||||||
|
onChange={(e) =>
|
||||||
|
setLines((pre) =>
|
||||||
|
pre.map((value, i) =>
|
||||||
|
i === index ? { ...value, apc_name: e! } : value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td fz={"sm"} p="3px" ta={"center"} fw={700}>
|
||||||
|
<NumberInput
|
||||||
|
value={row?.outlet}
|
||||||
|
onChange={(e) =>
|
||||||
|
setLines((pre) =>
|
||||||
|
pre.map((value, i) =>
|
||||||
|
i === index ? { ...value, outlet: Number(e!) } : value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td fz={"sm"} p="3px" ta={"center"} fw={700}>
|
||||||
|
{row?.lineNumber ? (
|
||||||
|
<ActionIcon
|
||||||
|
title="Remove line"
|
||||||
|
variant="outline"
|
||||||
|
color="red"
|
||||||
|
onClick={() => {
|
||||||
|
setLines((pre) => [
|
||||||
|
...pre.slice(0, index),
|
||||||
|
...pre.slice(index + 1, lines.length),
|
||||||
|
]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconInputX />
|
||||||
|
</ActionIcon>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
...form.values,
|
||||||
|
lines: lines.filter((el) => el.lineNumber && el.port),
|
||||||
|
};
|
||||||
|
if (isEdit) payload.id = dataStation?.id || 0;
|
||||||
|
const url = isEdit ? "api/stations/update" : "api/stations/create";
|
||||||
|
const response = await axios.post(apiUrl + url, payload);
|
||||||
|
if (response.data.status) {
|
||||||
|
if (response.data.data) {
|
||||||
|
const station = response.data.data[0];
|
||||||
|
setStations((pre) =>
|
||||||
|
isEdit
|
||||||
|
? pre.map((el) =>
|
||||||
|
el.id === station.id ? { ...el, ...station } : el
|
||||||
|
)
|
||||||
|
: [...pre, station]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
notifications.show({
|
||||||
|
title: "Success",
|
||||||
|
message: response.data.message,
|
||||||
|
color: "green",
|
||||||
|
});
|
||||||
|
onClose();
|
||||||
|
} else
|
||||||
|
notifications.show({
|
||||||
|
title: "Error",
|
||||||
|
message: response.data.message,
|
||||||
|
color: "red",
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
notifications.show({
|
||||||
|
title: "Error",
|
||||||
|
message: "Error save station",
|
||||||
|
color: "red",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post(apiUrl + "api/stations/delete", {
|
||||||
|
id: dataStation?.id,
|
||||||
|
});
|
||||||
|
if (response.data.status) {
|
||||||
|
setStations((pre) => pre.filter((el) => el.id !== dataStation?.id));
|
||||||
|
setActiveTab();
|
||||||
|
notifications.show({
|
||||||
|
title: "Success",
|
||||||
|
message: response.data.message,
|
||||||
|
color: "green",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
notifications.show({
|
||||||
|
title: "Error",
|
||||||
|
message: "Error delete station",
|
||||||
|
color: "red",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Modal
|
||||||
|
title={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "10px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isEdit ? "Edit Station" : "Add Station"}{" "}
|
||||||
|
<Button
|
||||||
|
style={{ height: "30px" }}
|
||||||
|
color="green"
|
||||||
|
onClick={() => {
|
||||||
|
handleSave();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
{isEdit && (
|
||||||
|
<Button
|
||||||
|
style={{ height: "30px" }}
|
||||||
|
color="red"
|
||||||
|
onClick={() => {
|
||||||
|
setOpenConfirm(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
size={"60%"}
|
||||||
|
style={{ position: "absolute", left: 0 }}
|
||||||
|
centered
|
||||||
|
opened={isOpen}
|
||||||
|
onClose={() => {
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex justify={"space-between"} gap={"sm"}>
|
||||||
|
{/* Station info */}
|
||||||
|
<Box w={"40%"}>
|
||||||
|
<TextInput
|
||||||
|
required
|
||||||
|
name="name"
|
||||||
|
label="Name"
|
||||||
|
size="sm"
|
||||||
|
value={form.values.name || ""}
|
||||||
|
onChange={(e) => form.setFieldValue("name", e.target.value)}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
width: "100%",
|
||||||
|
gap: "6px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
required
|
||||||
|
label="IP"
|
||||||
|
size="sm"
|
||||||
|
value={form.values.ip || ""}
|
||||||
|
onChange={(e) => form.setFieldValue("ip", e.target.value)}
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
required
|
||||||
|
size="sm"
|
||||||
|
label="Port"
|
||||||
|
value={form.values.port || ""}
|
||||||
|
onChange={(e) => form.setFieldValue("port", Number(e!))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
width: "100%",
|
||||||
|
gap: "6px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
value={form.values.netmask || ""}
|
||||||
|
label={"Netmask"}
|
||||||
|
onChange={(e) => {
|
||||||
|
form.setFieldValue("netmask", e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
value={form.values.network || ""}
|
||||||
|
label={"Network"}
|
||||||
|
onChange={(e) => {
|
||||||
|
form.setFieldValue("network", e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
width: "100%",
|
||||||
|
gap: "6px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
value={form.values.gateway || ""}
|
||||||
|
label={"Gateway"}
|
||||||
|
onChange={(e) => {
|
||||||
|
form.setFieldValue("gateway", e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
value={form.values.tftp_ip || ""}
|
||||||
|
label={"TFTP IP"}
|
||||||
|
onChange={(e) => {
|
||||||
|
form.setFieldValue("tftp_ip", e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{}}>
|
||||||
|
<Group
|
||||||
|
mt={"md"}
|
||||||
|
title="APC 1"
|
||||||
|
style={{
|
||||||
|
border: "1px solid #e1e1e1",
|
||||||
|
padding: "10px",
|
||||||
|
borderRadius: "5px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
w={"100%"}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
label="APC1 IP"
|
||||||
|
size="xs"
|
||||||
|
w={"60%"}
|
||||||
|
value={form.values.apc_1_ip || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
form.setFieldValue("apc_1_ip", e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
label="APC1 Port"
|
||||||
|
w={"39%"}
|
||||||
|
size="xs"
|
||||||
|
value={form.values.apc_1_port || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
form.setFieldValue("apc_1_port", parseInt(e.toString()))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
w={"100%"}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
label="Username"
|
||||||
|
w={"49%"}
|
||||||
|
size="xs"
|
||||||
|
autoComplete="new-username"
|
||||||
|
value={form.values.apc_1_username || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
form.setFieldValue("apc_1_username", e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PasswordInput
|
||||||
|
label="Password"
|
||||||
|
w={"49%"}
|
||||||
|
size="xs"
|
||||||
|
autoComplete="new-password"
|
||||||
|
value={form.values.apc_1_password || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
form.setFieldValue("apc_1_password", e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* APC2 configuration */}
|
||||||
|
<Group
|
||||||
|
mt={"md"}
|
||||||
|
title="APC 2"
|
||||||
|
style={{
|
||||||
|
border: "1px solid #e1e1e1",
|
||||||
|
padding: "10px",
|
||||||
|
borderRadius: "5px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
w={"100%"}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
label="APC2 IP"
|
||||||
|
w={"60%"}
|
||||||
|
size="xs"
|
||||||
|
value={form.values.apc_2_ip || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
form.setFieldValue("apc_2_ip", e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
label="APC2 Port"
|
||||||
|
w={"39%"}
|
||||||
|
size="xs"
|
||||||
|
value={form.values.apc_2_port || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
form.setFieldValue("apc_2_port", parseInt(e.toString()))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
w={"100%"}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
label="Username"
|
||||||
|
w={"49%"}
|
||||||
|
size="xs"
|
||||||
|
value={form.values.apc_2_username || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
form.setFieldValue("apc_2_username", e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PasswordInput
|
||||||
|
label="Password"
|
||||||
|
w={"49%"}
|
||||||
|
size="xs"
|
||||||
|
value={form.values.apc_2_password || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
form.setFieldValue("apc_2_password", e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* APC1 configuration */}
|
||||||
|
<Group
|
||||||
|
mt={"md"}
|
||||||
|
title="Switch control"
|
||||||
|
style={{
|
||||||
|
border: "1px solid #e1e1e1",
|
||||||
|
padding: "10px",
|
||||||
|
borderRadius: "5px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
w={"100%"}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
label="Switch IP"
|
||||||
|
size="xs"
|
||||||
|
w={"60%"}
|
||||||
|
value={form.values.switch_control_ip || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
form.setFieldValue("switch_control_ip", e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
label="Switch Port"
|
||||||
|
w={"39%"}
|
||||||
|
size="xs"
|
||||||
|
value={form.values.switch_control_port || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
form.setFieldValue(
|
||||||
|
"switch_control_port",
|
||||||
|
parseInt(e.toString())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
w={"100%"}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
label="Username"
|
||||||
|
size="xs"
|
||||||
|
w={"49%"}
|
||||||
|
value={form.values.switch_control_username || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
form.setFieldValue(
|
||||||
|
"switch_control_username",
|
||||||
|
e.target.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<PasswordInput
|
||||||
|
label="Password"
|
||||||
|
size="xs"
|
||||||
|
w={"49%"}
|
||||||
|
value={form.values.switch_control_password || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
form.setFieldValue(
|
||||||
|
"switch_control_password",
|
||||||
|
e.target.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Group>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
{/* Lines */}
|
||||||
|
<Box w={"60%"}>
|
||||||
|
<Table
|
||||||
|
verticalSpacing="xs"
|
||||||
|
horizontalSpacing="lg"
|
||||||
|
striped
|
||||||
|
highlightOnHover
|
||||||
|
withTableBorder
|
||||||
|
withColumnBorders
|
||||||
|
>
|
||||||
|
<Table.Thead>
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Th fz={"sm"} w={"15%"} ta={"center"}>
|
||||||
|
Line number
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th fz={"sm"} w={"15%"} ta={"center"}>
|
||||||
|
Port
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th fz={"sm"} w={"15%"} ta={"center"}>
|
||||||
|
Clear line
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th fz={"sm"} w={"15%"} ta={"center"}>
|
||||||
|
APC
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th fz={"sm"} w={"15%"} ta={"center"}>
|
||||||
|
Outlet
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th fz={"sm"} w={"10%"} ta={"center"}></Table.Th>
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>{renderLinesTable()}</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<DialogConfirm
|
||||||
|
opened={openConfirm}
|
||||||
|
close={() => {
|
||||||
|
setOpenConfirm(false);
|
||||||
|
}}
|
||||||
|
message={"Are you sure delete this station?"}
|
||||||
|
handle={() => {
|
||||||
|
handleDelete();
|
||||||
|
setOpenConfirm(false);
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
centered={true}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StationSetting;
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
.title {
|
||||||
|
background-color: light-dark(var(white), var(--mantine-color-dark-7));
|
||||||
|
z-index: 100;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 var(--mantine-spacing-sm) var(--mantine-spacing-lg)
|
||||||
|
var(--mantine-spacing-sm);
|
||||||
|
border-bottom: solid rgba(201, 201, 201, 0.377) 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.optionIcon {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleteIcon {
|
||||||
|
color: red;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editIcon {
|
||||||
|
color: rgb(9, 132, 132);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editIcon:hover {
|
||||||
|
background-color: rgba(203, 203, 203, 0.809);
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleteIcon:hover {
|
||||||
|
background-color: rgba(203, 203, 203, 0.809);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
background-color: light-dark(white, #2d353c);
|
||||||
|
text-align: center;
|
||||||
|
border: solid 1px rgb(255, 145, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialogText {
|
||||||
|
color: light-dark(#2d353c, white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dragging {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border: 1px dashed #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rc-tooltipCustom {
|
||||||
|
}
|
||||||
|
|
||||||
|
.rc-tooltip-inner {
|
||||||
|
background-color: #1d1d1d !important;
|
||||||
|
padding: 0px 8px !important;
|
||||||
|
color: #fff;
|
||||||
|
text-align: left;
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: #565656;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
box-shadow: 0 0 4px rgba(0, 0, 0, 0.17);
|
||||||
|
min-height: 25px !important;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.rc-tooltip-arrow {
|
||||||
|
/* background-color: #ffcc00 !important;
|
||||||
|
color: #ffcc00 !important; */
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.rc-tooltip-content {
|
||||||
|
max-height: 97vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalFullWidth > div {
|
||||||
|
padding-left: 0 !important;
|
||||||
|
padding-right: 0 !important;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
import { Table, TextInput } from "@mantine/core";
|
||||||
|
import { IconRowInsertTop, IconX } from "@tabler/icons-react";
|
||||||
|
import classes from "./Scenario.module.css";
|
||||||
|
import { numberOnly } from "../../untils/helper";
|
||||||
|
|
||||||
|
interface IPayload {
|
||||||
|
element: any;
|
||||||
|
i: any;
|
||||||
|
form: any;
|
||||||
|
deleteRow: any;
|
||||||
|
addRowUnder: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TableRows = ({ element, i, form, deleteRow, addRowUnder }: IPayload) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Td>
|
||||||
|
<span>{i + 1}</span>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* <Box {...provided.dragHandleProps} style={{ cursor: 'grab' }}>
|
||||||
|
<IconGripVertical />
|
||||||
|
</Box> */}
|
||||||
|
<TextInput
|
||||||
|
style={{ width: "250px" }}
|
||||||
|
value={element.expect}
|
||||||
|
placeholder="Expect previous output"
|
||||||
|
onChange={(e) => {
|
||||||
|
const newBody = [...form.values.body];
|
||||||
|
form.setFieldValue(
|
||||||
|
"body",
|
||||||
|
newBody.map((el, index) =>
|
||||||
|
i === index
|
||||||
|
? {
|
||||||
|
...el,
|
||||||
|
expect: e.target.value,
|
||||||
|
}
|
||||||
|
: el
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<TextInput
|
||||||
|
style={{ width: "250px" }}
|
||||||
|
value={element.send}
|
||||||
|
placeholder="Command send"
|
||||||
|
onChange={(e) => {
|
||||||
|
const newBody = [...form.values.body];
|
||||||
|
form.setFieldValue(
|
||||||
|
"body",
|
||||||
|
newBody.map((el, index) =>
|
||||||
|
i === index ? { ...el, send: e.target.value } : el
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<TextInput
|
||||||
|
style={{ width: "100px" }}
|
||||||
|
value={element.delay}
|
||||||
|
placeholder="Delay send"
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = numberOnly(e.target.value);
|
||||||
|
if (Number(value) <= 1000000) {
|
||||||
|
const newBody = [...form.values.body];
|
||||||
|
form.setFieldValue(
|
||||||
|
"body",
|
||||||
|
newBody.map((el, index) =>
|
||||||
|
i === index ? { ...el, delay: value } : el
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<TextInput
|
||||||
|
style={{ width: "70px" }}
|
||||||
|
value={element.repeat}
|
||||||
|
placeholder="Repeat"
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = numberOnly(e.target.value);
|
||||||
|
if (Number(value) <= 1000) {
|
||||||
|
const newBody = [...form.values.body];
|
||||||
|
form.setFieldValue(
|
||||||
|
"body",
|
||||||
|
newBody.map((el, index) =>
|
||||||
|
i === index
|
||||||
|
? {
|
||||||
|
...el,
|
||||||
|
repeat: value,
|
||||||
|
}
|
||||||
|
: el
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === ".") e.preventDefault();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "4px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div role="button" onClick={() => addRowUnder(i)}>
|
||||||
|
<IconRowInsertTop className={classes.editIcon} />
|
||||||
|
</div>
|
||||||
|
<IconX
|
||||||
|
className={classes.deleteIcon}
|
||||||
|
onClick={() => {
|
||||||
|
deleteRow(i);
|
||||||
|
}}
|
||||||
|
width={20}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TableRows;
|
||||||
|
|
@ -10,237 +10,237 @@ import {
|
||||||
IconSettingsAutomation,
|
IconSettingsAutomation,
|
||||||
IconKey,
|
IconKey,
|
||||||
IconClipboardList,
|
IconClipboardList,
|
||||||
} from '@tabler/icons-react'
|
} from "@tabler/icons-react";
|
||||||
|
|
||||||
export const SOCKET_EVENTS = {
|
export const SOCKET_EVENTS = {
|
||||||
ROOM: {
|
ROOM: {
|
||||||
JOINED: 'room_joined',
|
JOINED: "room_joined",
|
||||||
LEFT: 'room_left',
|
LEFT: "room_left",
|
||||||
},
|
},
|
||||||
|
|
||||||
APP_STATUS: { RECEIVED: 'app_status_received' },
|
APP_STATUS: { RECEIVED: "app_status_received" },
|
||||||
|
|
||||||
APP_DATA: {
|
APP_DATA: {
|
||||||
SENT: 'app_data_sent',
|
SENT: "app_data_sent",
|
||||||
RECEIVED: 'app_data_received',
|
RECEIVED: "app_data_received",
|
||||||
},
|
},
|
||||||
|
|
||||||
SCRIPT_TEST: {
|
SCRIPT_TEST: {
|
||||||
SENT: 'script_test_sent',
|
SENT: "script_test_sent",
|
||||||
TIME_RECEIVED: 'script_test_time_received',
|
TIME_RECEIVED: "script_test_time_received",
|
||||||
},
|
},
|
||||||
|
|
||||||
DATA_OUTPUT: { RECEIVED: 'data_output_received' },
|
DATA_OUTPUT: { RECEIVED: "data_output_received" },
|
||||||
NOTIFICATION: {
|
NOTIFICATION: {
|
||||||
FROM_APP: 'notification_send_from_app',
|
FROM_APP: "notification_send_from_app",
|
||||||
SEND_ALL: 'notification_send_to_all',
|
SEND_ALL: "notification_send_to_all",
|
||||||
},
|
},
|
||||||
APC_CONTROL: {
|
APC_CONTROL: {
|
||||||
FROM_WEB: 'apc_control_request_from_web',
|
FROM_WEB: "apc_control_request_from_web",
|
||||||
TO_APP: 'apc_control_request_to_app',
|
TO_APP: "apc_control_request_to_app",
|
||||||
FROM_WEB_ALL_APC: 'all_apc_control_request_from_web',
|
FROM_WEB_ALL_APC: "all_apc_control_request_from_web",
|
||||||
},
|
},
|
||||||
SYSTEM_LOG: {
|
SYSTEM_LOG: {
|
||||||
FROM_APP: 'system_log_send_from_app',
|
FROM_APP: "system_log_send_from_app",
|
||||||
GET_SYSTEM_LOG_FROM_WEB: 'get_system_log_from_web',
|
GET_SYSTEM_LOG_FROM_WEB: "get_system_log_from_web",
|
||||||
REQUEST_LIST_SYSTEM_LOG_FROM_WEB: 'request_list_system_log_from_web',
|
REQUEST_LIST_SYSTEM_LOG_FROM_WEB: "request_list_system_log_from_web",
|
||||||
RESPONSE_LIST_SYSTEM_LOG_FROM_APP: 'response_list_system_log_from_app',
|
RESPONSE_LIST_SYSTEM_LOG_FROM_APP: "response_list_system_log_from_app",
|
||||||
RESPONSE_SYSTEM_LOG_FROM_APP: 'response_system_log_from_app',
|
RESPONSE_SYSTEM_LOG_FROM_APP: "response_system_log_from_app",
|
||||||
RESPONSE_SYSTEM_LOG_TO_WEB: 'response_system_log_to_web',
|
RESPONSE_SYSTEM_LOG_TO_WEB: "response_system_log_to_web",
|
||||||
},
|
},
|
||||||
CLI: {
|
CLI: {
|
||||||
OPEN_CLI_LINE_FROM_WEB: 'open_cli_line_from_web',
|
OPEN_CLI_LINE_FROM_WEB: "open_cli_line_from_web",
|
||||||
CLOSE_CLI_LINE_FROM_WEB: 'close_cli_line_from_web',
|
CLOSE_CLI_LINE_FROM_WEB: "close_cli_line_from_web",
|
||||||
OPEN_CLI_MULTI_LINE_FROM_WEB: 'open_cli_multi_line_from_web',
|
OPEN_CLI_MULTI_LINE_FROM_WEB: "open_cli_multi_line_from_web",
|
||||||
CLOSE_CLI_MULTI_LINE_FROM_WEB: 'close_cli_multi_line_from_web',
|
CLOSE_CLI_MULTI_LINE_FROM_WEB: "close_cli_multi_line_from_web",
|
||||||
WRITE_COMMAND_FROM_WEB: 'write_command_line_from_web',
|
WRITE_COMMAND_FROM_WEB: "write_command_line_from_web",
|
||||||
WRITE_COMMAND_TO_APP: 'write_command_line_to_app',
|
WRITE_COMMAND_TO_APP: "write_command_line_to_app",
|
||||||
RECEIVE_COMMAND_DATA_FROM_APP: 'receive_command_data_from_app',
|
RECEIVE_COMMAND_DATA_FROM_APP: "receive_command_data_from_app",
|
||||||
RECEIVE_COMMAND_DATA_TO_WEB: 'receive_command_data_to_web',
|
RECEIVE_COMMAND_DATA_TO_WEB: "receive_command_data_to_web",
|
||||||
},
|
},
|
||||||
RESCAN: {
|
RESCAN: {
|
||||||
SEND_LIST_RESCAN_FROM_WEB: 'send_list_rescan_from_web',
|
SEND_LIST_RESCAN_FROM_WEB: "send_list_rescan_from_web",
|
||||||
SEND_LIST_RESCAN_TO_APP: 'send_list_rescan_to_app',
|
SEND_LIST_RESCAN_TO_APP: "send_list_rescan_to_app",
|
||||||
},
|
},
|
||||||
LOCK: {
|
LOCK: {
|
||||||
SEND_LIST_LOCK_FROM_WEB: 'send_list_lock_from_web',
|
SEND_LIST_LOCK_FROM_WEB: "send_list_lock_from_web",
|
||||||
SEND_LIST_LOCK_TO_APP: 'send_list_lock_to_app',
|
SEND_LIST_LOCK_TO_APP: "send_list_lock_to_app",
|
||||||
},
|
},
|
||||||
CHANGE_STAGE: {
|
CHANGE_STAGE: {
|
||||||
SEND_STAGE_FROM_WEB: 'send_stage_from_web',
|
SEND_STAGE_FROM_WEB: "send_stage_from_web",
|
||||||
SEND_STAGE_TO_APP: 'send_stage_to_app',
|
SEND_STAGE_TO_APP: "send_stage_to_app",
|
||||||
},
|
},
|
||||||
UPDATE_PROPERTY: {
|
UPDATE_PROPERTY: {
|
||||||
UPDATE_PROPERTY_FROM_WEB: 'update_property_from_web',
|
UPDATE_PROPERTY_FROM_WEB: "update_property_from_web",
|
||||||
UPDATE_PROPERTY_TO_APP: 'update_property_to_app',
|
UPDATE_PROPERTY_TO_APP: "update_property_to_app",
|
||||||
},
|
},
|
||||||
RUN_SCENARIOS: {
|
RUN_SCENARIOS: {
|
||||||
RUN_SCENARIOS_FROM_WEB: 'run_scenarios_from_web',
|
RUN_SCENARIOS_FROM_WEB: "run_scenarios_from_web",
|
||||||
RUN_SCENARIOS_TO_APP: 'run_scenarios_to_app',
|
RUN_SCENARIOS_TO_APP: "run_scenarios_to_app",
|
||||||
},
|
},
|
||||||
SEND_BREAK: {
|
SEND_BREAK: {
|
||||||
SEND_BREAK_FROM_WEB: 'send_break_from_web',
|
SEND_BREAK_FROM_WEB: "send_break_from_web",
|
||||||
SEND_BREAK_TO_APP: 'send_break_to_app',
|
SEND_BREAK_TO_APP: "send_break_to_app",
|
||||||
},
|
},
|
||||||
JOIN_MULTI_ROOM: {
|
JOIN_MULTI_ROOM: {
|
||||||
JOIN_MULTI_ROOM_FROM_WEB: 'join_multi_room_from_web',
|
JOIN_MULTI_ROOM_FROM_WEB: "join_multi_room_from_web",
|
||||||
},
|
},
|
||||||
LEAVE_MULTI_ROOM: {
|
LEAVE_MULTI_ROOM: {
|
||||||
LEAVE_MULTI_ROOM_FROM_WEB: 'leave_multi_room_from_web',
|
LEAVE_MULTI_ROOM_FROM_WEB: "leave_multi_room_from_web",
|
||||||
},
|
},
|
||||||
TAKE_OVER: {
|
TAKE_OVER: {
|
||||||
TAKE_OVER_FROM_WEB: 'take_over_from_web',
|
TAKE_OVER_FROM_WEB: "take_over_from_web",
|
||||||
TAKE_OVER_TO_WEB: 'take_over_to_web',
|
TAKE_OVER_TO_WEB: "take_over_to_web",
|
||||||
},
|
},
|
||||||
CONNECT_APC: {
|
CONNECT_APC: {
|
||||||
CONNECT_APC_FROM_WEB: 'connect_apc_from_web',
|
CONNECT_APC_FROM_WEB: "connect_apc_from_web",
|
||||||
CONNECT_APC_TO_APP: 'connect_apc_to_app',
|
CONNECT_APC_TO_APP: "connect_apc_to_app",
|
||||||
},
|
},
|
||||||
DATA_APC_RECEIVED: {
|
DATA_APC_RECEIVED: {
|
||||||
DATA_APC_RECEIVED_FROM_APP: 'data_apc_received_from_app',
|
DATA_APC_RECEIVED_FROM_APP: "data_apc_received_from_app",
|
||||||
DATA_APC_RECEIVED_TO_WEB: 'data_apc_received_to_web',
|
DATA_APC_RECEIVED_TO_WEB: "data_apc_received_to_web",
|
||||||
},
|
},
|
||||||
SEND_COMMAND_TO_APC: {
|
SEND_COMMAND_TO_APC: {
|
||||||
SEND_COMMAND_TO_APC_FROM_WEB: 'send_command_to_apc_from_web',
|
SEND_COMMAND_TO_APC_FROM_WEB: "send_command_to_apc_from_web",
|
||||||
SEND_COMMAND_TO_APC_TO_APP: 'send_command_to_apc_to_app',
|
SEND_COMMAND_TO_APC_TO_APP: "send_command_to_apc_to_app",
|
||||||
},
|
},
|
||||||
SEND_CLEAR_LINE: {
|
SEND_CLEAR_LINE: {
|
||||||
SEND_CLEAR_LINE_FROM_WEB: 'send_clear_line_from_web',
|
SEND_CLEAR_LINE_FROM_WEB: "send_clear_line_from_web",
|
||||||
SEND_CLEAR_LINE_TO_APP: 'send_clear_line_to_app',
|
SEND_CLEAR_LINE_TO_APP: "send_clear_line_to_app",
|
||||||
},
|
},
|
||||||
SEND_CLOSE_LINE: {
|
SEND_CLOSE_LINE: {
|
||||||
SEND_CLOSE_LINE_FROM_WEB: 'send_close_line_from_web',
|
SEND_CLOSE_LINE_FROM_WEB: "send_close_line_from_web",
|
||||||
SEND_CLOSE_LINE_TO_APP: 'send_close_line_to_app',
|
SEND_CLOSE_LINE_TO_APP: "send_close_line_to_app",
|
||||||
},
|
},
|
||||||
SEND_OPEN_LINE: {
|
SEND_OPEN_LINE: {
|
||||||
SEND_OPEN_LINE_FROM_WEB: 'send_open_line_from_web',
|
SEND_OPEN_LINE_FROM_WEB: "send_open_line_from_web",
|
||||||
SEND_OPEN_LINE_TO_APP: 'send_open_line_to_app',
|
SEND_OPEN_LINE_TO_APP: "send_open_line_to_app",
|
||||||
},
|
},
|
||||||
CONTROL_APP: {
|
CONTROL_APP: {
|
||||||
SEND_PAUSE_APP_FROM_WEB: 'send_pause_app_from_web',
|
SEND_PAUSE_APP_FROM_WEB: "send_pause_app_from_web",
|
||||||
SEND_RESUME_APP_FROM_WEB: 'send_resume_app_from_web',
|
SEND_RESUME_APP_FROM_WEB: "send_resume_app_from_web",
|
||||||
SEND_RESTART_APP_FROM_WEB: 'send_restart_app_from_web',
|
SEND_RESTART_APP_FROM_WEB: "send_restart_app_from_web",
|
||||||
SEND_QUIT_APP_FROM_WEB: 'send_quit_app_from_web',
|
SEND_QUIT_APP_FROM_WEB: "send_quit_app_from_web",
|
||||||
},
|
},
|
||||||
CONNECT_SWITCH: {
|
CONNECT_SWITCH: {
|
||||||
CONNECT_SWITCH_FROM_WEB: 'connect_switch_from_web',
|
CONNECT_SWITCH_FROM_WEB: "connect_switch_from_web",
|
||||||
CONNECT_SWITCH_TO_APP: 'connect_switch_to_app',
|
CONNECT_SWITCH_TO_APP: "connect_switch_to_app",
|
||||||
},
|
},
|
||||||
DATA_SWITCH_RECEIVED: {
|
DATA_SWITCH_RECEIVED: {
|
||||||
DATA_SWITCH_RECEIVED_FROM_APP: 'data_switch_received_from_app',
|
DATA_SWITCH_RECEIVED_FROM_APP: "data_switch_received_from_app",
|
||||||
DATA_SWITCH_RECEIVED_TO_WEB: 'data_switch_received_to_web',
|
DATA_SWITCH_RECEIVED_TO_WEB: "data_switch_received_to_web",
|
||||||
},
|
},
|
||||||
SEND_COMMAND_TO_SWITCH: {
|
SEND_COMMAND_TO_SWITCH: {
|
||||||
SEND_COMMAND_TO_SWITCH_FROM_WEB: 'send_command_to_switch_from_web',
|
SEND_COMMAND_TO_SWITCH_FROM_WEB: "send_command_to_switch_from_web",
|
||||||
SEND_COMMAND_TO_SWITCH_TO_APP: 'send_command_to_switch_to_app',
|
SEND_COMMAND_TO_SWITCH_TO_APP: "send_command_to_switch_to_app",
|
||||||
SEND_COMMAND_TO_SWITCH_FROM_APP: 'send_command_to_switch_from_app',
|
SEND_COMMAND_TO_SWITCH_FROM_APP: "send_command_to_switch_from_app",
|
||||||
},
|
},
|
||||||
RELOAD_TICKET: {
|
RELOAD_TICKET: {
|
||||||
RELOAD_TICKET_FROM_WEB: 'reload_ticket_from_web',
|
RELOAD_TICKET_FROM_WEB: "reload_ticket_from_web",
|
||||||
RELOAD_TICKET_TO_WEB: 'reload_ticket_to_web',
|
RELOAD_TICKET_TO_WEB: "reload_ticket_to_web",
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
export const LINE_STATUS = {
|
export const LINE_STATUS = {
|
||||||
CHECK_INVENTORY: 'CHECK_INVENTORY',
|
CHECK_INVENTORY: "CHECK_INVENTORY",
|
||||||
STATUS_TEST: 'TESTING',
|
STATUS_TEST: "TESTING",
|
||||||
CONNECT_FAIL: 'CONNECT_FAIL',
|
CONNECT_FAIL: "CONNECT_FAIL",
|
||||||
CONNECTED: 'CONNECTED',
|
CONNECTED: "CONNECTED",
|
||||||
STATUS_READY: 'READY',
|
STATUS_READY: "READY",
|
||||||
STATUS_DONE: 'DONE',
|
STATUS_DONE: "DONE",
|
||||||
STATUS_CHECKING: 'CHECKING',
|
STATUS_CHECKING: "CHECKING",
|
||||||
STATUS_LOCKED: 'LOCKED',
|
STATUS_LOCKED: "LOCKED",
|
||||||
STATUS_CLOSED: 'CLOSED',
|
STATUS_CLOSED: "CLOSED",
|
||||||
STATUS_TIMEOUT: 'TIMEOUT',
|
STATUS_TIMEOUT: "TIMEOUT",
|
||||||
STATUS_PHYSICAL_TEST: 'STATUS_PHYSICAL_TEST',
|
STATUS_PHYSICAL_TEST: "STATUS_PHYSICAL_TEST",
|
||||||
STATUS_PHYSICAL_TEST_DONE: 'STATUS_PHYSICAL_TEST_DONE',
|
STATUS_PHYSICAL_TEST_DONE: "STATUS_PHYSICAL_TEST_DONE",
|
||||||
STATUS_UNDIFINED_INVEN: 'INVENTORY_UNIDENTIFIED',
|
STATUS_UNDIFINED_INVEN: "INVENTORY_UNIDENTIFIED",
|
||||||
STATUS_RUNNING_SCENARIOS: 'RUNNING_SCENARIOS',
|
STATUS_RUNNING_SCENARIOS: "RUNNING_SCENARIOS",
|
||||||
APC_CONTROL: 'APC_CONTROL',
|
APC_CONTROL: "APC_CONTROL",
|
||||||
STATUS_STARTING: 'STARTING',
|
STATUS_STARTING: "STARTING",
|
||||||
STATUS_TURN_OFF: 'TURN_OFF',
|
STATUS_TURN_OFF: "TURN_OFF",
|
||||||
STATUS_RESTARTING: 'RESTARTING',
|
STATUS_RESTARTING: "RESTARTING",
|
||||||
}
|
};
|
||||||
|
|
||||||
export const LIST_FAVORITE_COMMANDS = [
|
export const LIST_FAVORITE_COMMANDS = [
|
||||||
'sh inv',
|
"sh inv",
|
||||||
'sh ver',
|
"sh ver",
|
||||||
// 'sh diag',
|
// 'sh diag',
|
||||||
// 'sh post',
|
// 'sh post',
|
||||||
// 'sh env',
|
// 'sh env',
|
||||||
// 'sh log',
|
// 'sh log',
|
||||||
// 'sh platform',
|
// 'sh platform',
|
||||||
]
|
];
|
||||||
|
|
||||||
export const dataPermission = [
|
export const dataPermission = [
|
||||||
{
|
{
|
||||||
link: '/dashboard',
|
link: "/dashboard",
|
||||||
label: 'Dashboard',
|
label: "Dashboard",
|
||||||
icon: IconHome,
|
icon: IconHome,
|
||||||
requiredPermissions: [],
|
requiredPermissions: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
link: '/station-setting',
|
link: "/station-setting",
|
||||||
label: 'Station Setting',
|
label: "Station Setting",
|
||||||
icon: IconServer,
|
icon: IconServer,
|
||||||
requiredPermissions: ['station_activity'],
|
requiredPermissions: ["station_activity"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
link: '/monitor',
|
link: "/monitor",
|
||||||
label: 'Monitoring',
|
label: "Monitoring",
|
||||||
icon: IconDeviceDesktop,
|
icon: IconDeviceDesktop,
|
||||||
requiredPermissions: [
|
requiredPermissions: [
|
||||||
'monitor_power',
|
"monitor_power",
|
||||||
'monitor_cli',
|
"monitor_cli",
|
||||||
'monitor_other_items',
|
"monitor_other_items",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
link: '/control-apc',
|
link: "/control-apc",
|
||||||
label: 'Control APC',
|
label: "Control APC",
|
||||||
icon: IconSettingsAutomation,
|
icon: IconSettingsAutomation,
|
||||||
requiredPermissions: ['control_apc_activity'],
|
requiredPermissions: ["control_apc_activity"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
link: '/group-model',
|
link: "/group-model",
|
||||||
label: 'Group - Model',
|
label: "Group - Model",
|
||||||
icon: IconRouter,
|
icon: IconRouter,
|
||||||
requiredPermissions: ['group_model_activity'],
|
requiredPermissions: ["group_model_activity"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
link: '/keyword',
|
link: "/keyword",
|
||||||
label: 'Keyword',
|
label: "Keyword",
|
||||||
icon: IconKey,
|
icon: IconKey,
|
||||||
requiredPermissions: ['keyword_activity', 'keyword_limit'],
|
requiredPermissions: ["keyword_activity", "keyword_limit"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
link: '/exclude-error',
|
link: "/exclude-error",
|
||||||
label: 'Exclude Errors',
|
label: "Exclude Errors",
|
||||||
icon: IconBan,
|
icon: IconBan,
|
||||||
requiredPermissions: ['exclude_error_activity', 'exclude_error_limit'],
|
requiredPermissions: ["exclude_error_activity", "exclude_error_limit"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
link: '/list-logs',
|
link: "/list-logs",
|
||||||
label: 'List Logs',
|
label: "List Logs",
|
||||||
icon: IconFile,
|
icon: IconFile,
|
||||||
requiredPermissions: [],
|
requiredPermissions: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
link: '/webhooks',
|
link: "/webhooks",
|
||||||
label: 'Webhooks',
|
label: "Webhooks",
|
||||||
icon: IconWebhook,
|
icon: IconWebhook,
|
||||||
requiredPermissions: ['webhook_activity', 'webhook_add_limit'],
|
requiredPermissions: ["webhook_activity", "webhook_add_limit"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
link: '/scenario',
|
link: "/scenario",
|
||||||
label: 'Scenario',
|
label: "Scenario",
|
||||||
icon: IconClipboardList,
|
icon: IconClipboardList,
|
||||||
requiredPermissions: ['scenario_activity', 'scenario_add_limit'],
|
requiredPermissions: ["scenario_activity", "scenario_add_limit"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
link: '/upgrade',
|
link: "/upgrade",
|
||||||
label: 'Upgrade now!',
|
label: "Upgrade now!",
|
||||||
icon: IconCrown,
|
icon: IconCrown,
|
||||||
requiredPermissions: [],
|
requiredPermissions: [],
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
export const numberOnly = (value: string): string => {
|
||||||
|
const matched = value.match(/[\d.]+/g);
|
||||||
|
return matched ? matched.join("") : "";
|
||||||
|
};
|
||||||
|
|
@ -58,9 +58,10 @@ export type TLine = {
|
||||||
id?: number;
|
id?: number;
|
||||||
port: number;
|
port: number;
|
||||||
lineNumber: number;
|
lineNumber: number;
|
||||||
|
line_number?: number;
|
||||||
lineClear: number;
|
lineClear: number;
|
||||||
|
line_clear?: number;
|
||||||
station_id: number;
|
station_id: number;
|
||||||
is_active: string | boolean;
|
|
||||||
data?: string | any;
|
data?: string | any;
|
||||||
type?: string;
|
type?: string;
|
||||||
inventory?: any;
|
inventory?: any;
|
||||||
|
|
@ -69,7 +70,6 @@ export type TLine = {
|
||||||
outlet?: number;
|
outlet?: number;
|
||||||
cliOpened?: boolean;
|
cliOpened?: boolean;
|
||||||
systemLogUrl?: string;
|
systemLogUrl?: string;
|
||||||
start_round_at: number;
|
|
||||||
apc_name: string;
|
apc_name: string;
|
||||||
created_at?: string; // or use Date if you're working with Date objects
|
created_at?: string; // or use Date if you're working with Date objects
|
||||||
updated_at?: string; // or use Date if you're working with Date objects
|
updated_at?: string; // or use Date if you're working with Date objects
|
||||||
|
|
@ -138,3 +138,20 @@ export type LineConfig = {
|
||||||
output: string;
|
output: string;
|
||||||
status: string;
|
status: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type IScenario = {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
timeout: number;
|
||||||
|
is_reboot: boolean;
|
||||||
|
updated_at: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IBodyScenario = {
|
||||||
|
expect: string;
|
||||||
|
send: string;
|
||||||
|
delay: string;
|
||||||
|
repeat: string;
|
||||||
|
note: string;
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue