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) { if (!this.client || this.client.destroyed) {
console.log('Not connected') console.log('Not connected')
this.isRunningScript = false this.isRunningScript = false
this.socketIO.emit('running_scenario', {
stationId: this.config.stationId,
lineId: this.config.id,
title: '',
})
this.outputBuffer = '' this.outputBuffer = ''
return return
} }
@ -299,6 +304,11 @@ export default class LineConnection {
) )
if (script?.title === 'DPELP') this.dataDPELP = '' if (script?.title === 'DPELP') this.dataDPELP = ''
this.isRunningScript = true this.isRunningScript = true
this.socketIO.emit('running_scenario', {
stationId: this.config.stationId,
lineId: this.config.id,
title: script?.title,
})
const now = Date.now() const now = Date.now()
this.outputScenario += `\n\n---start-scenarios---${now}---${userName}---${script?.title}---\n---scenario---${script?.title}---${now}---\n` this.outputScenario += `\n\n---start-scenarios---${now}---${userName}---${script?.title}---\n---scenario---${script?.title}---${now}---\n`
appendLog( appendLog(
@ -318,6 +328,11 @@ export default class LineConnection {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const timeoutTimer = setTimeout(() => { const timeoutTimer = setTimeout(() => {
this.isRunningScript = false this.isRunningScript = false
this.socketIO.emit('running_scenario', {
stationId: this.config.stationId,
lineId: this.config.id,
title: '',
})
this.outputBuffer = '' this.outputBuffer = ''
this.outputScenario = '' this.outputScenario = ''
this.config.output += 'Timeout run scenario' this.config.output += 'Timeout run scenario'
@ -347,6 +362,11 @@ export default class LineConnection {
return return
} else clearTimeout(timeoutTimer) } else clearTimeout(timeoutTimer)
this.isRunningScript = false this.isRunningScript = false
this.socketIO.emit('running_scenario', {
stationId: this.config.stationId,
lineId: this.config.id,
title: '',
})
this.outputBuffer = '' this.outputBuffer = ''
this.outputScenario += `\n---end-scenarios---${now}---${userName}---\n` this.outputScenario += `\n---end-scenarios---${now}---${userName}---\n`
appendLog( 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 ý) (Tóm tắt trạng thái tổng thể của hệ thống trong 24 ý)
issue: 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: Quy tắc:
Không giải thích dài dòng. 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> { public async getPorts(): Promise<boolean> {
this._send(' terminal length 0') this._send(' terminal length 0')
this._send('show interface status') this._send('show interface')
this._send(' ') this._send(' ')
await this.sleep(2000) await this.sleep(2000)
const statusOutput = this.buffer const statusOutput = this.buffer
@ -289,17 +289,25 @@ export default class SwitchController {
const lines = statusOutput.split('\n') const lines = statusOutput.split('\n')
const ports = this.ports?.length > 0 ? [...this.ports] : [] const ports = this.ports?.length > 0 ? [...this.ports] : []
for (const line of lines) { 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) { if (match) {
const name = match[1] const name = match[1] + line.split(' ')[0].replace(match[1], '')
const rawStatus = match[2].toLowerCase() const status1 = match[2].toLowerCase() // up / down / administratively
const status = rawStatus === 'connected' ? 'ON' : 'OFF' 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) const port = ports.find((p) => p.name === name)
if (port) { if (port) {
port.status = status 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); }, 100);
}); });
socket?.on("running_scenario", (data) => {
setTimeout(() => {
updateValueLineStation(
data?.lineId,
{ runningScenario: data?.title || "" },
data?.stationId
);
}, 100);
});
// ✅ cleanup on unmount or when socket changes // ✅ cleanup on unmount or when socket changes
return () => { return () => {
socket.off("init"); socket.off("init");
@ -359,6 +369,9 @@ function App() {
socket.off("response_content_log"); socket.off("response_content_log");
socket.off("data_textfsm"); socket.off("data_textfsm");
socket.off("update_ticket"); socket.off("update_ticket");
socket.off("update_baud");
socket.off("line_connecting");
socket.off("running_scenario");
}; };
}, [socket, stations, selectedLine]); }, [socket, stations, selectedLine]);

View File

@ -284,6 +284,19 @@ const CardLine = ({
connecting... connecting...
</motion.div> </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>
<Flex justify={"space-between"} w={"100%"} display={"none"}> <Flex justify={"space-between"} w={"100%"} display={"none"}>
<Box> <Box>

View File

@ -33,6 +33,7 @@ import axios from "axios";
import { notifications } from "@mantine/notifications"; import { notifications } from "@mantine/notifications";
import classes from "./Component.module.css"; import classes from "./Component.module.css";
import { listBaudDefault } from "../untils/constanst"; import { listBaudDefault } from "../untils/constanst";
import { motion } from "motion/react";
const apiUrl = import.meta.env.VITE_BACKEND_URL; const apiUrl = import.meta.env.VITE_BACKEND_URL;
const INIT_TICKET = { const INIT_TICKET = {
@ -427,6 +428,34 @@ const ModalTerminal = ({
> >
<Grid> <Grid>
<Grid.Col span={3}> <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%"}> <Flex justify={"space-between"} direction={"column"} h={"95%"}>
<Box> <Box>
<Flex gap={"sm"} justify={"center"} align={"center"}> <Flex gap={"sm"} justify={"center"} align={"center"}>

View File

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

View File

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