Skip to content

Commit ae060b8

Browse files
feat: show routing table (#61)
Co-authored-by: pgallino <[email protected]>
1 parent 7a4d5de commit ae060b8

File tree

14 files changed

+406
-43
lines changed

14 files changed

+406
-43
lines changed

src/context.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,28 @@ import {
66
saveToLocalStorage,
77
} from "./types/viewportManager";
88
import { Layer } from "./types/devices/device";
9+
import { IpAddressGenerator } from "./packets/ip";
910

1011
export class GlobalContext {
1112
private viewport: Viewport = null;
1213
private datagraph: DataGraph;
1314
private viewgraph: ViewGraph;
1415
private saveIntervalId: NodeJS.Timeout | null = null;
16+
private ipGenerator: IpAddressGenerator;
1517

1618
initialize(viewport: Viewport) {
1719
this.viewport = viewport;
20+
21+
const baseIp = "192.168.1.0";
22+
const mask = "255.255.255.0";
23+
this.ipGenerator = new IpAddressGenerator(baseIp, mask);
1824
loadFromLocalStorage(this);
1925
}
2026

27+
getNextIp(): { ip: string; mask: string } {
28+
return this.ipGenerator.getNextIp();
29+
}
30+
2131
private setNetwork(datagraph: DataGraph, layer: Layer) {
2232
this.datagraph = datagraph;
2333
this.viewport.clear();

src/graphics/renderables/device_info.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import { Device } from "../../types/devices";
22
import { DeviceType } from "../../types/devices/device";
33
import { ViewGraph } from "../../types/graphs/viewgraph";
44
import { sendPacket } from "../../types/packet";
5-
import { createDropdown, createRightBarButton } from "../right_bar";
5+
import {
6+
createDropdown,
7+
createToggleTable,
8+
createRightBarButton,
9+
} from "../right_bar";
610
import { StyledInfo } from "./styled_info";
711

812
export class DeviceInfo extends StyledInfo {
@@ -65,6 +69,20 @@ export class DeviceInfo extends StyledInfo {
6569
);
6670
}
6771

72+
addRoutingTable(entries: { ip: string; mask: string; iface: string }[]) {
73+
const rows = entries.map((entry) => [entry.ip, entry.mask, entry.iface]);
74+
75+
const dynamicTable = createToggleTable(
76+
"Routing Table", // Title
77+
["IP Address", "Mask", "Interface"], // Headers
78+
rows, // Generated files
79+
"right-bar-toggle-button", // Button class
80+
"right-bar-table", // Table class
81+
);
82+
83+
this.inputFields.push(dynamicTable);
84+
}
85+
6886
toHTML(): Node[] {
6987
return super.toHTML().concat(this.inputFields);
7088
}

src/graphics/right_bar.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,62 @@ export class RightBar {
8888
}
8989
}
9090

91+
export function createToggleTable(
92+
title: string,
93+
headers: string[],
94+
rows: string[][],
95+
buttonClass = "right-bar-toggle-button",
96+
tableClass = "right-bar-table",
97+
) {
98+
const container = document.createElement("div");
99+
container.classList.add("toggle-table-container");
100+
101+
// Create toggle button
102+
const button = document.createElement("button");
103+
button.classList.add(buttonClass);
104+
button.textContent = title;
105+
106+
// Create table
107+
const table = document.createElement("table");
108+
table.classList.add(tableClass, "hidden");
109+
110+
// Add headers
111+
const headerRow = document.createElement("tr");
112+
headers.forEach((header) => {
113+
const th = document.createElement("th");
114+
th.textContent = header;
115+
headerRow.appendChild(th);
116+
});
117+
table.appendChild(headerRow);
118+
119+
// Add rows
120+
rows.forEach((row) => {
121+
const rowElement = document.createElement("tr");
122+
row.forEach((cellData) => {
123+
const cell = document.createElement("td");
124+
cell.textContent = cellData;
125+
rowElement.appendChild(cell);
126+
});
127+
table.appendChild(rowElement);
128+
});
129+
130+
// Toggle when clicking on button
131+
button.onclick = () => {
132+
const isHidden = table.classList.contains("hidden");
133+
table.classList.toggle("hidden", !isHidden);
134+
table.classList.toggle("open", isHidden);
135+
container.classList.toggle("hidden", !isHidden);
136+
container.classList.toggle("open", isHidden);
137+
button.classList.toggle("open", isHidden);
138+
};
139+
140+
// Add button and table to container
141+
container.appendChild(button);
142+
container.appendChild(table);
143+
144+
return container;
145+
}
146+
91147
export function createRightBarButton(
92148
text: string,
93149
onClick: () => void,

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { GlobalContext } from "./context";
1616

1717
// Assets
1818
// Doing this includes the file in the build
19-
import "./style.css";
19+
import "./styles";
2020
import RouterSvg from "./assets/router.svg";
2121
import ComputerSvg from "./assets/pc.svg";
2222
import PlaySvg from "./assets/play-icon.svg";

src/packets/ip.ts

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,79 @@ export class IpAddress {
2121
this.octets = octets;
2222
}
2323

24-
static parse(addrString: string) {
24+
// Parse IP address from a string representation (10.25.34.42)
25+
static parse(addrString: string): IpAddress {
2526
const octets = new Uint8Array(4);
26-
addrString.split(".").map((octet, i) => {
27+
addrString.split(".").forEach((octet, i) => {
2728
const octetInt = parseInt(octet);
28-
if (octetInt < 0 || octetInt > 255) {
29-
throw new Error("Invalid IP address");
29+
if (isNaN(octetInt) || octetInt < 0 || octetInt > 255) {
30+
throw new Error(`Invalid IP address: ${addrString}`);
3031
}
3132
octets[i] = octetInt;
3233
});
3334
return new this(octets);
3435
}
36+
37+
// Turn to string
38+
toString(): string {
39+
return Array.from(this.octets).join(".");
40+
}
41+
42+
// Check if two IP addresses are equal.
43+
equals(other: IpAddress): boolean {
44+
return this.octets.every((octet, index) => octet === other.octets[index]);
45+
}
46+
47+
// Apply a bitmask to the address (bitwise AND) and return the result.
48+
applyMask(mask: IpAddress): IpAddress {
49+
const maskedOctets = new Uint8Array(
50+
this.octets.map((octet, i) => octet & mask.octets[i]),
51+
);
52+
return new IpAddress(maskedOctets);
53+
}
54+
55+
// Return true if the two IP addresses are in the same subnet specified by the mask.
56+
isInSubnet(baseIp: IpAddress, mask: IpAddress): boolean {
57+
const maskedThis = this.applyMask(mask);
58+
const maskedBase = baseIp.applyMask(mask);
59+
return maskedThis.equals(maskedBase);
60+
}
61+
}
62+
63+
export class IpAddressGenerator {
64+
private baseIp: number;
65+
private currentIp: number;
66+
private mask: string;
67+
68+
constructor(baseIp: string, mask: string) {
69+
this.baseIp = IpAddressGenerator.ipToNumber(baseIp);
70+
this.currentIp = this.baseIp + 1; // Start on first valid IP
71+
this.mask = mask;
72+
}
73+
74+
// Generate next valid IP
75+
getNextIp(): { ip: string; mask: string } {
76+
const nextIp = IpAddressGenerator.numberToIp(this.currentIp);
77+
this.currentIp++;
78+
return { ip: nextIp, mask: this.mask };
79+
}
80+
81+
// Turn IP into a number
82+
static ipToNumber(ip: string): number {
83+
return ip
84+
.split(".")
85+
.reduce((acc, octet) => (acc << 8) + parseInt(octet, 10), 0);
86+
}
87+
88+
// Turn number into IP
89+
static numberToIp(num: number): string {
90+
return [
91+
(num >> 24) & 0xff,
92+
(num >> 16) & 0xff,
93+
(num >> 8) & 0xff,
94+
num & 0xff,
95+
].join(".");
96+
}
3597
}
3698

3799
export interface IpPayload {

src/styles/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import "./style.css";

src/style.css renamed to src/styles/style.css

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,29 @@ canvas {
150150
height: 60%;
151151
}
152152

153+
/* width */
154+
::-webkit-scrollbar {
155+
width: 10px;
156+
}
157+
158+
/* Track */
159+
::-webkit-scrollbar-track {
160+
background: #f1f1f1;
161+
}
162+
163+
/* Handle */
164+
::-webkit-scrollbar-thumb {
165+
background: #888;
166+
}
167+
168+
/* Handle on hover */
169+
::-webkit-scrollbar-thumb:hover {
170+
background: #555;
171+
}
172+
153173
/* Style for the right sidebar */
154174
.right-bar {
155-
flex: 0 0 250px; /* Fixed width of 250px for the right bar */
175+
flex: 0 0 300px; /* Fixed width of 250px for the right bar */
156176
background-color: #f1f1f1; /* Light background for the bar */
157177
box-shadow: -2px 0px 5px rgba(0, 0, 0, 0.2); /* Left shadow to highlight the bar */
158178
padding: 20px; /* Internal space around content */
@@ -192,10 +212,14 @@ canvas {
192212
border-radius: 4px;
193213
width: 100%; /* Occupies 100% of the right-bar width */
194214
box-sizing: border-box; /* Includes padding in the total width */
215+
transition:
216+
background-color 0.3s ease,
217+
transform 0.2s ease;
195218
}
196219

197220
.right-bar-button:hover {
198221
background-color: #0056b3;
222+
transform: scale(1.02); /* Small scale effect on hover */
199223
}
200224

201225
.right-bar-button.selected-button {
@@ -249,3 +273,115 @@ canvas {
249273
.dropdown-container {
250274
margin-bottom: 8px; /* Reduce bottom margin */
251275
}
276+
277+
/* Container for button and table */
278+
.toggle-table-container {
279+
display: flex;
280+
flex-direction: column;
281+
gap: 5px; /* Spacing between button and table */
282+
width: 100%;
283+
}
284+
285+
/* Style for buttons in the right bar with green background */
286+
.right-bar-toggle-button {
287+
background-color: #1d7437; /* Dark green */
288+
border: none;
289+
color: white;
290+
padding: 6px 12px;
291+
text-align: center; /* Center text horizontally */
292+
text-decoration: none;
293+
display: flex; /* Use flexbox to organize text and arrow */
294+
justify-content: center; /* Center content horizontally */
295+
align-items: center; /* Center content vertically */
296+
font-size: 14px;
297+
margin: 5px 0; /* Adjust margin between buttons */
298+
cursor: pointer;
299+
border-radius: 4px; /* Rounded borders */
300+
width: 100%;
301+
box-sizing: border-box; /* Include padding on all length */
302+
transition:
303+
background-color 0.3s ease,
304+
transform 0.2s ease;
305+
position: relative; /* To position internal elements like the arrow */
306+
}
307+
308+
/* Hover for the button */
309+
.right-bar-toggle-button:hover {
310+
background-color: #135025; /* Change to a darker green */
311+
transform: scale(1.02); /* Small scale effect */
312+
}
313+
314+
/* Triangle icon */
315+
.right-bar-toggle-button::after {
316+
content: "";
317+
display: inline-block;
318+
position: absolute; /* Position the triangle on the right */
319+
right: 12px; /* Right margin for the triangle */
320+
width: 0;
321+
height: 0;
322+
border-left: 6px solid transparent;
323+
border-right: 6px solid transparent;
324+
border-top: 6px solid white; /* White triangle */
325+
transition: transform 0.3s ease;
326+
}
327+
328+
/* Rotate triangle when opening */
329+
.right-bar-toggle-button.open::after {
330+
transform: rotate(180deg); /* Rotate triangle upwards */
331+
}
332+
333+
/* Table style */
334+
.toggle-table-container table {
335+
width: 100%;
336+
border-collapse: collapse;
337+
font-size: 14px;
338+
text-align: center;
339+
transition: opacity 0.5s ease-out; /* Make transition smoother */
340+
border-radius: 8px; /* Rounded borders */
341+
overflow: hidden; /* Avoid content spilling */
342+
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
343+
}
344+
345+
/* Table headers */
346+
.toggle-table-container th {
347+
background-color: #1d7437;
348+
color: white;
349+
padding: 8px;
350+
border: 1px solid #ddd;
351+
text-align: center;
352+
}
353+
354+
/* Table rows */
355+
.toggle-table-container td {
356+
padding: 8px;
357+
border: 1px solid #ddd;
358+
text-align: center;
359+
}
360+
361+
/* Alternate between row colors */
362+
.toggle-table-container tr:nth-child(even) {
363+
background-color: #f9f9f9;
364+
}
365+
366+
.toggle-table-container tr:nth-child(odd) {
367+
background-color: #ffffff;
368+
}
369+
370+
/* Hover on rows */
371+
.toggle-table-container tr:hover {
372+
background-color: #f1f1f1;
373+
}
374+
375+
/* Compatibility adjustments */
376+
.toggle-table-container caption {
377+
font-size: 16px;
378+
font-weight: bold;
379+
margin-bottom: 5px;
380+
color: #333333;
381+
text-align: center;
382+
}
383+
384+
/* Hidden table */
385+
.toggle-table-container table.hidden {
386+
display: none; /* Hides table */
387+
}

0 commit comments

Comments
 (0)