Update short log, print log

This commit is contained in:
andrew.ng 2026-05-20 10:46:34 +07:00
parent d4ea801bef
commit e9c99814b2
6 changed files with 170 additions and 28 deletions

View File

@ -2183,7 +2183,7 @@ Ports Missing/Down: ${missing.length}\n\n`
const regex = new RegExp(`\\b${sn.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\$&')}\\b`, 'g') const regex = new RegExp(`\\b${sn.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\$&')}\\b`, 'g')
result = result.replace( result = result.replace(
regex, regex,
`<span id="${escapeHtml(sn)}" style="background-color:#fbbf24;color:#78350f;font-weight:600;padding:2px 6px;border-radius:3px;cursor:pointer;" title="Click Hardware Inventory link to scroll">${escapeHtml(sn)}</span>` `<span id="${sn}" style="background-color:#fbbf24;color:#78350f;font-weight:600;padding:2px 6px;border-radius:3px;cursor:pointer;" title="Click Hardware Inventory link to scroll">${escapeHtml(sn)}</span>`
) )
} }
}) })

View File

@ -241,6 +241,7 @@ const ModalLineHistory = ({
setOpenLog(false); setOpenLog(false);
}} }}
testLogContent={selectedHistory?.output || ""} testLogContent={selectedHistory?.output || ""}
isShowShortLog={true}
/> />
</> </>
); );

View File

@ -1,16 +1,29 @@
import { Modal, Text } from "@mantine/core"; import { Button, Flex, Modal, Text } from "@mantine/core";
import classes from "../Component.module.css"; import classes from "../Component.module.css";
import { convertTimestampToDate } from "../../untils/helper"; import {
convertTimestampToDate,
createShortLog,
printLogWeb,
} from "../../untils/helper";
import { useEffect, useState } from "react";
const ModalLog = ({ const ModalLog = ({
opened, opened,
onClose, onClose,
testLogContent, testLogContent,
isShowShortLog = false,
}: { }: {
opened: boolean; opened: boolean;
onClose: () => void; onClose: () => void;
testLogContent: string; testLogContent: string;
isShowShortLog?: boolean;
}) => { }) => {
const [valueLog, setValueLog] = useState(testLogContent);
const [isShort, setIsShort] = useState(false);
useEffect(() => {
setValueLog(testLogContent);
}, [testLogContent]);
const addTooltipsToHighlights = () => { const addTooltipsToHighlights = () => {
const highlights = document.querySelectorAll(".highlight"); const highlights = document.querySelectorAll(".highlight");
highlights.forEach((highlight) => { highlights.forEach((highlight) => {
@ -60,7 +73,7 @@ const ModalLog = ({
size="90%" size="90%"
styles={{ styles={{
content: { content: {
height: "85vh", height: isShowShortLog ? "90vh" : "85vh",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
}, },
@ -72,13 +85,50 @@ const ModalLog = ({
> >
<div <div
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: highlightSystemLog(testLogContent), __html: highlightSystemLog(valueLog),
}} }}
className={`${classes.viewLog} ${classes.logLight}`} className={`${classes.viewLog} ${classes.logLight}`}
ref={(el) => { ref={(el) => {
if (el) addTooltipsToHighlights(); if (el) addTooltipsToHighlights();
}} }}
></div> ></div>
{isShowShortLog ? (
<Flex justify="flex-end" mt="md" gap={"md"}>
{!isShort ? (
<Button
variant="outline"
onClick={() => {
setValueLog(createShortLog(testLogContent));
setIsShort(true);
}}
color="green"
>
Short
</Button>
) : (
<Button
variant="outline"
onClick={() => {
setValueLog(testLogContent);
setIsShort(false);
}}
color="green"
>
Original
</Button>
)}
<Button
variant="outline"
onClick={() => printLogWeb(valueLog)}
color="blue"
>
Print
</Button>
</Flex>
) : (
""
)}
</Modal> </Modal>
); );
}; };

View File

@ -543,6 +543,7 @@ const ModalTerminal = ({
<Modal <Modal
zIndex={95} zIndex={95}
opened={opened} opened={opened}
closeOnEscape={false}
onClose={() => { onClose={() => {
if (openDrawerScenario) { if (openDrawerScenario) {
setOpenDrawerScenario(false); setOpenDrawerScenario(false);
@ -642,7 +643,7 @@ const ModalTerminal = ({
</Flex> </Flex>
</Flex> </Flex>
<Stepper <Stepper
w={600} w={"35vw"}
size="xs" size="xs"
color={isDisable ? "gray" : "cyan"} color={isDisable ? "gray" : "cyan"}
active={activeStep} active={activeStep}
@ -743,7 +744,20 @@ const ModalTerminal = ({
description={"Complete all to send report"} description={"Complete all to send report"}
></Stepper.Step> ></Stepper.Step>
</Stepper> </Stepper>
<Flex></Flex> <Flex justify={"flex-end"} w={"15vw"}>
<Button
fw={400}
disabled={isDisable}
variant="outline"
color="cyan"
size="xs"
onClick={() => {
setOpenLineHistory(true);
}}
>
View History
</Button>
</Flex>
</Flex> </Flex>
} }
> >
@ -1570,18 +1584,6 @@ const ModalTerminal = ({
> >
Send Break Send Break
</Button> </Button>
<Button
fw={400}
disabled={isDisable}
variant="outline"
color="cyan"
size="xs"
onClick={() => {
setOpenLineHistory(true);
}}
>
View History
</Button>
<Menu trigger="hover" withArrow shadow="md" position="top"> <Menu trigger="hover" withArrow shadow="md" position="top">
<Menu.Target> <Menu.Target>
<Button <Button

View File

@ -113,11 +113,11 @@ const TerminalCLI: React.FC<TerminalCLIProps> = ({
// Handle Ctrl+V (Paste) // Handle Ctrl+V (Paste)
if (e.ctrlKey && e.key.toLowerCase() === "v") return false; if (e.ctrlKey && e.key.toLowerCase() === "v") return false;
// Handle Esc // Handle Esc
if (e.key === "Escape") return false; // if (e.key === "Escape") return false;
// Handle Enter // Handle Enter
// if (e.key === "ArrowUp") handleArrowUp(); // if (e.key === "ArrowUp") handleArrowUp();
return true; // allow all other keys through return true; // allow all other keys through
} },
); );
const handleContextMenu = async (e: MouseEvent) => { const handleContextMenu = async (e: MouseEvent) => {
@ -147,11 +147,11 @@ const TerminalCLI: React.FC<TerminalCLIProps> = ({
"[CLEAR_TERMINAL_SCROLL_BACK]", "[CLEAR_TERMINAL_SCROLL_BACK]",
Array(miniSize ? 20 : 70) Array(miniSize ? 20 : 70)
.fill("\r\n") .fill("\r\n")
.join("") .join(""),
) )
.replaceAll( .replaceAll(
"[CONNECT_TO_SERVER_TFTP_FAIL]", "[CONNECT_TO_SERVER_TFTP_FAIL]",
"\x1b[41;37m CONNECT TO SERVER TFTP FAIL \x1b[0m\n" "\x1b[41;37m CONNECT TO SERVER TFTP FAIL \x1b[0m\n",
); );
terminal.current?.write(valueContent); terminal.current?.write(valueContent);
terminal.current?.scrollToBottom(); terminal.current?.scrollToBottom();
@ -182,11 +182,11 @@ const TerminalCLI: React.FC<TerminalCLIProps> = ({
"[CLEAR_TERMINAL_SCROLL_BACK]", "[CLEAR_TERMINAL_SCROLL_BACK]",
Array(miniSize ? 20 : 70) Array(miniSize ? 20 : 70)
.fill("\r\n") .fill("\r\n")
.join("") .join(""),
) )
.replaceAll( .replaceAll(
"[CONNECT_TO_SERVER_TFTP_FAIL]", "[CONNECT_TO_SERVER_TFTP_FAIL]",
"\x1b[41;37m CONNECT TO SERVER TFTP FAIL \x1b[0m\n" "\x1b[41;37m CONNECT TO SERVER TFTP FAIL \x1b[0m\n",
); );
terminal.current?.write(valueContent); terminal.current?.write(valueContent);
setIsInit(true); setIsInit(true);

View File

@ -43,7 +43,7 @@ export const convertTimestampToDate = (timestamp: number) => {
*/ */
export const useDebounce = <T extends (...args: any[]) => void>( export const useDebounce = <T extends (...args: any[]) => void>(
callback: T, callback: T,
delay: number delay: number,
) => { ) => {
const timer = useRef<ReturnType<typeof setTimeout> | null>(null); const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
@ -57,7 +57,7 @@ export const useDebounce = <T extends (...args: any[]) => void>(
callback(...args); callback(...args);
}, delay); }, delay);
}, },
[callback, delay] [callback, delay],
); );
return debouncedFn; return debouncedFn;
@ -199,7 +199,7 @@ export const bodyDPELP = [
export function convertFromKilobytesString( export function convertFromKilobytesString(
input: string, input: string,
decimals = 0 decimals = 0,
): string { ): string {
if (!input) return "0 KB"; if (!input) return "0 KB";
@ -242,3 +242,92 @@ export function convertFromKilobytesString(
return `${displayValue.toFixed(decimals)} ${units[unitIndex]}`; return `${displayValue.toFixed(decimals)} ${units[unitIndex]}`;
} }
/**
* Hàm tách rút gọn log Cisco (show inv, show version, show license)
* @param {string} rawLog - Chuỗi log thô ban đu (chứa toàn bộ kết quả terminal)
* @returns {string} Chuỗi log đã đưc rút gọn theo đnh dạng yêu cầu
*/
export function createShortLog(rawLog: string): string {
const shortLog = [];
// 1. Tách show inventory
const invRegex =
/(NAME:\s*"[^"]*",\s*DESCR:\s*"[^"]*"\r?\nPID:\s*[^,]+,\s*VID:\s*[^,]+,\s*SN:\s*\S+)/i;
const invMatch = rawLog.match(invRegex);
if (invMatch) {
shortLog.push(invMatch[1].trim());
}
// 2. Tách show version (Đã fix vụ bỏ phần thừa ở giữa)
const verRegex =
/(System image file is[^\r\n]+)[\s\S]*?(cisco\s+[a-zA-Z0-9\-]+\s+\([^)]+\)\s+processor[\s\S]*?Configuration register is 0x[0-9a-fA-F]+)/i;
const verMatch = rawLog.match(verRegex);
if (verMatch) {
shortLog.push(`${verMatch[1].trim()}\n${verMatch[2].trim()}`);
}
// 3. Tách show license
const licRegex =
/(Index\s+1\s+Feature:[\s\S]*?)(?=\r?\n[a-zA-Z0-9\-\_]+[#>]|$)/i;
const licMatch = rawLog.match(licRegex);
if (licMatch) {
shortLog.push("\n" + licMatch[1].trim());
}
return shortLog.join("\n");
}
/**
* Hàm in log ra máy in vật trong môi trường Web (React/Vue/Vanilla)
* @param shortLog - Chuỗi log đã đưc rút gọn
*/
export function printLogWeb(shortLog: string): void {
if (!shortLog.trim()) {
console.warn("Không có dữ liệu để in.");
return;
}
// Mở một cửa sổ ẩn để in
const printWindow = window.open("", "_blank", "width=800,height=600");
if (!printWindow) {
console.error("Trình duyệt đã chặn popup. Vui lòng cho phép popup để in.");
return;
}
// Ghi nội dung HTML với font monospace (giống terminal)
printWindow.document.write(`
<!DOCTYPE html>
<html>
<head>
<title>Cisco Short Log</title>
<style>
body {
font-family: 'Courier New', Courier, monospace;
font-size: 12px;
padding: 20px;
color: #000;
}
pre {
white-space: pre-wrap; /* Tự động xuống dòng nếu quá dài */
word-wrap: break-word;
}
@media print {
@page { margin: 1cm; }
body { padding: 0; }
}
</style>
</head>
<body>
<pre>${shortLog}</pre>
</body>
</html>
`);
printWindow.document.close();
printWindow.focus();
// Gọi lệnh in và đóng cửa sổ sau khi in xong (hoặc hủy)
printWindow.print();
printWindow.onafterprint = () => printWindow.close();
}