diff --git a/src/graphics/right_bar.ts b/src/graphics/right_bar.ts index 93b807f7..e7fd7268 100644 --- a/src/graphics/right_bar.ts +++ b/src/graphics/right_bar.ts @@ -74,6 +74,20 @@ export class RightBar { } } + addToggleButton( + title: string, + details: Record, + buttonClass = "right-bar-toggle-button", + infoClass = "right-bar-info", + ) { + const container = createToggleInfo(title, details, buttonClass, infoClass); + const infoContent = document.getElementById("info-content"); + + if (infoContent) { + infoContent.appendChild(container); + } + } + // Adds a select dropdown to the right-bar addDropdown( label: string, @@ -144,6 +158,64 @@ export function createToggleTable( return container; } +export function createToggleInfo( + title: string, + details: Record, + buttonClass = "right-bar-toggle-button", + infoClass = "right-bar-info", +) { + const container = document.createElement("div"); + container.classList.add("toggle-info-container"); + + // Create toggle button + const button = document.createElement("button"); + button.classList.add(buttonClass); + button.textContent = "Show Details"; + + // Create Packet Details title + const header = document.createElement("h3"); + header.classList.toggle("hidden", true); + header.textContent = title; + + // Create info list + const list = document.createElement("ul"); + list.classList.add(infoClass, "hidden"); + + // Add details to the list + Object.entries(details).forEach(([key, value]) => { + const listItem = document.createElement("li"); + if (key === "Payload") { + // Format the payload as JSON + const pre = document.createElement("pre"); + pre.textContent = JSON.stringify(value, null, 2); // Pretty-print JSON + listItem.innerHTML = `${key}:`; + listItem.appendChild(pre); + } else { + listItem.innerHTML = `${key}: ${value}`; + } + list.appendChild(listItem); + }); + + // Toggle when clicking on button + button.onclick = () => { + const isHidden = list.classList.contains("hidden"); + list.classList.toggle("hidden", !isHidden); + list.classList.toggle("open", isHidden); + container.classList.toggle("hidden", !isHidden); + container.classList.toggle("open", isHidden); + button.classList.toggle("open", isHidden); + header.classList.toggle("hidden", !isHidden); + button.textContent = isHidden ? "Hide Details" : "Show Details"; + }; + + // Add elements to container + container.appendChild(button); + container.appendChild(header); + container.appendChild(list); + + return container; +} + export function createRightBarButton( text: string, onClick: () => void, diff --git a/src/styles/index.ts b/src/styles/index.ts index bf5b2a82..d7c4ad22 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5,4 +5,5 @@ import "./canvas.css"; import "./right-bar.css"; import "./right-bar-buttons.css"; import "./table.css"; +import "./info.css"; import "./buttons.css"; diff --git a/src/styles/info.css b/src/styles/info.css new file mode 100644 index 00000000..0536772a --- /dev/null +++ b/src/styles/info.css @@ -0,0 +1,41 @@ +/* Estilos base para el contenedor */ +.toggle-info-container { + overflow: hidden; + text-align: left; + margin: 0; + padding: 0; +} + +/* Ajustes para el texto de la lista */ +.toggle-info-container .right-bar-info { + margin: 0; + padding: 0; + list-style: none; +} + +.toggle-info-container .right-bar-info li { + margin: 20px 0px; + padding: 0; + text-align: flex; +} + +/* Estilo para el pre que contiene el payload */ +.toggle-info-container .right-bar-info li pre { + background-color: #000; + color: #fff; + padding: 10px; + border-radius: 4px; + font-family: monospace; + white-space: pre-wrap; + overflow-x: auto; +} + +/* Elementos abiertos */ +.toggle-info-container .right-bar-info.open { + display: block; +} + +/* Transiciones para una experiencia más fluida */ +.toggle-info-container .hidden { + display: none; +} diff --git a/src/types/packet.ts b/src/types/packet.ts index 016e173e..c63e0f05 100644 --- a/src/types/packet.ts +++ b/src/types/packet.ts @@ -11,7 +11,7 @@ import { RightBar, StyledInfo } from "../graphics/right_bar"; import { Position } from "./common"; import { ViewGraph } from "./graphs/viewgraph"; import { EmptyPayload, IpAddress, IPv4Packet } from "../packets/ip"; -import { EchoRequest } from "../packets/icmp"; +import { EchoRequest, EchoReply } from "../packets/icmp"; import { DeviceId, isRouter } from "./graphs/datagraph"; const contextPerPacketType: Record = { @@ -31,6 +31,7 @@ export class Packet extends Graphics { type: string; sourceId: number; destinationId: number; + private detailsVisible = false; rawPacket: IPv4Packet; @@ -82,8 +83,55 @@ export class Packet extends Graphics { this.removeHighlight(); } + private getPacketDetails(packet: IPv4Packet) { + // Creates a dictionary with the data of the packet + const packetDetails: Record = { + Version: packet.version, + "Internet Header Length": packet.internetHeaderLength, + "Type of Service": packet.typeOfService, + "Total Length": packet.totalLength, + Identification: packet.identification, + Flags: packet.flags, + "Fragment Offset": packet.fragmentOffset, + "Time to Live": packet.timeToLive, + Protocol: packet.protocol, + "Header Checksum": packet.headerChecksum, + }; + + // Add payload details if available + if (packet.payload instanceof EchoRequest) { + const echoRequest = packet.payload as EchoRequest; + packetDetails.Payload = { + type: "EchoRequest", + identifier: echoRequest.identifier, + sequenceNumber: echoRequest.sequenceNumber, + data: Array.from(echoRequest.data), + }; + } else if (packet.payload instanceof EchoReply) { + const echoReply = packet.payload as EchoReply; + packetDetails.Payload = { + type: "EchoReply", + identifier: echoReply.identifier, + sequenceNumber: echoReply.sequenceNumber, + data: Array.from(echoReply.data), + }; + } else { + packetDetails.Payload = { + type: "Unknown", + protocol: packet.payload.protocol(), + }; + } + + return packetDetails; + } + showInfo() { const rightbar = RightBar.getInstance(); + if (!rightbar) { + console.error("RightBar instance not found."); + return; + } + const info = new StyledInfo("Packet Information"); info.addField("Type", this.type); info.addField("Source ID", this.sourceId.toString()); @@ -104,6 +152,11 @@ export class Packet extends Graphics { }, "right-bar-delete-button", ); + + // Add a toggle info section for packet details + const packetDetails = this.getPacketDetails(this.rawPacket); + + rightbar.addToggleButton("Packet Details", packetDetails); } highlight() { @@ -111,6 +164,10 @@ export class Packet extends Graphics { } removeHighlight() { + if (!this.context || !contextPerPacketType[this.type]) { + console.warn("Context or packet type context is null"); + return; + } this.context = contextPerPacketType[this.type]; }