Add running scenario status to line connections

Implemented real-time tracking and display of running scenarios for line connections. Backend now emits 'running_scenario' events, and frontend components show the current scenario being executed. Also improved switch port status parsing and ensured terminal scrolls to bottom on open.
This commit is contained in:
nguyentrungthat 2025-12-02 08:55:57 +07:00
parent f60be2f543
commit 075ceb7de4
7 changed files with 96 additions and 9 deletions

View File

@ -286,6 +286,11 @@ export default class LineConnection {
if (!this.client || this.client.destroyed) {
console.log('Not connected')
this.isRunningScript = false
this.socketIO.emit('running_scenario', {
stationId: this.config.stationId,
lineId: this.config.id,
title: '',
})
this.outputBuffer = ''
return
}
@ -299,6 +304,11 @@ export default class LineConnection {
)
if (script?.title === 'DPELP') this.dataDPELP = ''
this.isRunningScript = true
this.socketIO.emit('running_scenario', {
stationId: this.config.stationId,
lineId: this.config.id,
title: script?.title,
})
const now = Date.now()
this.outputScenario += `\n\n---start-scenarios---${now}---${userName}---${script?.title}---\n---scenario---${script?.title}---${now}---\n`
appendLog(
@ -318,6 +328,11 @@ export default class LineConnection {
return new Promise((resolve, reject) => {
const timeoutTimer = setTimeout(() => {
this.isRunningScript = false
this.socketIO.emit('running_scenario', {
stationId: this.config.stationId,
lineId: this.config.id,
title: '',
})
this.outputBuffer = ''
this.outputScenario = ''
this.config.output += 'Timeout run scenario'
@ -347,6 +362,11 @@ export default class LineConnection {
return
} else clearTimeout(timeoutTimer)
this.isRunningScript = false
this.socketIO.emit('running_scenario', {
stationId: this.config.stationId,
lineId: this.config.id,
title: '',
})
this.outputBuffer = ''
this.outputScenario += `\n---end-scenarios---${now}---${userName}---\n`
appendLog(
@ -676,7 +696,7 @@ export default class LineConnection {
(Tóm tắt trạng thái tổng thể của hệ thống trong 24 ý)
issue:
(Tóm tắt cực ngắn gọn các lỗi/dấu hiệu bất thường, mỗi vấn đ 1 dòng)
(Tóm tắt cực ngắn gọn các lỗi/dấu hiệu bất thường, mỗi vấn đ 1 dòng, bỏ qua các vấn đ không quan trọng như về port up/down hay Invalid input, Incomplete command)
Quy tắc:
Không giải thích dài dòng.

View File

@ -281,7 +281,7 @@ export default class SwitchController {
public async getPorts(): Promise<boolean> {
this._send(' terminal length 0')
this._send('show interface status')
this._send('show interface')
this._send(' ')
await this.sleep(2000)
const statusOutput = this.buffer
@ -289,17 +289,25 @@ export default class SwitchController {
const lines = statusOutput.split('\n')
const ports = this.ports?.length > 0 ? [...this.ports] : []
for (const line of lines) {
const match = line.match(/^(\S+)\s+(connected|notconnect|disabled|inactive)/i)
// Match: "Gi0/1 is up, line protocol is up"
const match = line.match(
/^(TenGigabitEthernet|GigabitEthernet|FastEthernet|Ethernet)\S*\s+is\s+(\S+),\s+line protocol is\s+(\S+)/i
)
if (match) {
const name = match[1]
const rawStatus = match[2].toLowerCase()
const status = rawStatus === 'connected' ? 'ON' : 'OFF'
const name = match[1] + line.split(' ')[0].replace(match[1], '')
const status1 = match[2].toLowerCase() // up / down / administratively
const status2 = match[3].toLowerCase() // up / down
// Rule: interface is considered ON only when both are "up"
const status = status1 === 'up' && status2 === 'up' ? 'ON' : 'OFF'
const port = ports.find((p) => p.name === name)
if (port) {
port.status = status
} else ports.push({ name, status, poe: 'UNKNOWN' })
} else {
ports.push({ name, status, poe: 'UNKNOWN' })
}
}
}

View File

@ -346,6 +346,16 @@ function App() {
}, 100);
});
socket?.on("running_scenario", (data) => {
setTimeout(() => {
updateValueLineStation(
data?.lineId,
{ runningScenario: data?.title || "" },
data?.stationId
);
}, 100);
});
// ✅ cleanup on unmount or when socket changes
return () => {
socket.off("init");
@ -359,6 +369,9 @@ function App() {
socket.off("response_content_log");
socket.off("data_textfsm");
socket.off("update_ticket");
socket.off("update_baud");
socket.off("line_connecting");
socket.off("running_scenario");
};
}, [socket, stations, selectedLine]);

View File

@ -284,6 +284,19 @@ const CardLine = ({
connecting...
</motion.div>
)}
{line?.runningScenario && (
<motion.div
style={{ fontSize: "11px", color: "red" }}
animate={{ opacity: [0.2, 1, 0.2] }}
transition={{
duration: 1.2,
repeat: Infinity,
ease: "easeInOut",
}}
>
Running {line?.runningScenario}
</motion.div>
)}
</Flex>
<Flex justify={"space-between"} w={"100%"} display={"none"}>
<Box>

View File

@ -33,6 +33,7 @@ import axios from "axios";
import { notifications } from "@mantine/notifications";
import classes from "./Component.module.css";
import { listBaudDefault } from "../untils/constanst";
import { motion } from "motion/react";
const apiUrl = import.meta.env.VITE_BACKEND_URL;
const INIT_TICKET = {
@ -427,6 +428,34 @@ const ModalTerminal = ({
>
<Grid>
<Grid.Col span={3}>
<Flex style={{ height: "20px" }}>
{line?.connecting && (
<motion.div
style={{ fontSize: "12px", color: "red" }}
animate={{ opacity: [0.2, 1, 0.2] }}
transition={{
duration: 1.2,
repeat: Infinity,
ease: "easeInOut",
}}
>
connecting...
</motion.div>
)}
{line?.runningScenario && (
<motion.div
style={{ fontSize: "12px", color: "red" }}
animate={{ opacity: [0.2, 1, 0.2] }}
transition={{
duration: 1.2,
repeat: Infinity,
ease: "easeInOut",
}}
>
Running {line?.runningScenario}
</motion.div>
)}
</Flex>
<Flex justify={"space-between"} direction={"column"} h={"95%"}>
<Box>
<Flex gap={"sm"} justify={"center"} align={"center"}>

View File

@ -134,7 +134,10 @@ const TerminalCLI: React.FC<TerminalCLIProps> = ({
useEffect(() => {
if (cliOpened && isInit) {
if (terminal.current)
setTimeout(() => terminal.current?.write(content), 200);
setTimeout(() => {
terminal.current?.write(content);
terminal.current?.scrollToBottom();
}, 200);
if (fitRef.current) setTimeout(() => fitRef.current?.fit(), 500);
}

View File

@ -101,6 +101,7 @@ export type TLine = {
baud?: number;
tickets?: TDataTicket[];
connecting?: boolean;
runningScenario?: string;
};
export type TUser = {