Clear terminal scrollback; format report output

Append a CLEAR_TERMINAL_SCROLL_BACK marker on disconnect and consume it in the frontend to effectively clear terminal scrollback. Backend: add marker on client close and in socket provider, import convertFromKilobytesString and use it to format MEMORY and FLASH values in the report, and compute/list missing ports (separating PoE vs SFP) in the summary HTML. Frontend: emit the marker on line_disconnected and have TerminalXTerm replace the marker with multiple newlines (varying by miniSize) before writing/initializing terminal content to scroll to bottom.
This commit is contained in:
nguyentrungthat 2026-02-11 10:11:34 +07:00
parent 0cc78e8715
commit 7d3c5ad069
4 changed files with 45 additions and 5 deletions

View File

@ -6,6 +6,7 @@ import {
buildBody, buildBody,
classifyLog, classifyLog,
cleanData, cleanData,
convertFromKilobytesString,
detectConfigRamByModel, detectConfigRamByModel,
detectScenarioByModel, detectScenarioByModel,
escapeHtml, escapeHtml,
@ -282,6 +283,7 @@ export default class LineConnection {
this.client.on('close', async () => { this.client.on('close', async () => {
console.log(`[${Date.now()}] 🔌 Line ${lineNumber} disconnected`) console.log(`[${Date.now()}] 🔌 Line ${lineNumber} disconnected`)
this.config.status = 'disconnected' this.config.status = 'disconnected'
this.config.output += this.config.output + '[CLEAR_TERMINAL_SCROLL_BACK]'
// this.config.inventory = undefined // this.config.inventory = undefined
this.socketIO.emit('line_disconnected', { this.socketIO.emit('line_disconnected', {
stationId, stationId,
@ -1693,6 +1695,9 @@ ${log}
sendReportSummary = async () => { sendReportSummary = async () => {
const portPhysical = Array.from(this.physicalTest.ports.values()) const portPhysical = Array.from(this.physicalTest.ports.values())
const missing = portPhysical.filter((p) => !p.tested)
const missingPoE = missing.filter((p) => !p.name.includes('SFP'))
const missingSFP = missing.filter((p) => p.name.includes('SFP'))
const showVersion = this.config?.data?.find( const showVersion = this.config?.data?.find(
(d) => d.command?.trim()?.includes('show ver') || d.command?.trim()?.includes('sh ver') (d) => d.command?.trim()?.includes('show ver') || d.command?.trim()?.includes('sh ver')
) )
@ -1718,8 +1723,8 @@ ${log}
Serial Number: <b>${this.config?.inventory?.sn ?? 'N/A'}</b><br/> Serial Number: <b>${this.config?.inventory?.sn ?? 'N/A'}</b><br/>
MAC: <b>${dataShowVersion?.MAC_ADDRESS ?? ''}</b><br/> MAC: <b>${dataShowVersion?.MAC_ADDRESS ?? ''}</b><br/>
IOS: <b>${dataShowVersion?.SOFTWARE_IMAGE ?? ''}</b> <b>${dataShowVersion?.VERSION ?? ''}</b><br/> IOS: <b>${dataShowVersion?.SOFTWARE_IMAGE ?? ''}</b> <b>${dataShowVersion?.VERSION ?? ''}</b><br/>
MEM: <b>${dataShowVersion?.MEMORY ?? ''}</b><br/> MEM: <b>${dataShowVersion?.MEMORY ? convertFromKilobytesString(dataShowVersion?.MEMORY) : ''}</b><br/>
FLASH: <b>${dataShowVersion?.USB_FLASH ?? ''}</b><br/> FLASH: <b>${dataShowVersion?.USB_FLASH ? convertFromKilobytesString(dataShowVersion?.USB_FLASH) : ''}</b><br/>
Licenses: <b>${ Licenses: <b>${
dataShowLic dataShowLic
? dataShowLic ? dataShowLic
@ -1735,6 +1740,23 @@ ${log}
Total Ports: ${portPhysical?.length}<br/> Total Ports: ${portPhysical?.length}<br/>
Ports Tested (Link UP): <b style="color: #008000;">${portPhysical.filter((p) => p.tested).length}</b><br/> Ports Tested (Link UP): <b style="color: #008000;">${portPhysical.filter((p) => p.tested).length}</b><br/>
Ports Missing/Down: <b style="color: #ff0000;">${portPhysical.filter((p) => !p.tested).length}</b><br/> Ports Missing/Down: <b style="color: #ff0000;">${portPhysical.filter((p) => !p.tested).length}</b><br/>
${
missingPoE?.length
? `
<br/><b style="color: #ff0000;">Ports Missing PoE</b><br/>
<br/>
<div style="column-count: 12;">${missingPoE.map((p) => this.physicalTest.normalizePortName(p.name)).join('<br/>')}</div>
`
: ''
}
${
missingSFP?.length
? `
<br/><b style="color: #ff0000;">Ports Missing SFP</b><br/>
<br/>
<div style="column-count: 12;">${missingSFP.map((p) => this.physicalTest.normalizePortName(p.name)).join('<br/>')}</div>`
: ''
}
</td> </td>
</tr> </tr>
</table>` </table>`

View File

@ -140,6 +140,7 @@ export class WebSocketIo {
if (config.status !== 'connected') { if (config.status !== 'connected') {
config.runningScenario = '' config.runningScenario = ''
config.runningPhysical = false config.runningPhysical = false
config.output = config.output + '[CLEAR_TERMINAL_SCROLL_BACK]'
} }
return config return config
}) })

View File

@ -250,7 +250,12 @@ function App() {
socket.on("line_disconnected", (data) => socket.on("line_disconnected", (data) =>
updateValueLineStation( updateValueLineStation(
data?.lineId, data?.lineId,
{ status: data.status, connecting: false }, {
status: data.status,
connecting: false,
netOutput: "[CLEAR_TERMINAL_SCROLL_BACK]",
output: "[CLEAR_TERMINAL_SCROLL_BACK]",
},
data?.stationId data?.stationId
) )
); );

View File

@ -142,7 +142,13 @@ const TerminalCLI: React.FC<TerminalCLIProps> = ({
if (cliOpened && isInit) { if (cliOpened && isInit) {
if (terminal.current) if (terminal.current)
setTimeout(() => { setTimeout(() => {
terminal.current?.write(content); const valueContent = content.replaceAll(
"[CLEAR_TERMINAL_SCROLL_BACK]",
Array(miniSize ? 20 : 70)
.fill("\r\n")
.join("")
);
terminal.current?.write(valueContent);
terminal.current?.scrollToBottom(); terminal.current?.scrollToBottom();
}, 200); }, 200);
@ -166,7 +172,13 @@ const TerminalCLI: React.FC<TerminalCLIProps> = ({
useEffect(() => { useEffect(() => {
if (!loading && !isInit) { if (!loading && !isInit) {
if (terminal.current) { if (terminal.current) {
terminal.current?.write(initContent); const valueContent = initContent.replaceAll(
"[CLEAR_TERMINAL_SCROLL_BACK]",
Array(miniSize ? 20 : 70)
.fill("\r\n")
.join("")
);
terminal.current?.write(valueContent);
setIsInit(true); setIsInit(true);
if (!miniSize && !isDisabled) terminal.current?.focus(); if (!miniSize && !isDisabled) terminal.current?.focus();
setTimeout(() => { setTimeout(() => {