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) {
|
||||
try {
|
||||
const scenarioId = request.param('id')
|
||||
const payload = await request.all()
|
||||
const scenario = await Scenario.findOrFail(scenarioId)
|
||||
const scenarioId = request.body().id
|
||||
const payload = request.body()
|
||||
const scenario = await Scenario.find(scenarioId)
|
||||
if (!scenario) return response.status(404).json({ message: 'Scenario not found' })
|
||||
payload.body = JSON.stringify(payload.body)
|
||||
scenario.merge(payload)
|
||||
await scenario.save()
|
||||
|
|
@ -113,7 +114,7 @@ export default class ScenariosController {
|
|||
*/
|
||||
async delete({ request, response }: HttpContext) {
|
||||
try {
|
||||
const scenarioId = request.param('id')
|
||||
const scenarioId = request.body().id
|
||||
const scenario = await Scenario.findOrFail(scenarioId)
|
||||
|
||||
if (!scenario) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import Station from '#models/station'
|
||||
import Line from '#models/line'
|
||||
|
||||
export default class StationsController {
|
||||
public async index({}: HttpContext) {
|
||||
|
|
@ -8,6 +9,8 @@ export default class StationsController {
|
|||
|
||||
public async store({ request, response }: HttpContext) {
|
||||
let payload = request.only(Array.from(Station.$columnsDefinitions.keys()))
|
||||
let lines: Line[] = request.body().lines || []
|
||||
|
||||
try {
|
||||
const stationName = await Station.findBy('name', payload.name)
|
||||
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!' })
|
||||
|
||||
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({
|
||||
status: true,
|
||||
message: 'Station created successfully',
|
||||
data: station,
|
||||
data: resStation.map((el) => ({ ...el.$original, lines: newLines })),
|
||||
})
|
||||
} catch (error) {
|
||||
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'
|
||||
)
|
||||
)
|
||||
let lines: Line[] = request.body().lines || []
|
||||
|
||||
try {
|
||||
const station = await Station.find(request.body().id)
|
||||
|
||||
|
|
@ -48,7 +76,21 @@ export default class StationsController {
|
|||
|
||||
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) {
|
||||
return response.badRequest({ error: error, message: 'Station update failed', status: false })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import net from 'node:net'
|
||||
import { cleanData } from '../ultils/helper.js'
|
||||
import { cleanData, sleep } from '../ultils/helper.js'
|
||||
import Scenario from '#models/scenario'
|
||||
|
||||
interface LineConfig {
|
||||
|
|
@ -59,7 +59,7 @@ export default class LineConnection {
|
|||
this.client.on('data', (data) => {
|
||||
if (this.connecting) return
|
||||
let message = data.toString()
|
||||
this.outputBuffer += message
|
||||
if (this.isRunningScript) this.outputBuffer += message
|
||||
// let output = cleanData(message)
|
||||
// console.log(`📨 [${this.config.port}] ${message}`)
|
||||
// Handle netOutput with backspace support
|
||||
|
|
@ -109,7 +109,7 @@ export default class LineConnection {
|
|||
|
||||
console.log(`⏳ Connection timeout line ${lineNumber}`)
|
||||
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) {
|
||||
if (!this.client || this.client.destroyed) {
|
||||
throw new Error('Not connected')
|
||||
console.log('Not connected')
|
||||
this.isRunningScript = false
|
||||
this.outputBuffer = ''
|
||||
return
|
||||
}
|
||||
if (this.isRunningScript) {
|
||||
throw new Error('Script already running')
|
||||
console.log('Script already running')
|
||||
return
|
||||
}
|
||||
|
||||
this.isRunningScript = true
|
||||
|
|
@ -152,10 +156,10 @@ export default class LineConnection {
|
|||
const timeoutTimer = setTimeout(() => {
|
||||
this.isRunningScript = false
|
||||
this.outputBuffer = ''
|
||||
reject(new Error('Script timeout'))
|
||||
// reject(new Error('Script timeout'))
|
||||
}, script.timeout || 300000)
|
||||
|
||||
const runStep = (index: number) => {
|
||||
const runStep = async (index: number) => {
|
||||
if (index >= steps.length) {
|
||||
clearTimeout(timeoutTimer)
|
||||
this.isRunningScript = false
|
||||
|
|
@ -166,11 +170,9 @@ export default class LineConnection {
|
|||
|
||||
const step = steps[index]
|
||||
let repeatCount = Number(step.repeat) || 1
|
||||
|
||||
const sendCommand = () => {
|
||||
if (repeatCount <= 0) {
|
||||
// Done → next step
|
||||
this.client.off('data', onOutput)
|
||||
stepIndex++
|
||||
return runStep(stepIndex)
|
||||
}
|
||||
|
|
@ -183,24 +185,19 @@ export default class LineConnection {
|
|||
setTimeout(() => sendCommand(), Number(step?.delay) || 500)
|
||||
}
|
||||
|
||||
// Lắng nghe output cho expect
|
||||
const onOutput = (data: string) => {
|
||||
const output = data.toString()
|
||||
this.outputBuffer += output
|
||||
|
||||
if (output.includes(step.expect)) {
|
||||
this.client.off('data', onOutput)
|
||||
setTimeout(() => sendCommand(), Number(step?.delay) || 500)
|
||||
}
|
||||
}
|
||||
|
||||
// Nếu expect rỗng → gửi ngay
|
||||
if (!step?.expect || step?.expect.trim() === '') {
|
||||
setTimeout(() => sendCommand(), Number(step?.delay) || 500)
|
||||
return
|
||||
}
|
||||
|
||||
this.client.on('data', onOutput)
|
||||
while (this.outputBuffer) {
|
||||
await sleep(200)
|
||||
if (this.outputBuffer.includes(step.expect)) {
|
||||
this.outputBuffer = ''
|
||||
setTimeout(() => sendCommand(), Number(step?.delay) || 500)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runStep(stepIndex)
|
||||
|
|
|
|||
|
|
@ -12,3 +12,7 @@ export const cleanData = (data: string) => {
|
|||
.replace(/[^\x20-\x7E\r\n]/g, '') // Remove non-printable characters
|
||||
// .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('outlet')
|
||||
table.integer('station_id').unsigned().references('id').inTable('stations')
|
||||
table.string('apc_name').notNullable()
|
||||
table.string('apc_name')
|
||||
table.timestamps()
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ export default class extends BaseSchema {
|
|||
this.schema.createTable(this.tableName, (table) => {
|
||||
table.increments('id')
|
||||
table.string('title').notNullable()
|
||||
table.string('name').notNullable()
|
||||
table.text('body').notNullable()
|
||||
table.integer('timeout').notNullable()
|
||||
table.boolean('isReboot').defaultTo(false)
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ export class WebSocketIo {
|
|||
intervalMap: { [key: string]: NodeJS.Timeout } = {}
|
||||
stationMap: Map<number, Station> = new Map()
|
||||
lineMap: Map<number, LineConnection> = new Map() // key = lineId
|
||||
lineConnecting: number[] = [] // key = lineId
|
||||
|
||||
constructor(protected app: ApplicationService) {}
|
||||
|
||||
|
|
@ -94,15 +95,19 @@ export class WebSocketIo {
|
|||
for (const lineId of lineIds) {
|
||||
const line = this.lineMap.get(lineId)
|
||||
if (line) {
|
||||
this.lineConnecting.filter((el) => el !== lineId)
|
||||
this.setTimeoutConnect(lineId, line)
|
||||
line.writeCommand(command)
|
||||
} else {
|
||||
if (this.lineConnecting.includes(lineId)) continue
|
||||
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) {
|
||||
this.lineConnecting.filter((el) => el !== lineId)
|
||||
this.setTimeoutConnect(lineId, lineReconnect)
|
||||
lineReconnect.writeCommand(command)
|
||||
}
|
||||
|
|
@ -131,7 +136,7 @@ export class WebSocketIo {
|
|||
line.runScript(scenario)
|
||||
} else {
|
||||
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) {
|
||||
await this.connectLine(io, [linesData], stationData)
|
||||
const lineReconnect = this.lineMap.get(lineId)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<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" />
|
||||
<title>Automation Test</title>
|
||||
</head>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@
|
|||
"dependencies": {
|
||||
"@mantine/core": "^8.3.5",
|
||||
"@mantine/dates": "^8.3.5",
|
||||
"@mantine/form": "^8.3.5",
|
||||
"@mantine/hooks": "^8.3.5",
|
||||
"@mantine/notifications": "^8.3.5",
|
||||
"@tabler/icons-react": "^3.35.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
|
|
@ -1114,12 +1116,24 @@
|
|||
"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": {
|
||||
"version": "8.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.3.5.tgz",
|
||||
"integrity": "sha512-0Wf08eWLKi3WkKlxnV1W5vfuN6wcvAV2VbhQlOy0R9nrWorGTtonQF6qqBE3PnJFYF1/ZE+HkYZQ/Dr7DmYSMQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"react": "^18.x || ^19.x"
|
||||
}
|
||||
|
|
@ -2631,7 +2645,6 @@
|
|||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
|
|
@ -3115,6 +3128,15 @@
|
|||
"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": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
"dependencies": {
|
||||
"@mantine/core": "^8.3.5",
|
||||
"@mantine/dates": "^8.3.5",
|
||||
"@mantine/form": "^8.3.5",
|
||||
"@mantine/hooks": "^8.3.5",
|
||||
"@mantine/notifications": "^8.3.5",
|
||||
"@tabler/icons-react": "^3.35.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 classes from "./App.module.css";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Suspense, useEffect, useState } from "react";
|
||||
import {
|
||||
Tabs,
|
||||
Text,
|
||||
|
|
@ -16,20 +16,24 @@ import {
|
|||
ScrollArea,
|
||||
Button,
|
||||
ActionIcon,
|
||||
LoadingOverlay,
|
||||
} 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 CardLine from "./components/CardLine";
|
||||
import { IconEdit, IconSettingsPlus } from "@tabler/icons-react";
|
||||
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;
|
||||
|
||||
/**
|
||||
* Main Component
|
||||
*/
|
||||
export function App() {
|
||||
function App() {
|
||||
document.title = "Automation Test";
|
||||
const { socket } = useSocket();
|
||||
const [stations, setStations] = useState<TStation[]>([]);
|
||||
|
|
@ -45,6 +49,10 @@ export function App() {
|
|||
};
|
||||
const [showBottomShadow, setShowBottomShadow] = 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
|
||||
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(() => {
|
||||
getStation();
|
||||
getScenarios();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -121,7 +144,6 @@ export function App() {
|
|||
|
||||
return (
|
||||
<Container py="xl" w={"100%"} style={{ maxWidth: "100%" }}>
|
||||
{/* Tabs (Top Bar) */}
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onChange={(id) => setActiveTab(id?.toString() || "0")}
|
||||
|
|
@ -146,14 +168,35 @@ export function App() {
|
|||
className={classes.indicator}
|
||||
/>
|
||||
<Flex gap={"sm"}>
|
||||
<ActionIcon title="Add Station" variant="outline" color="green">
|
||||
<IconSettingsPlus />
|
||||
</ActionIcon>
|
||||
{Number(activeTab) && (
|
||||
<ActionIcon title="Edit Station" variant="outline">
|
||||
{Number(activeTab) ? (
|
||||
<ActionIcon
|
||||
title="Edit Station"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setStationEdit(
|
||||
stations.find((el) => el.id === Number(activeTab))
|
||||
);
|
||||
setIsOpenAddStation(true);
|
||||
setIsEditStation(true);
|
||||
}}
|
||||
>
|
||||
<IconEdit />
|
||||
</ActionIcon>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
<ActionIcon
|
||||
title="Add Station"
|
||||
variant="outline"
|
||||
color="green"
|
||||
onClick={() => {
|
||||
setIsOpenAddStation(true);
|
||||
setIsEditStation(false);
|
||||
setStationEdit(undefined);
|
||||
}}
|
||||
>
|
||||
<IconSettingsPlus />
|
||||
</ActionIcon>
|
||||
</Flex>
|
||||
</Tabs.List>
|
||||
|
||||
|
|
@ -249,6 +292,11 @@ export function App() {
|
|||
>
|
||||
Connect
|
||||
</Button>
|
||||
<hr style={{ width: "100%" }} />
|
||||
<DrawerScenario
|
||||
scenarios={scenarios}
|
||||
setScenarios={setScenarios}
|
||||
/>
|
||||
<ButtonDPELP
|
||||
socket={socket}
|
||||
selectedLines={selectedLines}
|
||||
|
|
@ -261,12 +309,42 @@ export function App() {
|
|||
}, 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>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Tabs.Panel>
|
||||
))}
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
|
@ -275,7 +353,18 @@ export default function Main() {
|
|||
return (
|
||||
<MantineProvider>
|
||||
<SocketProvider>
|
||||
<App />
|
||||
<Suspense
|
||||
fallback={
|
||||
<LoadingOverlay
|
||||
visible={true}
|
||||
zIndex={1000}
|
||||
overlayProps={{ radius: "sm", blur: 1 }}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Notifications position="top-right" autoClose={5000} />
|
||||
<App />
|
||||
</Suspense>
|
||||
</SocketProvider>
|
||||
</MantineProvider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
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 classes from "./Component.module.css";
|
||||
|
||||
export const ButtonDPELP = ({
|
||||
socket,
|
||||
|
|
@ -18,7 +19,7 @@ export const ButtonDPELP = ({
|
|||
disabled={isDisable}
|
||||
miw={"100px"}
|
||||
// radius="lg"
|
||||
h={"24px"}
|
||||
h={"28px"}
|
||||
mr={"5px"}
|
||||
variant="filled"
|
||||
color="#00a164"
|
||||
|
|
@ -130,3 +131,42 @@ export const ButtonDPELP = ({
|
|||
</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>
|
||||
<Text fw={600} style={{ display: "flex", gap: "4px" }}>
|
||||
Line {line.lineNumber} - {line.port}{" "}
|
||||
Line: {line.lineNumber || line.line_number} - {line.port}{" "}
|
||||
{line.status === "connected" && (
|
||||
<IconCircleCheckFilled color="green" />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -13,4 +13,8 @@
|
|||
gap: 4px;
|
||||
margin-top: 4px;
|
||||
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,
|
||||
IconKey,
|
||||
IconClipboardList,
|
||||
} from '@tabler/icons-react'
|
||||
} from "@tabler/icons-react";
|
||||
|
||||
export const SOCKET_EVENTS = {
|
||||
ROOM: {
|
||||
JOINED: 'room_joined',
|
||||
LEFT: 'room_left',
|
||||
JOINED: "room_joined",
|
||||
LEFT: "room_left",
|
||||
},
|
||||
|
||||
APP_STATUS: { RECEIVED: 'app_status_received' },
|
||||
APP_STATUS: { RECEIVED: "app_status_received" },
|
||||
|
||||
APP_DATA: {
|
||||
SENT: 'app_data_sent',
|
||||
RECEIVED: 'app_data_received',
|
||||
SENT: "app_data_sent",
|
||||
RECEIVED: "app_data_received",
|
||||
},
|
||||
|
||||
SCRIPT_TEST: {
|
||||
SENT: 'script_test_sent',
|
||||
TIME_RECEIVED: 'script_test_time_received',
|
||||
SENT: "script_test_sent",
|
||||
TIME_RECEIVED: "script_test_time_received",
|
||||
},
|
||||
|
||||
DATA_OUTPUT: { RECEIVED: 'data_output_received' },
|
||||
DATA_OUTPUT: { RECEIVED: "data_output_received" },
|
||||
NOTIFICATION: {
|
||||
FROM_APP: 'notification_send_from_app',
|
||||
SEND_ALL: 'notification_send_to_all',
|
||||
FROM_APP: "notification_send_from_app",
|
||||
SEND_ALL: "notification_send_to_all",
|
||||
},
|
||||
APC_CONTROL: {
|
||||
FROM_WEB: 'apc_control_request_from_web',
|
||||
TO_APP: 'apc_control_request_to_app',
|
||||
FROM_WEB_ALL_APC: 'all_apc_control_request_from_web',
|
||||
FROM_WEB: "apc_control_request_from_web",
|
||||
TO_APP: "apc_control_request_to_app",
|
||||
FROM_WEB_ALL_APC: "all_apc_control_request_from_web",
|
||||
},
|
||||
SYSTEM_LOG: {
|
||||
FROM_APP: 'system_log_send_from_app',
|
||||
GET_SYSTEM_LOG_FROM_WEB: 'get_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_SYSTEM_LOG_FROM_APP: 'response_system_log_from_app',
|
||||
RESPONSE_SYSTEM_LOG_TO_WEB: 'response_system_log_to_web',
|
||||
FROM_APP: "system_log_send_from_app",
|
||||
GET_SYSTEM_LOG_FROM_WEB: "get_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_SYSTEM_LOG_FROM_APP: "response_system_log_from_app",
|
||||
RESPONSE_SYSTEM_LOG_TO_WEB: "response_system_log_to_web",
|
||||
},
|
||||
CLI: {
|
||||
OPEN_CLI_LINE_FROM_WEB: 'open_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',
|
||||
CLOSE_CLI_MULTI_LINE_FROM_WEB: 'close_cli_multi_line_from_web',
|
||||
WRITE_COMMAND_FROM_WEB: 'write_command_line_from_web',
|
||||
WRITE_COMMAND_TO_APP: 'write_command_line_to_app',
|
||||
RECEIVE_COMMAND_DATA_FROM_APP: 'receive_command_data_from_app',
|
||||
RECEIVE_COMMAND_DATA_TO_WEB: 'receive_command_data_to_web',
|
||||
OPEN_CLI_LINE_FROM_WEB: "open_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",
|
||||
CLOSE_CLI_MULTI_LINE_FROM_WEB: "close_cli_multi_line_from_web",
|
||||
WRITE_COMMAND_FROM_WEB: "write_command_line_from_web",
|
||||
WRITE_COMMAND_TO_APP: "write_command_line_to_app",
|
||||
RECEIVE_COMMAND_DATA_FROM_APP: "receive_command_data_from_app",
|
||||
RECEIVE_COMMAND_DATA_TO_WEB: "receive_command_data_to_web",
|
||||
},
|
||||
RESCAN: {
|
||||
SEND_LIST_RESCAN_FROM_WEB: 'send_list_rescan_from_web',
|
||||
SEND_LIST_RESCAN_TO_APP: 'send_list_rescan_to_app',
|
||||
SEND_LIST_RESCAN_FROM_WEB: "send_list_rescan_from_web",
|
||||
SEND_LIST_RESCAN_TO_APP: "send_list_rescan_to_app",
|
||||
},
|
||||
LOCK: {
|
||||
SEND_LIST_LOCK_FROM_WEB: 'send_list_lock_from_web',
|
||||
SEND_LIST_LOCK_TO_APP: 'send_list_lock_to_app',
|
||||
SEND_LIST_LOCK_FROM_WEB: "send_list_lock_from_web",
|
||||
SEND_LIST_LOCK_TO_APP: "send_list_lock_to_app",
|
||||
},
|
||||
CHANGE_STAGE: {
|
||||
SEND_STAGE_FROM_WEB: 'send_stage_from_web',
|
||||
SEND_STAGE_TO_APP: 'send_stage_to_app',
|
||||
SEND_STAGE_FROM_WEB: "send_stage_from_web",
|
||||
SEND_STAGE_TO_APP: "send_stage_to_app",
|
||||
},
|
||||
UPDATE_PROPERTY: {
|
||||
UPDATE_PROPERTY_FROM_WEB: 'update_property_from_web',
|
||||
UPDATE_PROPERTY_TO_APP: 'update_property_to_app',
|
||||
UPDATE_PROPERTY_FROM_WEB: "update_property_from_web",
|
||||
UPDATE_PROPERTY_TO_APP: "update_property_to_app",
|
||||
},
|
||||
RUN_SCENARIOS: {
|
||||
RUN_SCENARIOS_FROM_WEB: 'run_scenarios_from_web',
|
||||
RUN_SCENARIOS_TO_APP: 'run_scenarios_to_app',
|
||||
RUN_SCENARIOS_FROM_WEB: "run_scenarios_from_web",
|
||||
RUN_SCENARIOS_TO_APP: "run_scenarios_to_app",
|
||||
},
|
||||
SEND_BREAK: {
|
||||
SEND_BREAK_FROM_WEB: 'send_break_from_web',
|
||||
SEND_BREAK_TO_APP: 'send_break_to_app',
|
||||
SEND_BREAK_FROM_WEB: "send_break_from_web",
|
||||
SEND_BREAK_TO_APP: "send_break_to_app",
|
||||
},
|
||||
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_FROM_WEB: 'leave_multi_room_from_web',
|
||||
LEAVE_MULTI_ROOM_FROM_WEB: "leave_multi_room_from_web",
|
||||
},
|
||||
TAKE_OVER: {
|
||||
TAKE_OVER_FROM_WEB: 'take_over_from_web',
|
||||
TAKE_OVER_TO_WEB: 'take_over_to_web',
|
||||
TAKE_OVER_FROM_WEB: "take_over_from_web",
|
||||
TAKE_OVER_TO_WEB: "take_over_to_web",
|
||||
},
|
||||
CONNECT_APC: {
|
||||
CONNECT_APC_FROM_WEB: 'connect_apc_from_web',
|
||||
CONNECT_APC_TO_APP: 'connect_apc_to_app',
|
||||
CONNECT_APC_FROM_WEB: "connect_apc_from_web",
|
||||
CONNECT_APC_TO_APP: "connect_apc_to_app",
|
||||
},
|
||||
DATA_APC_RECEIVED: {
|
||||
DATA_APC_RECEIVED_FROM_APP: 'data_apc_received_from_app',
|
||||
DATA_APC_RECEIVED_TO_WEB: 'data_apc_received_to_web',
|
||||
DATA_APC_RECEIVED_FROM_APP: "data_apc_received_from_app",
|
||||
DATA_APC_RECEIVED_TO_WEB: "data_apc_received_to_web",
|
||||
},
|
||||
SEND_COMMAND_TO_APC: {
|
||||
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_FROM_WEB: "send_command_to_apc_from_web",
|
||||
SEND_COMMAND_TO_APC_TO_APP: "send_command_to_apc_to_app",
|
||||
},
|
||||
SEND_CLEAR_LINE: {
|
||||
SEND_CLEAR_LINE_FROM_WEB: 'send_clear_line_from_web',
|
||||
SEND_CLEAR_LINE_TO_APP: 'send_clear_line_to_app',
|
||||
SEND_CLEAR_LINE_FROM_WEB: "send_clear_line_from_web",
|
||||
SEND_CLEAR_LINE_TO_APP: "send_clear_line_to_app",
|
||||
},
|
||||
SEND_CLOSE_LINE: {
|
||||
SEND_CLOSE_LINE_FROM_WEB: 'send_close_line_from_web',
|
||||
SEND_CLOSE_LINE_TO_APP: 'send_close_line_to_app',
|
||||
SEND_CLOSE_LINE_FROM_WEB: "send_close_line_from_web",
|
||||
SEND_CLOSE_LINE_TO_APP: "send_close_line_to_app",
|
||||
},
|
||||
SEND_OPEN_LINE: {
|
||||
SEND_OPEN_LINE_FROM_WEB: 'send_open_line_from_web',
|
||||
SEND_OPEN_LINE_TO_APP: 'send_open_line_to_app',
|
||||
SEND_OPEN_LINE_FROM_WEB: "send_open_line_from_web",
|
||||
SEND_OPEN_LINE_TO_APP: "send_open_line_to_app",
|
||||
},
|
||||
CONTROL_APP: {
|
||||
SEND_PAUSE_APP_FROM_WEB: 'send_pause_app_from_web',
|
||||
SEND_RESUME_APP_FROM_WEB: 'send_resume_app_from_web',
|
||||
SEND_RESTART_APP_FROM_WEB: 'send_restart_app_from_web',
|
||||
SEND_QUIT_APP_FROM_WEB: 'send_quit_app_from_web',
|
||||
SEND_PAUSE_APP_FROM_WEB: "send_pause_app_from_web",
|
||||
SEND_RESUME_APP_FROM_WEB: "send_resume_app_from_web",
|
||||
SEND_RESTART_APP_FROM_WEB: "send_restart_app_from_web",
|
||||
SEND_QUIT_APP_FROM_WEB: "send_quit_app_from_web",
|
||||
},
|
||||
CONNECT_SWITCH: {
|
||||
CONNECT_SWITCH_FROM_WEB: 'connect_switch_from_web',
|
||||
CONNECT_SWITCH_TO_APP: 'connect_switch_to_app',
|
||||
CONNECT_SWITCH_FROM_WEB: "connect_switch_from_web",
|
||||
CONNECT_SWITCH_TO_APP: "connect_switch_to_app",
|
||||
},
|
||||
DATA_SWITCH_RECEIVED: {
|
||||
DATA_SWITCH_RECEIVED_FROM_APP: 'data_switch_received_from_app',
|
||||
DATA_SWITCH_RECEIVED_TO_WEB: 'data_switch_received_to_web',
|
||||
DATA_SWITCH_RECEIVED_FROM_APP: "data_switch_received_from_app",
|
||||
DATA_SWITCH_RECEIVED_TO_WEB: "data_switch_received_to_web",
|
||||
},
|
||||
SEND_COMMAND_TO_SWITCH: {
|
||||
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_FROM_APP: 'send_command_to_switch_from_app',
|
||||
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_FROM_APP: "send_command_to_switch_from_app",
|
||||
},
|
||||
RELOAD_TICKET: {
|
||||
RELOAD_TICKET_FROM_WEB: 'reload_ticket_from_web',
|
||||
RELOAD_TICKET_TO_WEB: 'reload_ticket_to_web',
|
||||
RELOAD_TICKET_FROM_WEB: "reload_ticket_from_web",
|
||||
RELOAD_TICKET_TO_WEB: "reload_ticket_to_web",
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export const LINE_STATUS = {
|
||||
CHECK_INVENTORY: 'CHECK_INVENTORY',
|
||||
STATUS_TEST: 'TESTING',
|
||||
CONNECT_FAIL: 'CONNECT_FAIL',
|
||||
CONNECTED: 'CONNECTED',
|
||||
STATUS_READY: 'READY',
|
||||
STATUS_DONE: 'DONE',
|
||||
STATUS_CHECKING: 'CHECKING',
|
||||
STATUS_LOCKED: 'LOCKED',
|
||||
STATUS_CLOSED: 'CLOSED',
|
||||
STATUS_TIMEOUT: 'TIMEOUT',
|
||||
STATUS_PHYSICAL_TEST: 'STATUS_PHYSICAL_TEST',
|
||||
STATUS_PHYSICAL_TEST_DONE: 'STATUS_PHYSICAL_TEST_DONE',
|
||||
STATUS_UNDIFINED_INVEN: 'INVENTORY_UNIDENTIFIED',
|
||||
STATUS_RUNNING_SCENARIOS: 'RUNNING_SCENARIOS',
|
||||
APC_CONTROL: 'APC_CONTROL',
|
||||
STATUS_STARTING: 'STARTING',
|
||||
STATUS_TURN_OFF: 'TURN_OFF',
|
||||
STATUS_RESTARTING: 'RESTARTING',
|
||||
}
|
||||
CHECK_INVENTORY: "CHECK_INVENTORY",
|
||||
STATUS_TEST: "TESTING",
|
||||
CONNECT_FAIL: "CONNECT_FAIL",
|
||||
CONNECTED: "CONNECTED",
|
||||
STATUS_READY: "READY",
|
||||
STATUS_DONE: "DONE",
|
||||
STATUS_CHECKING: "CHECKING",
|
||||
STATUS_LOCKED: "LOCKED",
|
||||
STATUS_CLOSED: "CLOSED",
|
||||
STATUS_TIMEOUT: "TIMEOUT",
|
||||
STATUS_PHYSICAL_TEST: "STATUS_PHYSICAL_TEST",
|
||||
STATUS_PHYSICAL_TEST_DONE: "STATUS_PHYSICAL_TEST_DONE",
|
||||
STATUS_UNDIFINED_INVEN: "INVENTORY_UNIDENTIFIED",
|
||||
STATUS_RUNNING_SCENARIOS: "RUNNING_SCENARIOS",
|
||||
APC_CONTROL: "APC_CONTROL",
|
||||
STATUS_STARTING: "STARTING",
|
||||
STATUS_TURN_OFF: "TURN_OFF",
|
||||
STATUS_RESTARTING: "RESTARTING",
|
||||
};
|
||||
|
||||
export const LIST_FAVORITE_COMMANDS = [
|
||||
'sh inv',
|
||||
'sh ver',
|
||||
"sh inv",
|
||||
"sh ver",
|
||||
// 'sh diag',
|
||||
// 'sh post',
|
||||
// 'sh env',
|
||||
// 'sh log',
|
||||
// 'sh platform',
|
||||
]
|
||||
];
|
||||
|
||||
export const dataPermission = [
|
||||
{
|
||||
link: '/dashboard',
|
||||
label: 'Dashboard',
|
||||
link: "/dashboard",
|
||||
label: "Dashboard",
|
||||
icon: IconHome,
|
||||
requiredPermissions: [],
|
||||
},
|
||||
{
|
||||
link: '/station-setting',
|
||||
label: 'Station Setting',
|
||||
link: "/station-setting",
|
||||
label: "Station Setting",
|
||||
icon: IconServer,
|
||||
requiredPermissions: ['station_activity'],
|
||||
requiredPermissions: ["station_activity"],
|
||||
},
|
||||
{
|
||||
link: '/monitor',
|
||||
label: 'Monitoring',
|
||||
link: "/monitor",
|
||||
label: "Monitoring",
|
||||
icon: IconDeviceDesktop,
|
||||
requiredPermissions: [
|
||||
'monitor_power',
|
||||
'monitor_cli',
|
||||
'monitor_other_items',
|
||||
"monitor_power",
|
||||
"monitor_cli",
|
||||
"monitor_other_items",
|
||||
],
|
||||
},
|
||||
{
|
||||
link: '/control-apc',
|
||||
label: 'Control APC',
|
||||
link: "/control-apc",
|
||||
label: "Control APC",
|
||||
icon: IconSettingsAutomation,
|
||||
requiredPermissions: ['control_apc_activity'],
|
||||
requiredPermissions: ["control_apc_activity"],
|
||||
},
|
||||
{
|
||||
link: '/group-model',
|
||||
label: 'Group - Model',
|
||||
link: "/group-model",
|
||||
label: "Group - Model",
|
||||
icon: IconRouter,
|
||||
requiredPermissions: ['group_model_activity'],
|
||||
requiredPermissions: ["group_model_activity"],
|
||||
},
|
||||
{
|
||||
link: '/keyword',
|
||||
label: 'Keyword',
|
||||
link: "/keyword",
|
||||
label: "Keyword",
|
||||
icon: IconKey,
|
||||
requiredPermissions: ['keyword_activity', 'keyword_limit'],
|
||||
requiredPermissions: ["keyword_activity", "keyword_limit"],
|
||||
},
|
||||
{
|
||||
link: '/exclude-error',
|
||||
label: 'Exclude Errors',
|
||||
link: "/exclude-error",
|
||||
label: "Exclude Errors",
|
||||
icon: IconBan,
|
||||
requiredPermissions: ['exclude_error_activity', 'exclude_error_limit'],
|
||||
requiredPermissions: ["exclude_error_activity", "exclude_error_limit"],
|
||||
},
|
||||
{
|
||||
link: '/list-logs',
|
||||
label: 'List Logs',
|
||||
link: "/list-logs",
|
||||
label: "List Logs",
|
||||
icon: IconFile,
|
||||
requiredPermissions: [],
|
||||
},
|
||||
{
|
||||
link: '/webhooks',
|
||||
label: 'Webhooks',
|
||||
link: "/webhooks",
|
||||
label: "Webhooks",
|
||||
icon: IconWebhook,
|
||||
requiredPermissions: ['webhook_activity', 'webhook_add_limit'],
|
||||
requiredPermissions: ["webhook_activity", "webhook_add_limit"],
|
||||
},
|
||||
{
|
||||
link: '/scenario',
|
||||
label: 'Scenario',
|
||||
link: "/scenario",
|
||||
label: "Scenario",
|
||||
icon: IconClipboardList,
|
||||
requiredPermissions: ['scenario_activity', 'scenario_add_limit'],
|
||||
requiredPermissions: ["scenario_activity", "scenario_add_limit"],
|
||||
},
|
||||
{
|
||||
link: '/upgrade',
|
||||
label: 'Upgrade now!',
|
||||
link: "/upgrade",
|
||||
label: "Upgrade now!",
|
||||
icon: IconCrown,
|
||||
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;
|
||||
port: number;
|
||||
lineNumber: number;
|
||||
line_number?: number;
|
||||
lineClear: number;
|
||||
line_clear?: number;
|
||||
station_id: number;
|
||||
is_active: string | boolean;
|
||||
data?: string | any;
|
||||
type?: string;
|
||||
inventory?: any;
|
||||
|
|
@ -69,7 +70,6 @@ export type TLine = {
|
|||
outlet?: number;
|
||||
cliOpened?: boolean;
|
||||
systemLogUrl?: string;
|
||||
start_round_at: number;
|
||||
apc_name: string;
|
||||
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
|
||||
|
|
@ -138,3 +138,20 @@ export type LineConfig = {
|
|||
output: 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