|
| 1 | +import { |
| 2 | + DeviceId, |
| 3 | + isRouter, |
| 4 | + RoutingTableEntry, |
| 5 | +} from "../types/graphs/datagraph"; |
| 6 | +import { ViewGraph } from "../types/graphs/viewgraph"; |
| 7 | + |
1 | 8 | export { StyledInfo } from "./renderables/styled_info"; |
2 | 9 | export { DeviceInfo } from "./renderables/device_info"; |
3 | 10 |
|
@@ -102,56 +109,240 @@ export class RightBar { |
102 | 109 | } |
103 | 110 | } |
104 | 111 |
|
105 | | -export function createToggleTable( |
| 112 | +// Function to create a toggle button |
| 113 | +function createToggleButton( |
106 | 114 | title: string, |
107 | | - headers: string[], |
108 | | - rows: string[][], |
109 | | - buttonClass = "right-bar-toggle-button", |
110 | | - tableClass = "right-bar-table", |
| 115 | + buttonClass: string, |
| 116 | + table: HTMLTableElement, |
111 | 117 | ) { |
112 | | - const container = document.createElement("div"); |
113 | | - container.classList.add("toggle-table-container"); |
114 | | - |
115 | | - // Create toggle button |
116 | 118 | const button = document.createElement("button"); |
117 | 119 | button.classList.add(buttonClass); |
118 | 120 | button.textContent = title; |
119 | 121 |
|
120 | | - // Create table |
| 122 | + button.onclick = () => { |
| 123 | + const isHidden = table.classList.contains("hidden"); |
| 124 | + table.classList.toggle("hidden", !isHidden); |
| 125 | + button.classList.toggle("open", isHidden); |
| 126 | + }; |
| 127 | + |
| 128 | + return button; |
| 129 | +} |
| 130 | + |
| 131 | +function updateRoutingTableUI( |
| 132 | + deviceId: DeviceId, |
| 133 | + newTableData: RoutingTableEntry[], |
| 134 | + viewgraph: ViewGraph, |
| 135 | +): void { |
| 136 | + const router = viewgraph.getDataGraph().getDevice(deviceId); |
| 137 | + if (!router || !isRouter(router)) { |
| 138 | + console.warn(`Device with ID ${deviceId} is not a valid router.`); |
| 139 | + return; |
| 140 | + } |
| 141 | + router.routingTable = newTableData; |
| 142 | + |
| 143 | + const tableContainer = document.querySelector(".toggle-table-container"); |
| 144 | + if (!tableContainer) |
| 145 | + return console.warn("Routing table container not found."); |
| 146 | + |
| 147 | + const existingTable = tableContainer.querySelector("table"); |
| 148 | + if (!existingTable) |
| 149 | + return console.warn("Existing table not found inside container."); |
| 150 | + |
| 151 | + clearTableRows(existingTable); |
| 152 | + newTableData.forEach((entry) => |
| 153 | + createTableRow(entry, existingTable, deviceId, viewgraph), |
| 154 | + ); |
| 155 | + console.log(`Routing table for router ID ${deviceId} updated successfully.`); |
| 156 | +} |
| 157 | + |
| 158 | +function createTable( |
| 159 | + headers: string[], |
| 160 | + rows: string[][], |
| 161 | + tableClass: string, |
| 162 | + viewgraph: ViewGraph, |
| 163 | + deviceId: DeviceId, |
| 164 | +): HTMLTableElement { |
121 | 165 | const table = document.createElement("table"); |
122 | 166 | table.classList.add(tableClass, "hidden"); |
123 | 167 |
|
124 | | - // Add headers |
125 | 168 | const headerRow = document.createElement("tr"); |
126 | 169 | headers.forEach((header) => { |
127 | 170 | const th = document.createElement("th"); |
128 | 171 | th.textContent = header; |
129 | 172 | headerRow.appendChild(th); |
130 | 173 | }); |
| 174 | + |
| 175 | + const actionsHeader = document.createElement("th"); |
| 176 | + actionsHeader.appendChild(createRegenerateButton(deviceId, viewgraph)); |
| 177 | + headerRow.appendChild(actionsHeader); |
131 | 178 | table.appendChild(headerRow); |
132 | 179 |
|
133 | | - // Add rows |
134 | 180 | rows.forEach((row) => { |
135 | | - const rowElement = document.createElement("tr"); |
136 | | - row.forEach((cellData) => { |
137 | | - const cell = document.createElement("td"); |
138 | | - cell.textContent = cellData; |
139 | | - rowElement.appendChild(cell); |
| 181 | + createTableRow( |
| 182 | + { ip: row[0], mask: row[1], iface: parseInt(row[2].replace("eth", "")) }, |
| 183 | + table, |
| 184 | + deviceId, |
| 185 | + viewgraph, |
| 186 | + ); |
| 187 | + }); |
| 188 | + |
| 189 | + return table; |
| 190 | +} |
| 191 | + |
| 192 | +function clearTableRows(table: HTMLTableElement): void { |
| 193 | + const rows = Array.from(table.querySelectorAll("tr")); |
| 194 | + rows.slice(1).forEach((row) => row.remove()); // Mantener solo el encabezado |
| 195 | +} |
| 196 | + |
| 197 | +function createTableRow( |
| 198 | + entry: RoutingTableEntry, |
| 199 | + table: HTMLTableElement, |
| 200 | + deviceId: DeviceId, |
| 201 | + viewgraph: ViewGraph, |
| 202 | +): void { |
| 203 | + const rowElement = document.createElement("tr"); |
| 204 | + |
| 205 | + [entry.ip, entry.mask, `eth${entry.iface}`].forEach((cellData, colIndex) => { |
| 206 | + const cell = document.createElement("td"); |
| 207 | + cell.textContent = cellData; |
| 208 | + cell.classList.add("editable-cell"); |
| 209 | + cell.contentEditable = "true"; |
| 210 | + |
| 211 | + cell.addEventListener("keydown", (event) => { |
| 212 | + if (event.key === "Delete" || event.key === "Backspace") { |
| 213 | + event.stopPropagation(); // Evita que el evento borre la fila en la tabla |
| 214 | + } |
| 215 | + }); |
| 216 | + |
| 217 | + cell.addEventListener("blur", () => { |
| 218 | + const updatedRowIndex = |
| 219 | + Array.from(table.querySelectorAll("tr")).indexOf(rowElement) - 1; // Ajuste dinámico del índice |
| 220 | + const newValue = cell.textContent?.trim() || ""; |
| 221 | + |
| 222 | + let isValid = false; |
| 223 | + if (colIndex === 0) isValid = isValidIP(newValue); |
| 224 | + else if (colIndex === 1) isValid = isValidIP(newValue); |
| 225 | + else if (colIndex === 2) isValid = isValidInterface(newValue); |
| 226 | + |
| 227 | + if (!isValid) { |
| 228 | + console.warn(`Invalid input for column ${colIndex}: ${newValue}`); |
| 229 | + cell.textContent = cellData; // Revertir cambio si es inválido |
| 230 | + return; |
| 231 | + } |
| 232 | + |
| 233 | + viewgraph |
| 234 | + .getDataGraph() |
| 235 | + .saveManualChange(deviceId, updatedRowIndex, colIndex, newValue); |
| 236 | + console.log( |
| 237 | + `Updated cell at row ${updatedRowIndex}, column ${colIndex} with value: ${newValue}`, |
| 238 | + ); |
140 | 239 | }); |
141 | | - table.appendChild(rowElement); |
| 240 | + |
| 241 | + rowElement.appendChild(cell); |
142 | 242 | }); |
143 | 243 |
|
144 | | - // Toggle when clicking on button |
145 | | - button.onclick = () => { |
146 | | - const isHidden = table.classList.contains("hidden"); |
147 | | - table.classList.toggle("hidden", !isHidden); |
148 | | - table.classList.toggle("open", isHidden); |
149 | | - container.classList.toggle("hidden", !isHidden); |
150 | | - container.classList.toggle("open", isHidden); |
151 | | - button.classList.toggle("open", isHidden); |
152 | | - }; |
| 244 | + rowElement.appendChild( |
| 245 | + createDeleteButton(rowElement, table, deviceId, viewgraph), |
| 246 | + ); |
| 247 | + table.appendChild(rowElement); |
| 248 | +} |
| 249 | + |
| 250 | +function createRegenerateButton( |
| 251 | + deviceId: DeviceId, |
| 252 | + viewgraph: ViewGraph, |
| 253 | +): HTMLButtonElement { |
| 254 | + const regenerateAllButton = document.createElement("button"); |
| 255 | + regenerateAllButton.innerHTML = "🔄"; |
| 256 | + regenerateAllButton.style.border = "none"; |
| 257 | + regenerateAllButton.style.background = "transparent"; |
| 258 | + regenerateAllButton.style.cursor = "pointer"; |
| 259 | + regenerateAllButton.style.fontSize = "1.2em"; |
| 260 | + regenerateAllButton.title = "Regenerate Full Routing Table"; |
| 261 | + |
| 262 | + regenerateAllButton.addEventListener("click", () => { |
| 263 | + console.log(`Regenerating full routing table for device ${deviceId}`); |
| 264 | + const newTableData = viewgraph |
| 265 | + .getDataGraph() |
| 266 | + .regenerateRoutingTableClean(deviceId); |
| 267 | + if (!newTableData.length) { |
| 268 | + console.warn("Failed to regenerate routing table."); |
| 269 | + return; |
| 270 | + } |
| 271 | + updateRoutingTableUI(deviceId, newTableData, viewgraph); |
| 272 | + }); |
| 273 | + |
| 274 | + return regenerateAllButton; |
| 275 | +} |
| 276 | + |
| 277 | +function createDeleteButton( |
| 278 | + rowElement: HTMLTableRowElement, |
| 279 | + table: HTMLTableElement, |
| 280 | + deviceId: DeviceId, |
| 281 | + viewgraph: ViewGraph, |
| 282 | +): HTMLTableCellElement { |
| 283 | + const deleteCell = document.createElement("td"); |
| 284 | + const deleteButton = document.createElement("button"); |
| 285 | + deleteButton.innerHTML = "🗑️"; |
| 286 | + deleteButton.style.border = "none"; |
| 287 | + deleteButton.style.background = "transparent"; |
| 288 | + deleteButton.style.cursor = "pointer"; |
| 289 | + deleteButton.title = "Delete row"; |
| 290 | + |
| 291 | + deleteButton.addEventListener("click", () => |
| 292 | + handleDeleteRow(rowElement, table, deviceId, viewgraph), |
| 293 | + ); |
| 294 | + |
| 295 | + deleteCell.appendChild(deleteButton); |
| 296 | + return deleteCell; |
| 297 | +} |
| 298 | + |
| 299 | +function handleDeleteRow( |
| 300 | + rowElement: HTMLTableRowElement, |
| 301 | + table: HTMLTableElement, |
| 302 | + deviceId: DeviceId, |
| 303 | + viewgraph: ViewGraph, |
| 304 | +): void { |
| 305 | + const updatedRowIndex = |
| 306 | + Array.from(table.querySelectorAll("tr")).indexOf(rowElement) - 1; // Ajuste dinámico del índice |
| 307 | + |
| 308 | + if (updatedRowIndex < 0) { |
| 309 | + console.warn("Cannot delete header row"); |
| 310 | + return; |
| 311 | + } |
| 312 | + |
| 313 | + table.removeChild(rowElement); |
| 314 | + viewgraph.getDataGraph().removeRoutingTableRow(deviceId, updatedRowIndex); |
| 315 | + console.log(`Deleted row ${updatedRowIndex} from device ${deviceId}`); |
| 316 | +} |
| 317 | + |
| 318 | +// Function to validate IP format |
| 319 | +function isValidIP(ip: string): boolean { |
| 320 | + const ipPattern = |
| 321 | + /^(25[0-5]|2[0-4][0-9]|1?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|1?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|1?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|1?[0-9][0-9]?)$/; |
| 322 | + return ipPattern.test(ip); |
| 323 | +} |
| 324 | + |
| 325 | +// Function to validate Interface format (ethX where X is a number) |
| 326 | +function isValidInterface(interfaceStr: string): boolean { |
| 327 | + const interfacePattern = /^eth[0-9]+$/; |
| 328 | + return interfacePattern.test(interfaceStr); |
| 329 | +} |
| 330 | + |
| 331 | +export function createToggleTable( |
| 332 | + title: string, |
| 333 | + headers: string[], |
| 334 | + rows: string[][], |
| 335 | + viewgraph: ViewGraph, |
| 336 | + deviceId: number, |
| 337 | + buttonClass = "right-bar-toggle-button", |
| 338 | + tableClass = "right-bar-table", |
| 339 | +) { |
| 340 | + const container = document.createElement("div"); |
| 341 | + container.classList.add("toggle-table-container"); |
| 342 | + |
| 343 | + const table = createTable(headers, rows, tableClass, viewgraph, deviceId); |
| 344 | + const button = createToggleButton(title, buttonClass, table); |
153 | 345 |
|
154 | | - // Add button and table to container |
155 | 346 | container.appendChild(button); |
156 | 347 | container.appendChild(table); |
157 | 348 |
|
|
0 commit comments