From d4f6a6e6242a9897cdb8c580d979b71b3cd84ebe Mon Sep 17 00:00:00 2001 From: pgallino Date: Tue, 27 May 2025 21:59:16 -0300 Subject: [PATCH 01/15] feat: add HTTP server icon display and tooltip management in ViewHost --- src/programs/http_client.ts | 3 ++- src/types/view-devices/vHost.ts | 48 ++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/programs/http_client.ts b/src/programs/http_client.ts index 3f88749a..bc284936 100644 --- a/src/programs/http_client.ts +++ b/src/programs/http_client.ts @@ -178,7 +178,7 @@ export class HttpServer extends ProgramBase { ); return; } - + srcDevice.showHttpServerIcon(); const listener = await this.runner.tcpListenOn(this.port); if (!listener) { showError(`Port ${this.port} already in use`); @@ -196,6 +196,7 @@ export class HttpServer extends ProgramBase { this.serveClient(socket); } listener.close(); + srcDevice.hideHttpServerIcon(); } // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/src/types/view-devices/vHost.ts b/src/types/view-devices/vHost.ts index 9a6bdcd7..2d94c336 100644 --- a/src/types/view-devices/vHost.ts +++ b/src/types/view-devices/vHost.ts @@ -14,7 +14,7 @@ import { Program, RunningProgram, } from "../../programs"; -import { Texture } from "pixi.js"; +import { Texture, Text } from "pixi.js"; import { EthernetFrame } from "../../packets/ethernet"; import { GlobalContext } from "../../context"; import { DataHost } from "../data-devices"; @@ -26,6 +26,7 @@ import { TcpModule, TcpSocket, } from "../network-modules/tcpModule"; +import { hideTooltip, removeTooltip, showTooltip } from "../../graphics/renderables/canvas_tooltip_manager"; export class ViewHost extends ViewNetworkDevice { static DEVICE_TEXTURE: Texture; @@ -39,6 +40,8 @@ export class ViewHost extends ViewNetworkDevice { private runningPrograms = new Map(); private lastProgramId = 0; + private httpServerIcon?: Text; + private httpServerIconTooltip?: Text; constructor( id: DeviceId, @@ -173,6 +176,49 @@ export class ViewHost extends ViewNetworkDevice { this.runningPrograms.clear(); } + showHttpServerIcon() { + if (this.httpServerIcon) return; // Already shown + + this.httpServerIcon = new Text("🌐"); + this.httpServerIcon.style = { fontSize: 20 }; + this.httpServerIcon.anchor.set(0, 0.5); + this.httpServerIcon.x = -13; + this.httpServerIcon.y = -this.height / 2 - 20; + this.httpServerIcon.zIndex = 100; + this.httpServerIcon.eventMode = "static"; + this.httpServerIcon.interactive = true; + this.httpServerIcon.cursor = "pointer"; + + // Tooltip Pixi + this.httpServerIcon.on("pointerover", () => { + // Puedes ajustar la posición del tooltip según tu preferencia + this.httpServerIconTooltip = showTooltip( + this, + "HTTP Server", + 7, + - 60, + this.httpServerIconTooltip + ); + }); + this.httpServerIcon.on("pointerout", () => { + hideTooltip(this.httpServerIconTooltip); + }); + + this.addChild(this.httpServerIcon); + } + + hideHttpServerIcon() { + if (this.httpServerIcon) { + this.removeChild(this.httpServerIcon); + this.httpServerIcon.destroy(); + this.httpServerIcon = undefined; + } + if (this.httpServerIconTooltip) { + removeTooltip(this, this.httpServerIconTooltip); + this.httpServerIconTooltip = undefined; + } + } + // TCP private tcpModule = new TcpModule(this); From 4683a138a08053fe751f839891999ed37e887550 Mon Sep 17 00:00:00 2001 From: pgallino Date: Wed, 28 May 2025 12:18:19 -0300 Subject: [PATCH 02/15] feat: enhance ViewHost with HTTP server icon and tooltip improvements --- src/types/view-devices/vHost.ts | 33 +++++++++++++++++++++------------ src/utils/utils.ts | 16 +++++++++++++++- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/types/view-devices/vHost.ts b/src/types/view-devices/vHost.ts index 2d94c336..9c6ab920 100644 --- a/src/types/view-devices/vHost.ts +++ b/src/types/view-devices/vHost.ts @@ -14,7 +14,7 @@ import { Program, RunningProgram, } from "../../programs"; -import { Texture, Text } from "pixi.js"; +import { Texture, Text, TextStyle } from "pixi.js"; import { EthernetFrame } from "../../packets/ethernet"; import { GlobalContext } from "../../context"; import { DataHost } from "../data-devices"; @@ -26,7 +26,12 @@ import { TcpModule, TcpSocket, } from "../network-modules/tcpModule"; -import { hideTooltip, removeTooltip, showTooltip } from "../../graphics/renderables/canvas_tooltip_manager"; +import { + hideTooltip, + removeTooltip, + showTooltip, +} from "../../graphics/renderables/canvas_tooltip_manager"; +import { blockPointerEvents } from "../../utils/utils"; export class ViewHost extends ViewNetworkDevice { static DEVICE_TEXTURE: Texture; @@ -179,11 +184,14 @@ export class ViewHost extends ViewNetworkDevice { showHttpServerIcon() { if (this.httpServerIcon) return; // Already shown - this.httpServerIcon = new Text("🌐"); - this.httpServerIcon.style = { fontSize: 20 }; - this.httpServerIcon.anchor.set(0, 0.5); - this.httpServerIcon.x = -13; - this.httpServerIcon.y = -this.height / 2 - 20; + const textStyle = new TextStyle({ + fontSize: 20, + }); + + this.httpServerIcon = new Text({ text: "🌐", style: textStyle }); + this.httpServerIcon.anchor.set(0.5, 1); + this.httpServerIcon.x = 0; + this.httpServerIcon.y = -this.height / 2 - 5; this.httpServerIcon.zIndex = 100; this.httpServerIcon.eventMode = "static"; this.httpServerIcon.interactive = true; @@ -191,19 +199,20 @@ export class ViewHost extends ViewNetworkDevice { // Tooltip Pixi this.httpServerIcon.on("pointerover", () => { - // Puedes ajustar la posición del tooltip según tu preferencia this.httpServerIconTooltip = showTooltip( this, - "HTTP Server", - 7, - - 60, - this.httpServerIconTooltip + "HTTP SERVER", + 0, + -this.height / 2 - 35, + this.httpServerIconTooltip, ); }); this.httpServerIcon.on("pointerout", () => { hideTooltip(this.httpServerIconTooltip); }); + blockPointerEvents(this.httpServerIcon); + this.addChild(this.httpServerIcon); } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index f14598c3..fe081179 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,4 +1,9 @@ -import { Application, GraphicsContext, RenderTexture } from "pixi.js"; +import { + Application, + GraphicsContext, + RenderTexture, + Container, +} from "pixi.js"; import { Viewport } from "../graphics/viewport"; import { ALERT_MESSAGES } from "./constants/alert_constants"; import { showError } from "../graphics/renderables/alert_manager"; @@ -82,3 +87,12 @@ export function captureAndDownloadViewport( link.click(); }, "image/png"); } + +export function blockPointerEvents(obj: Container) { + obj.on("pointerdown", (e) => e.stopPropagation()); + obj.on("pointerup", (e) => e.stopPropagation()); + obj.on("pointerupoutside", (e) => e.stopPropagation()); + obj.on("pointertap", (e) => e.stopPropagation()); + obj.on("pointermove", (e) => e.stopPropagation()); + obj.on("click", (e) => e.stopPropagation()); +} From 9fd6fedb933705144900b68b964d41fdd2589cd5 Mon Sep 17 00:00:00 2001 From: pgallino Date: Wed, 28 May 2025 18:00:41 -0300 Subject: [PATCH 03/15] feat: implement device icon management and tooltip functionality in ViewDevice --- src/types/view-devices/vDevice.ts | 46 +++++++++++++++++++++++- src/types/view-devices/vHost.ts | 58 +++++-------------------------- src/utils/utils.ts | 40 +++++++++++++++++++++ 3 files changed, 93 insertions(+), 51 deletions(-) diff --git a/src/types/view-devices/vDevice.ts b/src/types/view-devices/vDevice.ts index d0610363..1de999a3 100644 --- a/src/types/view-devices/vDevice.ts +++ b/src/types/view-devices/vDevice.ts @@ -15,7 +15,7 @@ import { urManager, } from "../viewportManager"; import { RightBar } from "../../graphics/right_bar"; -import { Colors, ZIndexLevels } from "../../utils/utils"; +import { Colors, createDeviceIcon, ZIndexLevels } from "../../utils/utils"; import { Position } from "../common"; import { DeviceInfo } from "../../graphics/renderables/device_info"; import { @@ -68,6 +68,8 @@ export abstract class ViewDevice extends Container { private circleGraphic?: Graphics; private idLabel?: Text; private isVisibleFlag = true; // Flag to track visibility + private deviceIcons: Record = {}; + private deviceTooltips: Record = {}; readonly id: DeviceId; readonly viewgraph: ViewGraph; @@ -400,6 +402,48 @@ export abstract class ViewDevice extends Container { RightBar.getInstance().renderInfo(new DeviceInfo(this)); } + showDeviceIcon( + iconKey: string, + emoji: string, + yOffset: number, + tooltipText?: string, + ) { + if (this.deviceIcons[iconKey]) return; + const icon = createDeviceIcon(emoji, yOffset); + this.deviceIcons[iconKey] = icon; + + if (tooltipText) { + icon.on("pointerover", () => { + this.deviceTooltips[iconKey] = showTooltip( + this, + tooltipText, + 0, + yOffset - 30, + this.deviceTooltips[iconKey], + ); + }); + icon.on("pointerout", () => { + hideTooltip(this.deviceTooltips[iconKey]); + }); + } + + this.addChild(icon); + } + + hideDeviceIcon(iconKey: string) { + const icon = this.deviceIcons[iconKey]; + if (icon) { + this.removeChild(icon); + icon.destroy(); + this.deviceIcons[iconKey] = undefined; + } + const tooltip = this.deviceTooltips[iconKey]; + if (tooltip) { + removeTooltip(this, tooltip); + this.deviceTooltips[iconKey] = undefined; + } + } + select() { if (this.isDragCircle) return; this.highlight(); // Calls highlight on select diff --git a/src/types/view-devices/vHost.ts b/src/types/view-devices/vHost.ts index 9c6ab920..d12afa42 100644 --- a/src/types/view-devices/vHost.ts +++ b/src/types/view-devices/vHost.ts @@ -14,7 +14,7 @@ import { Program, RunningProgram, } from "../../programs"; -import { Texture, Text, TextStyle } from "pixi.js"; +import { Texture } from "pixi.js"; import { EthernetFrame } from "../../packets/ethernet"; import { GlobalContext } from "../../context"; import { DataHost } from "../data-devices"; @@ -26,12 +26,6 @@ import { TcpModule, TcpSocket, } from "../network-modules/tcpModule"; -import { - hideTooltip, - removeTooltip, - showTooltip, -} from "../../graphics/renderables/canvas_tooltip_manager"; -import { blockPointerEvents } from "../../utils/utils"; export class ViewHost extends ViewNetworkDevice { static DEVICE_TEXTURE: Texture; @@ -45,8 +39,6 @@ export class ViewHost extends ViewNetworkDevice { private runningPrograms = new Map(); private lastProgramId = 0; - private httpServerIcon?: Text; - private httpServerIconTooltip?: Text; constructor( id: DeviceId, @@ -182,50 +174,16 @@ export class ViewHost extends ViewNetworkDevice { } showHttpServerIcon() { - if (this.httpServerIcon) return; // Already shown - - const textStyle = new TextStyle({ - fontSize: 20, - }); - - this.httpServerIcon = new Text({ text: "🌐", style: textStyle }); - this.httpServerIcon.anchor.set(0.5, 1); - this.httpServerIcon.x = 0; - this.httpServerIcon.y = -this.height / 2 - 5; - this.httpServerIcon.zIndex = 100; - this.httpServerIcon.eventMode = "static"; - this.httpServerIcon.interactive = true; - this.httpServerIcon.cursor = "pointer"; - - // Tooltip Pixi - this.httpServerIcon.on("pointerover", () => { - this.httpServerIconTooltip = showTooltip( - this, - "HTTP SERVER", - 0, - -this.height / 2 - 35, - this.httpServerIconTooltip, - ); - }); - this.httpServerIcon.on("pointerout", () => { - hideTooltip(this.httpServerIconTooltip); - }); - - blockPointerEvents(this.httpServerIcon); - - this.addChild(this.httpServerIcon); + this.showDeviceIcon( + "httpServer", + "🌐", + -this.height / 2 - 5, + "HTTP Server", + ); } hideHttpServerIcon() { - if (this.httpServerIcon) { - this.removeChild(this.httpServerIcon); - this.httpServerIcon.destroy(); - this.httpServerIcon = undefined; - } - if (this.httpServerIconTooltip) { - removeTooltip(this, this.httpServerIconTooltip); - this.httpServerIconTooltip = undefined; - } + this.hideDeviceIcon("httpServer"); } // TCP diff --git a/src/utils/utils.ts b/src/utils/utils.ts index fe081179..abb0d0db 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -3,6 +3,8 @@ import { GraphicsContext, RenderTexture, Container, + TextStyle, + Text, } from "pixi.js"; import { Viewport } from "../graphics/viewport"; import { ALERT_MESSAGES } from "./constants/alert_constants"; @@ -96,3 +98,41 @@ export function blockPointerEvents(obj: Container) { obj.on("pointermove", (e) => e.stopPropagation()); obj.on("click", (e) => e.stopPropagation()); } + +/** + * Creates a PixiJS emoji icon centered above the device. + * @param emoji The emoji or text to display (e.g., "🌐") + * @param yOffset Vertical offset from the center of the device (e.g., -this.height / 2 - 5) + * @param fontSize Font size (optional, default 20) + * @returns A Pixi.Text instance ready to be added as a child + */ +export function createDeviceIcon( + emoji: string, + yOffset: number, + fontSize = 20, +): Text { + const textStyle = new TextStyle({ + fontSize, + }); + + const icon = new Text({ text: emoji, style: textStyle }); + icon.anchor.set(0.5, 1); + icon.x = 1; + icon.y = yOffset; + icon.zIndex = 100; + icon.eventMode = "static"; + icon.interactive = true; + icon.cursor = "pointer"; + + blockPointerEvents(icon); + return icon; +} + +/** + * Changes the emoji/text of a PixiJS Text icon. + * @param icon The Pixi.Text instance. + * @param emoji The emoji or text to display. + */ +export function setIconEmoji(icon: Text, emoji: string) { + icon.text = emoji; +} From 78fd2a62bc8ed3d64d63a5cc142612614b333e6c Mon Sep 17 00:00:00 2001 From: pgallino Date: Wed, 28 May 2025 18:11:59 -0300 Subject: [PATCH 04/15] feat: add device icon visibility for full packet queue in ViewRouter --- src/types/view-devices/vRouter.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/types/view-devices/vRouter.ts b/src/types/view-devices/vRouter.ts index f4273ace..69843594 100644 --- a/src/types/view-devices/vRouter.ts +++ b/src/types/view-devices/vRouter.ts @@ -178,11 +178,16 @@ export class ViewRouter extends ViewNetworkDevice { const wasEmpty = this.packetQueue.isEmpty(); if (!this.packetQueue.enqueue(datagram)) { console.debug("Packet queue full, dropping packet"); + // Mostrar icono de "cola llena" + this.showDeviceIcon("queueFull", "❗", -this.height / 2 - 5, "Queue full"); // dummy values const dummyMac = this.interfaces[0].mac; const frame = new EthernetFrame(dummyMac, dummyMac, datagram); dropPacket(this.viewgraph, this.id, frame); return; + } else { + // Si la cola NO está llena, ocultar el icono (por si se vació antes) + this.hideDeviceIcon("queueFull"); } if (wasEmpty) { this.startPacketProcessor(); @@ -213,6 +218,7 @@ export class ViewRouter extends ViewNetworkDevice { } else console.debug(`Router ${this.id} could not forward packet.`); if (this.packetQueue.isEmpty()) { + this.hideDeviceIcon("queueFull"); this.stopPacketProcessor(); } } From 01bea739741d8606ad55a6363bd4bcffdd8fc513 Mon Sep 17 00:00:00 2001 From: pgallino Date: Wed, 28 May 2025 18:19:50 -0300 Subject: [PATCH 05/15] feat: hide device icons when not visible and prevent icon creation if device is hidden --- src/types/view-devices/vDevice.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/types/view-devices/vDevice.ts b/src/types/view-devices/vDevice.ts index 1de999a3..7b8900aa 100644 --- a/src/types/view-devices/vDevice.ts +++ b/src/types/view-devices/vDevice.ts @@ -192,6 +192,9 @@ export abstract class ViewDevice extends Container { updateDevicesAspect() { if (!this.isVisibleFlag) { + for (const iconKey in this.deviceIcons) { + this.hideDeviceIcon(iconKey); + } const edges = this.viewgraph .getConnections(this.id) .filter((e) => e.isVisible()); @@ -408,6 +411,7 @@ export abstract class ViewDevice extends Container { yOffset: number, tooltipText?: string, ) { + if (!this.isVisible()) return; if (this.deviceIcons[iconKey]) return; const icon = createDeviceIcon(emoji, yOffset); this.deviceIcons[iconKey] = icon; From 9ec7fbce1421fc08bac06a6fed4621194a274ad5 Mon Sep 17 00:00:00 2001 From: pgallino Date: Wed, 28 May 2025 18:20:55 -0300 Subject: [PATCH 06/15] fix: lints --- src/types/view-devices/vRouter.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/types/view-devices/vRouter.ts b/src/types/view-devices/vRouter.ts index 69843594..60b9cbfe 100644 --- a/src/types/view-devices/vRouter.ts +++ b/src/types/view-devices/vRouter.ts @@ -179,7 +179,12 @@ export class ViewRouter extends ViewNetworkDevice { if (!this.packetQueue.enqueue(datagram)) { console.debug("Packet queue full, dropping packet"); // Mostrar icono de "cola llena" - this.showDeviceIcon("queueFull", "❗", -this.height / 2 - 5, "Queue full"); + this.showDeviceIcon( + "queueFull", + "❗", + -this.height / 2 - 5, + "Queue full", + ); // dummy values const dummyMac = this.interfaces[0].mac; const frame = new EthernetFrame(dummyMac, dummyMac, datagram); From b334e9b119937d5f17227feb3aa79294afbac8a1 Mon Sep 17 00:00:00 2001 From: pgallino Date: Wed, 28 May 2025 19:43:13 -0300 Subject: [PATCH 07/15] feat: add showDeviceIconFor method to display device icons temporarily && broadcast icon --- src/types/view-devices/vDevice.ts | 13 +++++++++++++ src/types/view-devices/vSwitch.ts | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/types/view-devices/vDevice.ts b/src/types/view-devices/vDevice.ts index 7b8900aa..9633a6e1 100644 --- a/src/types/view-devices/vDevice.ts +++ b/src/types/view-devices/vDevice.ts @@ -405,6 +405,19 @@ export abstract class ViewDevice extends Container { RightBar.getInstance().renderInfo(new DeviceInfo(this)); } + showDeviceIconFor( + iconKey: string, + emoji: string, + yOffset: number, + tooltipText: string | undefined, + durationMs: number, + ) { + this.showDeviceIcon(iconKey, emoji, yOffset, tooltipText); + setTimeout(() => { + this.hideDeviceIcon(iconKey); + }, durationMs); + } + showDeviceIcon( iconKey: string, emoji: string, diff --git a/src/types/view-devices/vSwitch.ts b/src/types/view-devices/vSwitch.ts index 51373ba1..f50335c8 100644 --- a/src/types/view-devices/vSwitch.ts +++ b/src/types/view-devices/vSwitch.ts @@ -105,6 +105,14 @@ export class ViewSwitch extends ViewDevice { receiveFrame(frame: EthernetFrame, iface: number): void { if (frame.payload instanceof ArpRequest) { const { sha, spa, tha, tpa } = frame.payload; + + this.showDeviceIconFor( + "broadcast", + "📢", + -this.height / 2 - 5, + "Broadcast", + 2000, + ); this.interfaces.forEach((sendingIface, idx) => { const packet = new ArpRequest(sha, spa, tpa, tha); const frame = new EthernetFrame( @@ -137,6 +145,16 @@ export class ViewSwitch extends ViewDevice { { length: getNumberOfInterfaces(this.getType()) }, (_, i) => i, ); + + if (sendingIfaces.length > 1) { + this.showDeviceIconFor( + "broadcast", + "📢", + -this.height / 2 - 5, + "Broadcast", + 2000, + ); + } sendingIfaces.forEach((sendingIface) => this.forwardFrame(frame, sendingIface, iface), ); From c3e1570f64760858dc7ac9eb1f54be237233f0c6 Mon Sep 17 00:00:00 2001 From: pgallino Date: Wed, 28 May 2025 19:56:15 -0300 Subject: [PATCH 08/15] feat: update packet queue handling to show temporary icon for full queue --- src/types/view-devices/vRouter.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/types/view-devices/vRouter.ts b/src/types/view-devices/vRouter.ts index 60b9cbfe..0c576b98 100644 --- a/src/types/view-devices/vRouter.ts +++ b/src/types/view-devices/vRouter.ts @@ -179,20 +179,18 @@ export class ViewRouter extends ViewNetworkDevice { if (!this.packetQueue.enqueue(datagram)) { console.debug("Packet queue full, dropping packet"); // Mostrar icono de "cola llena" - this.showDeviceIcon( + this.showDeviceIconFor( "queueFull", "❗", -this.height / 2 - 5, "Queue full", + 2000, ); // dummy values const dummyMac = this.interfaces[0].mac; const frame = new EthernetFrame(dummyMac, dummyMac, datagram); dropPacket(this.viewgraph, this.id, frame); return; - } else { - // Si la cola NO está llena, ocultar el icono (por si se vació antes) - this.hideDeviceIcon("queueFull"); } if (wasEmpty) { this.startPacketProcessor(); @@ -223,7 +221,6 @@ export class ViewRouter extends ViewNetworkDevice { } else console.debug(`Router ${this.id} could not forward packet.`); if (this.packetQueue.isEmpty()) { - this.hideDeviceIcon("queueFull"); this.stopPacketProcessor(); } } From 814c83bdec32795b3297b62d842eddaef04780db Mon Sep 17 00:00:00 2001 From: pgallino Date: Fri, 30 May 2025 12:24:53 -0300 Subject: [PATCH 09/15] fix: set default duration for device icon display to 2000ms --- src/styles/alert.css | 3 +-- src/styles/tooltips.css | 3 +-- src/types/view-devices/vDevice.ts | 2 +- src/types/view-devices/vNetworkDevice.ts | 6 ++++++ src/types/view-devices/vRouter.ts | 1 - src/types/view-devices/vSwitch.ts | 2 -- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/styles/alert.css b/src/styles/alert.css index 988761a6..d6aeffb8 100644 --- a/src/styles/alert.css +++ b/src/styles/alert.css @@ -16,8 +16,7 @@ border: 1px solid rgba(255, 255, 255, 0.2); /* Subtle border */ max-width: 400px; /* Maximum width */ line-height: 1.5; /* Line spacing */ - font-family: - "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; /* Font family */ + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; /* Font family */ opacity: 0; /* Initially transparent */ transition: opacity 0.3s ease-in-out, diff --git a/src/styles/tooltips.css b/src/styles/tooltips.css index f9f40dbe..812e6dde 100644 --- a/src/styles/tooltips.css +++ b/src/styles/tooltips.css @@ -17,8 +17,7 @@ max-width: 37.5rem; /* Maximum width in rem (600px / 16) */ white-space: pre-wrap; /* Allow line breaks */ line-height: 1.8; /* Line spacing */ - font-family: - "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; /* Font family */ + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; /* Font family */ overflow-y: auto; /* Enable scrolling for long content */ opacity: 0.8; /* Slightly transparent */ transition: opacity 0.2s ease-in-out; /* Smooth transition for opacity */ diff --git a/src/types/view-devices/vDevice.ts b/src/types/view-devices/vDevice.ts index 9633a6e1..3df85167 100644 --- a/src/types/view-devices/vDevice.ts +++ b/src/types/view-devices/vDevice.ts @@ -410,7 +410,7 @@ export abstract class ViewDevice extends Container { emoji: string, yOffset: number, tooltipText: string | undefined, - durationMs: number, + durationMs = 2000, ) { this.showDeviceIcon(iconKey, emoji, yOffset, tooltipText); setTimeout(() => { diff --git a/src/types/view-devices/vNetworkDevice.ts b/src/types/view-devices/vNetworkDevice.ts index 18ae51f4..50a62e58 100644 --- a/src/types/view-devices/vNetworkDevice.ts +++ b/src/types/view-devices/vNetworkDevice.ts @@ -211,6 +211,12 @@ export abstract class ViewNetworkDevice extends ViewDevice { // drop packet const frame = new EthernetFrame(mac, sha, packet); dropPacket(this.viewgraph, this.id, frame); + this.showDeviceIconFor( + "arpDrop", + "⛔", + -this.height / 2 - 5, + "ARP dropped", + ); return; } // Send an ARP Reply to the requesting device diff --git a/src/types/view-devices/vRouter.ts b/src/types/view-devices/vRouter.ts index 0c576b98..b2cd2cb9 100644 --- a/src/types/view-devices/vRouter.ts +++ b/src/types/view-devices/vRouter.ts @@ -184,7 +184,6 @@ export class ViewRouter extends ViewNetworkDevice { "❗", -this.height / 2 - 5, "Queue full", - 2000, ); // dummy values const dummyMac = this.interfaces[0].mac; diff --git a/src/types/view-devices/vSwitch.ts b/src/types/view-devices/vSwitch.ts index 1a5adff1..b35daed0 100644 --- a/src/types/view-devices/vSwitch.ts +++ b/src/types/view-devices/vSwitch.ts @@ -111,7 +111,6 @@ export class ViewSwitch extends ViewDevice { "📢", -this.height / 2 - 5, "Broadcast", - 2000, ); this.interfaces.forEach((sendingIface, idx) => { const packet = new ArpRequest(sha, spa, tpa, tha); @@ -152,7 +151,6 @@ export class ViewSwitch extends ViewDevice { "📢", -this.height / 2 - 5, "Broadcast", - 2000, ); } sendingIfaces.forEach((sendingIface) => From afc497de5f8379111b4ae0d73f5c321a6657b84c Mon Sep 17 00:00:00 2001 From: pgallino Date: Fri, 30 May 2025 12:45:30 -0300 Subject: [PATCH 10/15] feat: reposition device icons horizontally above device Now, when multiple device icons are shown at the same time, they are arranged in a horizontal row centered above the device, preventing tooltips from overlapping. The repositioning is triggered automatically when icons are added or removed. --- src/types/view-devices/vDevice.ts | 33 +++++++++++++++++------- src/types/view-devices/vHost.ts | 7 +---- src/types/view-devices/vNetworkDevice.ts | 7 +---- src/types/view-devices/vRouter.ts | 7 +---- src/types/view-devices/vSwitch.ts | 15 +++-------- 5 files changed, 29 insertions(+), 40 deletions(-) diff --git a/src/types/view-devices/vDevice.ts b/src/types/view-devices/vDevice.ts index 3df85167..894f596c 100644 --- a/src/types/view-devices/vDevice.ts +++ b/src/types/view-devices/vDevice.ts @@ -405,28 +405,39 @@ export abstract class ViewDevice extends Container { RightBar.getInstance().renderInfo(new DeviceInfo(this)); } + private repositionDeviceIcons() { + const icons = Object.values(this.deviceIcons).filter(Boolean) as Text[]; + if (icons.length === 0) return; + + // Espaciado horizontal entre iconos + const spacing = 28; // Puedes ajustar el valor para más/menos separación + // Todos los iconos a la misma altura, arriba del dispositivo + const baseY = -this.height / 2 - 5; + + // Centrar el grupo de iconos + const totalWidth = (icons.length - 1) * spacing; + icons.forEach((icon, idx) => { + icon.x = -totalWidth / 2 + idx * spacing; + icon.y = baseY; + }); + } + showDeviceIconFor( iconKey: string, emoji: string, - yOffset: number, tooltipText: string | undefined, durationMs = 2000, ) { - this.showDeviceIcon(iconKey, emoji, yOffset, tooltipText); + this.showDeviceIcon(iconKey, emoji, tooltipText); setTimeout(() => { this.hideDeviceIcon(iconKey); }, durationMs); } - showDeviceIcon( - iconKey: string, - emoji: string, - yOffset: number, - tooltipText?: string, - ) { + showDeviceIcon(iconKey: string, emoji: string, tooltipText?: string) { if (!this.isVisible()) return; if (this.deviceIcons[iconKey]) return; - const icon = createDeviceIcon(emoji, yOffset); + const icon = createDeviceIcon(emoji, 0); // yOffset no se usa aquí this.deviceIcons[iconKey] = icon; if (tooltipText) { @@ -435,7 +446,7 @@ export abstract class ViewDevice extends Container { this, tooltipText, 0, - yOffset - 30, + icon.y - 30, this.deviceTooltips[iconKey], ); }); @@ -445,6 +456,7 @@ export abstract class ViewDevice extends Container { } this.addChild(icon); + this.repositionDeviceIcons(); } hideDeviceIcon(iconKey: string) { @@ -453,6 +465,7 @@ export abstract class ViewDevice extends Container { this.removeChild(icon); icon.destroy(); this.deviceIcons[iconKey] = undefined; + this.repositionDeviceIcons(); } const tooltip = this.deviceTooltips[iconKey]; if (tooltip) { diff --git a/src/types/view-devices/vHost.ts b/src/types/view-devices/vHost.ts index d12afa42..fcfaba16 100644 --- a/src/types/view-devices/vHost.ts +++ b/src/types/view-devices/vHost.ts @@ -174,12 +174,7 @@ export class ViewHost extends ViewNetworkDevice { } showHttpServerIcon() { - this.showDeviceIcon( - "httpServer", - "🌐", - -this.height / 2 - 5, - "HTTP Server", - ); + this.showDeviceIcon("httpServer", "🌐", "HTTP Server"); } hideHttpServerIcon() { diff --git a/src/types/view-devices/vNetworkDevice.ts b/src/types/view-devices/vNetworkDevice.ts index 50a62e58..c4569a16 100644 --- a/src/types/view-devices/vNetworkDevice.ts +++ b/src/types/view-devices/vNetworkDevice.ts @@ -211,12 +211,7 @@ export abstract class ViewNetworkDevice extends ViewDevice { // drop packet const frame = new EthernetFrame(mac, sha, packet); dropPacket(this.viewgraph, this.id, frame); - this.showDeviceIconFor( - "arpDrop", - "⛔", - -this.height / 2 - 5, - "ARP dropped", - ); + this.showDeviceIconFor("arpDrop", "⛔", "ARP dropped"); return; } // Send an ARP Reply to the requesting device diff --git a/src/types/view-devices/vRouter.ts b/src/types/view-devices/vRouter.ts index b2cd2cb9..1800731e 100644 --- a/src/types/view-devices/vRouter.ts +++ b/src/types/view-devices/vRouter.ts @@ -179,12 +179,7 @@ export class ViewRouter extends ViewNetworkDevice { if (!this.packetQueue.enqueue(datagram)) { console.debug("Packet queue full, dropping packet"); // Mostrar icono de "cola llena" - this.showDeviceIconFor( - "queueFull", - "❗", - -this.height / 2 - 5, - "Queue full", - ); + this.showDeviceIconFor("queueFull", "❗", "Queue full"); // dummy values const dummyMac = this.interfaces[0].mac; const frame = new EthernetFrame(dummyMac, dummyMac, datagram); diff --git a/src/types/view-devices/vSwitch.ts b/src/types/view-devices/vSwitch.ts index b35daed0..c6787944 100644 --- a/src/types/view-devices/vSwitch.ts +++ b/src/types/view-devices/vSwitch.ts @@ -78,6 +78,7 @@ export class ViewSwitch extends ViewDevice { } if (device instanceof DataSwitch) { device.updateForwardingTable(mac, iface); + this.showDeviceIconFor("update-ftable", "🔄", "Table Updated"); } }); } @@ -106,12 +107,7 @@ export class ViewSwitch extends ViewDevice { if (frame.payload instanceof ArpRequest) { const { sha, spa, tha, tpa } = frame.payload; - this.showDeviceIconFor( - "broadcast", - "📢", - -this.height / 2 - 5, - "Broadcast", - ); + this.showDeviceIconFor("broadcast", "📢", "Broadcast"); this.interfaces.forEach((sendingIface, idx) => { const packet = new ArpRequest(sha, spa, tpa, tha); const frame = new EthernetFrame( @@ -146,12 +142,7 @@ export class ViewSwitch extends ViewDevice { ); if (sendingIfaces.length > 1) { - this.showDeviceIconFor( - "broadcast", - "📢", - -this.height / 2 - 5, - "Broadcast", - ); + this.showDeviceIconFor("broadcast", "📢", "Broadcast"); } sendingIfaces.forEach((sendingIface) => this.forwardFrame(frame, sendingIface, iface), From 934f20baf42394875c82b61730db0721144e9e90 Mon Sep 17 00:00:00 2001 From: pgallino Date: Sat, 31 May 2025 00:41:42 -0300 Subject: [PATCH 11/15] fix lints --- src/types/view-devices/vDevice.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/types/view-devices/vDevice.ts b/src/types/view-devices/vDevice.ts index 894f596c..858b1dff 100644 --- a/src/types/view-devices/vDevice.ts +++ b/src/types/view-devices/vDevice.ts @@ -409,12 +409,9 @@ export abstract class ViewDevice extends Container { const icons = Object.values(this.deviceIcons).filter(Boolean) as Text[]; if (icons.length === 0) return; - // Espaciado horizontal entre iconos - const spacing = 28; // Puedes ajustar el valor para más/menos separación - // Todos los iconos a la misma altura, arriba del dispositivo + const spacing = 28; const baseY = -this.height / 2 - 5; - // Centrar el grupo de iconos const totalWidth = (icons.length - 1) * spacing; icons.forEach((icon, idx) => { icon.x = -totalWidth / 2 + idx * spacing; @@ -437,7 +434,7 @@ export abstract class ViewDevice extends Container { showDeviceIcon(iconKey: string, emoji: string, tooltipText?: string) { if (!this.isVisible()) return; if (this.deviceIcons[iconKey]) return; - const icon = createDeviceIcon(emoji, 0); // yOffset no se usa aquí + const icon = createDeviceIcon(emoji, 0); this.deviceIcons[iconKey] = icon; if (tooltipText) { From 8956aa263cf7c41eb694582c89bc2d4e9985bfcb Mon Sep 17 00:00:00 2001 From: pgallino Date: Sat, 31 May 2025 00:57:51 -0300 Subject: [PATCH 12/15] fix: lints --- src/styles/alert.css | 2 +- src/styles/tooltips.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/styles/alert.css b/src/styles/alert.css index d6aeffb8..be52bf0b 100644 --- a/src/styles/alert.css +++ b/src/styles/alert.css @@ -16,7 +16,7 @@ border: 1px solid rgba(255, 255, 255, 0.2); /* Subtle border */ max-width: 400px; /* Maximum width */ line-height: 1.5; /* Line spacing */ - font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; /* Font family */ + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; opacity: 0; /* Initially transparent */ transition: opacity 0.3s ease-in-out, diff --git a/src/styles/tooltips.css b/src/styles/tooltips.css index 812e6dde..1990d586 100644 --- a/src/styles/tooltips.css +++ b/src/styles/tooltips.css @@ -17,7 +17,7 @@ max-width: 37.5rem; /* Maximum width in rem (600px / 16) */ white-space: pre-wrap; /* Allow line breaks */ line-height: 1.8; /* Line spacing */ - font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; /* Font family */ + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; overflow-y: auto; /* Enable scrolling for long content */ opacity: 0.8; /* Slightly transparent */ transition: opacity 0.2s ease-in-out; /* Smooth transition for opacity */ From 09079cd30b2b1d7ddac0fee189a8bd2120188066 Mon Sep 17 00:00:00 2001 From: pgallino Date: Sun, 1 Jun 2025 16:03:12 -0300 Subject: [PATCH 13/15] feat: enhance device icon visibility control and update routing table lookup --- src/types/network-modules/tcp/tcpState.ts | 8 ++++++++ src/types/view-devices/vDevice.ts | 16 ++++++++++++++-- src/types/view-devices/vRouter.ts | 2 +- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/types/network-modules/tcp/tcpState.ts b/src/types/network-modules/tcp/tcpState.ts index 22976d7f..f8d3eabf 100644 --- a/src/types/network-modules/tcp/tcpState.ts +++ b/src/types/network-modules/tcp/tcpState.ts @@ -9,6 +9,7 @@ import { AsyncQueue } from "../asyncQueue"; import { SegmentWithIp } from "../tcpModule"; import { GlobalContext } from "../../../context"; import { CONFIG_SWITCH_KEYS } from "../../../config_menu/switches/switch_factory"; +import { Layer } from "../../layer"; enum TcpStateEnum { CLOSED = 0, @@ -714,6 +715,13 @@ export class TcpState { console.debug("[" + this.srcHost.id + "] [TCP] Processing timeout"); retransmitPromise = this.retransmissionQueue.pop(); // Retransmit the segment + this.srcHost.showDeviceIconFor( + "tcp_timeout", + "⏰", + "TCP Timeout", + 2000, + Layer.Transport, + ); this.resendPacket(result.seqNum, result.size); this.congestionControl.notifyTimeout(); continue; diff --git a/src/types/view-devices/vDevice.ts b/src/types/view-devices/vDevice.ts index 858b1dff..91122b80 100644 --- a/src/types/view-devices/vDevice.ts +++ b/src/types/view-devices/vDevice.ts @@ -424,14 +424,26 @@ export abstract class ViewDevice extends Container { emoji: string, tooltipText: string | undefined, durationMs = 2000, + visibleFromLayer?: Layer, ) { - this.showDeviceIcon(iconKey, emoji, tooltipText); + this.showDeviceIcon(iconKey, emoji, tooltipText, visibleFromLayer); setTimeout(() => { this.hideDeviceIcon(iconKey); }, durationMs); } - showDeviceIcon(iconKey: string, emoji: string, tooltipText?: string) { + showDeviceIcon( + iconKey: string, + emoji: string, + tooltipText?: string, + visibleFromLayer?: Layer, + ) { + if ( + visibleFromLayer !== undefined && + this.viewgraph.getLayer() > visibleFromLayer + ) { + return; + } if (!this.isVisible()) return; if (this.deviceIcons[iconKey]) return; const icon = createDeviceIcon(emoji, 0); diff --git a/src/types/view-devices/vRouter.ts b/src/types/view-devices/vRouter.ts index 1800731e..02c15114 100644 --- a/src/types/view-devices/vRouter.ts +++ b/src/types/view-devices/vRouter.ts @@ -248,7 +248,7 @@ export class ViewRouter extends ViewNetworkDevice { return; } - const result = device.routingTable.all().find((entry) => { + const result = device.routingTable.allActive().find((entry) => { const ip = IpAddress.parse(entry.ip); const mask = IpAddress.parse(entry.mask); return datagram.destinationAddress.isInSubnet(ip, mask); From 91dc2d56df5c31f471a6d41608be60aac96110f6 Mon Sep 17 00:00:00 2001 From: pgallino Date: Sun, 1 Jun 2025 16:08:42 -0300 Subject: [PATCH 14/15] fix: improve device icon visibility timeout handling using Ticker --- src/types/view-devices/vDevice.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/types/view-devices/vDevice.ts b/src/types/view-devices/vDevice.ts index 91122b80..8d6ca797 100644 --- a/src/types/view-devices/vDevice.ts +++ b/src/types/view-devices/vDevice.ts @@ -6,6 +6,7 @@ import { TextStyle, Text, Container, + Ticker, } from "pixi.js"; import { ViewGraph } from "../graphs/viewgraph"; import { @@ -427,9 +428,15 @@ export abstract class ViewDevice extends Container { visibleFromLayer?: Layer, ) { this.showDeviceIcon(iconKey, emoji, tooltipText, visibleFromLayer); - setTimeout(() => { - this.hideDeviceIcon(iconKey); - }, durationMs); + let progress = 0; + const tick = (ticker: Ticker) => { + progress += ticker.elapsedMS * this.ctx.getCurrentSpeed(); + if (progress >= durationMs) { + Ticker.shared.remove(tick, this); + this.hideDeviceIcon(iconKey); + } + }; + Ticker.shared.add(tick, this); } showDeviceIcon( From b43e8aa5d052e77c8ff3d48b1ad22e01e49f0afa Mon Sep 17 00:00:00 2001 From: pgallino Date: Sun, 1 Jun 2025 16:43:10 -0300 Subject: [PATCH 15/15] feat: add TCP handshake icon display during connection process in HttpClient --- src/programs/http_client.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/programs/http_client.ts b/src/programs/http_client.ts index 23a6dda9..f7b6476e 100644 --- a/src/programs/http_client.ts +++ b/src/programs/http_client.ts @@ -87,8 +87,18 @@ export class HttpClient extends ProgramBase { this.resource, ); + srcDevice.showDeviceIcon( + "tcp-handshake", + "🤝", + "TCP handshake in progress", + Layer.App, + ); + // Write request const socket = await this.runner.tcpConnect(this.dstId); + + srcDevice.hideDeviceIcon("tcp-handshake"); + if (!socket) { console.warn("HttpClient failed to connect"); return;