Last active
August 2, 2025 07:07
-
-
Save akansha-oi/e8c8c69daa4725e367965fd5359c04e9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Global variables | |
| // const BASE_URL = 'http://192.168.2.1:8443/api'; | |
| const BASE_URL = 'http://localhost:8443/api'; | |
| // const ip = '192.168.2.1'; // Hardcoded IP, mirroring original code's approach | |
| let refreshInterval; // For port status polling | |
| // Utility function to render content into main container | |
| function showContent(content) { | |
| const container = document.getElementById('main-content'); | |
| container.innerHTML = content; | |
| } | |
| // Navigation handler | |
| function loadContent(type) { | |
| if (refreshInterval) { | |
| clearInterval(refreshInterval); | |
| refreshInterval = null; | |
| } | |
| switch (type) { | |
| case 'industrial': | |
| showIndustrialMonitoring(); | |
| break; | |
| case 'system': | |
| showSystemConfiguration(); | |
| break; | |
| case 'serial': | |
| showSerialConfiguration(); | |
| break; | |
| case 'reboot': | |
| showRebootOptions(); | |
| break; | |
| case 'ping': | |
| showPingDiagnosis(); | |
| break; | |
| case 'user_management': | |
| showUserManagement(); | |
| break; | |
| case 'poe_query_config': | |
| showPoeQueryConfig(); | |
| break; | |
| case 'user_safety_config': | |
| showUserSafetyConfig(); | |
| break; | |
| case 'sntp_config': | |
| showSntpConfig(); | |
| break; | |
| case 'jumbo_frame_config': | |
| showJumboFrameConfig(); | |
| break; | |
| case 'save_config': | |
| showSaveConfig(); | |
| break; | |
| case 'config_file': | |
| showConfigFile(); | |
| break; | |
| case 'file_upload': | |
| showFileUpload(); | |
| break; | |
| case 'port_common_config': | |
| showPortCommonConfig(); | |
| refreshInterval = setInterval(fetchPortStatus, 10000); | |
| break; | |
| case 'port_stats': | |
| showPortStats(); | |
| break; | |
| case 'strom': | |
| showStormControl(); | |
| break; | |
| case 'flow_control': | |
| showFlowControl(); | |
| break; | |
| case 'port_limit': | |
| showPortRateLimit(); | |
| break; | |
| case 'protected_port': | |
| showProtectedPort(); | |
| break; | |
| case 'learn_limit': | |
| showLearnLimit(); | |
| break; | |
| case 'community_name': | |
| showCommunityName(); | |
| break; | |
| case 'trap_target': | |
| showTrapTargets(); | |
| break; | |
| case 'qos_apply': | |
| showQoS(); | |
| break; | |
| case 'qos_schedule': | |
| showQoSSchedule(); | |
| break; | |
| case 'aaa_authentication': | |
| showAAAAuthentication(); | |
| break; | |
| case 'tacacs_config': | |
| showTacacsConfig(); | |
| break; | |
| case 'radius_config': | |
| showRadiusConfig(); | |
| break; | |
| case 'dot1x_config': | |
| showDot1xConfig(); | |
| break; | |
| case 'dot1x_port_config': | |
| showDot1xPortConfig(); | |
| break; | |
| case 'dot1x_user_auth': | |
| showDot1xUserAuth(); | |
| break; | |
| case 'igmp_snooping_config': | |
| showIgmpSnoopingConfig(); | |
| break; | |
| case 'multicast_group_info': | |
| showMulticastGroupInfo(); | |
| break; | |
| case 'gmrp_global_config': | |
| showGmrpGlobalConfig(); | |
| break; | |
| case 'gmrp_ports_config': | |
| showGmrpPortsConfig(); | |
| break; | |
| case 'gmrp_state_machine': | |
| showGmrpStateMachine(); | |
| break; | |
| case 'gvrp_global_config': | |
| showGvrpGlobalConfig(); | |
| break; | |
| case 'gvrp_ports_config': | |
| showGvrpPortsConfig(); | |
| break; | |
| case 'gvrp_state_machine': | |
| showGvrpStateMachine(); | |
| break; | |
| case 'eaps_config': | |
| showEapsConfig(); | |
| break; | |
| case 'eaps_info': | |
| showEapsInfo(); | |
| break; | |
| case 'rmon_statistics': | |
| showRmonStatistics(); | |
| break; | |
| case 'rmon_history': | |
| showRmonHistory(); | |
| break; | |
| case 'rmon_alarm': | |
| showRmonAlarm(); | |
| break; | |
| case 'rmon_event': | |
| showRmonEvent(); | |
| break; | |
| case 'erps_config': | |
| showErpsConfig(); | |
| break; | |
| case 'erps_info': | |
| showErpsInfo(); | |
| break; | |
| case 'lldp_global': | |
| lldpGlobal(); | |
| break | |
| case 'lldp_ports': | |
| lldpPort(); | |
| break; | |
| case 'lldp_neighbor': | |
| lldpNeighbor(); | |
| break | |
| case 'mac_address_table': | |
| showMacAddressTable(); | |
| break; | |
| case 'mac_bind_table': | |
| showMacBindTable(); | |
| break; | |
| case 'mac_auto_bind': | |
| macAutoBind(); | |
| break; | |
| case 'mac_auto_filter': | |
| macAutoFind(); | |
| break; | |
| case 'mac_filter': | |
| showMacFilter(); | |
| break; | |
| case 'vlans': | |
| showVlans(); | |
| break; | |
| case 'static_vlan': | |
| fetchVlanData(); | |
| break; | |
| case 'vlan_port_config': | |
| showVlanPortConfig(); | |
| break; | |
| case 'snmp_config': | |
| snmpConfiguration(); | |
| break; | |
| case 'ip_address_config': | |
| ipAddressConfig(); | |
| break; | |
| case 'arp_config': | |
| arpConfigandDisplay(); | |
| break; | |
| case 'host_static_route': | |
| hostStaticRoute(); | |
| break; | |
| case 'mstp_global_config': | |
| mstpGlobalConfig(); | |
| break; | |
| case 'mstp_port_config': | |
| mstpPortConfig(); | |
| break; | |
| case 'mstp_show': | |
| mstpPortInformation(); | |
| break; | |
| case 'ndp_config': | |
| ndpConfig(); | |
| break; | |
| case 'ntdp_config': | |
| ntdpConfig(); | |
| break; | |
| case 'cluster_config': | |
| showClusterConfigPage(); | |
| break; | |
| case 'poe_policy_config': | |
| poePolicyConfig(); | |
| break; | |
| case 'poe_port_config': | |
| poePortConfig(); | |
| break; | |
| case 'log_information': | |
| logInfo(); | |
| break; | |
| case 'log_configuration': | |
| logConfig(); | |
| break; | |
| case 'standard_ip': | |
| standardIP(); | |
| break; | |
| case 'extended_ip': | |
| extendedIP(); | |
| break; | |
| case 'mac_ip': | |
| macIP(); | |
| break; | |
| case 'mac_arp': | |
| macARP(); | |
| break; | |
| case 'acl_information': | |
| aclInformation(); | |
| break; | |
| case 'acl_reference': | |
| aclReference(); | |
| break; | |
| case 'port_trunking': | |
| portTrunking(); | |
| break; | |
| case 'mirror': | |
| mirror(); | |
| break | |
| case 'ddm_information': | |
| ddmConfiguration(); | |
| break; | |
| case 'cable_diagnosis': | |
| cableDiagnosis(); | |
| break; | |
| default: | |
| showContent('<h2>Home</h2><p>Welcome to the switch management interface.</p>'); | |
| } | |
| } | |
| // Initialize page | |
| window.onload = () => { | |
| loadContent('industrial'); | |
| fetchPortStatus(); | |
| refreshInterval = setInterval(fetchPortStatus, 10000); | |
| }; | |
| // Cleanup on window unload | |
| window.onunload = () => { | |
| if (refreshInterval) { | |
| clearInterval(refreshInterval); | |
| } | |
| }; | |
| // Toggle dropdown menu icon and visibility | |
| function toggleIcon(el) { | |
| const img = el.querySelector('img'); | |
| const submenu = el.nextElementSibling; | |
| const isClosed = img.src.includes('close.png'); | |
| img.src = isClosed ? 'open.png' : 'close.png'; | |
| if (submenu && submenu.classList.contains('submenu')) { | |
| submenu.style.display = submenu.style.display === 'block' ? 'none' : 'block'; | |
| } | |
| } | |
| // Close dropdowns when clicking outside | |
| document.addEventListener('click', function (event) { | |
| const dropdowns = document.querySelectorAll('.submenu'); | |
| let isDropdownClick = false; | |
| dropdowns.forEach(dropdown => { | |
| if (dropdown.contains(event.target) || dropdown.previousElementSibling.contains(event.target)) { | |
| isDropdownClick = true; | |
| } | |
| }); | |
| if (!isDropdownClick) { | |
| dropdowns.forEach(dropdown => { | |
| dropdown.style.display = 'none'; | |
| }); | |
| } | |
| }); | |
| // Fetch port status for real-time updates | |
| async function fetchPortStatus() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/port-status`); | |
| const data = await response.json(); | |
| updateSwitchIndicators(data); | |
| if (document.getElementById('main-content').innerHTML.includes('Port Common Configuration')) { | |
| loadPortTableData(data); // Update port table if on port_common_config | |
| } | |
| } catch (err) { | |
| console.log('Error fetching port status:', err); | |
| } | |
| } | |
| // Update switch indicators based on port status | |
| function updateSwitchIndicators(data) { | |
| const switchElements = document.querySelectorAll('.switch'); | |
| switchElements.forEach((el, index) => { | |
| const portStatus = data[index] || { status: 'down' }; | |
| el.classList.remove('green', 'gray', 'red'); | |
| if (portStatus.status === 'up') { | |
| el.classList.add('green'); | |
| } else if (portStatus.status === 'down') { | |
| el.classList.add('gray'); | |
| } else if (portStatus.status === 'disabled') { | |
| el.classList.add('red'); | |
| } | |
| }); | |
| } | |
| // Industrial Switch Monitoring | |
| function showIndustrialMonitoring() { | |
| showContent(` | |
| <div style="padding: 10px;"> | |
| <h2 >Industrial Switch Monitoring</h2> | |
| <p style="color:#555;">Real-time overview of switch performance and port status.</p> | |
| <div style="background-color: #f9f9f9; border: 1px solid #ddd; padding: 15px; margin: 10px 0; border-radius: 5px;"> | |
| <h3 style="color:#333; margin-top: 0;">Status Dashboard</h3> | |
| <table> | |
| <tr><td style="width:150px;"><strong>Switch Status</strong></td><td id="statusValue">Loading...</td></tr> | |
| <tr><td><strong>Uptime</strong></td><td id="uptimeValue">Loading...</td></tr> | |
| <tr><td><strong>CPU Usage</strong></td><td id="cpuValue">Loading...</td></tr> | |
| <tr><td><strong>Memory Usage</strong></td><td id="memoryValue">Loading...</td></tr> | |
| <tr><td><strong>Temperature</strong></td><td id="tempValue">Loading...</td></tr> | |
| <tr><td><strong>Power Status</strong></td><td id="powerValue">Loading...</td></tr> | |
| </table> | |
| </div> | |
| <div style="background-color: #f9f9f9; border: 1px solid #ddd; padding: 15px; margin: 10px 0; border-radius: 5px;"> | |
| <h3 style="color:#333; margin-top: 0;">Port Activity</h3> | |
| <table> | |
| <thead> | |
| <tr style="background-color: #e44a4a; color: white;"> | |
| <th>Port</th><th>Link Status</th><th>Speed</th><th>Activity</th> | |
| </tr> | |
| </thead> | |
| <tbody id="portActivityTable"> | |
| <tr><td colspan="4">Loading...</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| <div class="buttons"> | |
| <button onclick="showIndustrialMonitoring()" id="refreshBtn" >Refresh</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| </div> | |
| `); | |
| loadIndustrialData(); | |
| } | |
| async function loadIndustrialData() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/industrial-status`); | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| // Update status table | |
| document.getElementById('statusValue').textContent = data.status || 'Unknown'; | |
| document.getElementById('uptimeValue').textContent = data.uptime || 'Unknown'; | |
| document.getElementById('cpuValue').textContent = `${data.cpuUsage || 0}%`; | |
| document.getElementById('memoryValue').textContent = `${data.memoryUsage || 0}%`; | |
| document.getElementById('tempValue').textContent = `${data.temperature || 0}°C`; | |
| document.getElementById('powerValue').textContent = data.powerStatus || 'Unknown'; | |
| // Update port activity table | |
| const tbody = document.getElementById('portActivityTable'); | |
| tbody.innerHTML = (data.ports || []).slice(0, 8).map((port, index) => ` | |
| <tr> | |
| <td>${index + 1}</td> | |
| <td>${port.linkStatus || 'Down'}</td> | |
| <td>${port.speed || 'N/A'}</td> | |
| <td>${port.activity || 'Idle'}</td> | |
| </tr> | |
| `).join('') || '<tr><td colspan="4">No port data available</td></tr>'; | |
| } catch (err) { | |
| console.error('Error fetching industrial status:', err); | |
| // Fallback data if API fails | |
| document.getElementById('statusValue').textContent = 'Offline'; | |
| document.getElementById('uptimeValue').textContent = 'N/A'; | |
| document.getElementById('cpuValue').textContent = '0%'; | |
| document.getElementById('memoryValue').textContent = '0%'; | |
| document.getElementById('tempValue').textContent = 'N/A'; | |
| document.getElementById('powerValue').textContent = 'N/A'; | |
| const tbody = document.getElementById('portActivityTable'); | |
| tbody.innerHTML = '<tr><td colspan="4">API unavailable. Please check the server.</td></tr>'; | |
| } | |
| } | |
| // System Configuration | |
| async function showSystemConfiguration() { | |
| showContent('<p>Loading system configuration...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/system-config`); | |
| const data = await response.json(); | |
| showContent(` | |
| <h2>System Configuration</h2> | |
| <table> | |
| <tr ><td><strong>System Description</strong></td><td>${data.description}</td></tr> | |
| <tr><td><strong>System Object ID</strong></td><td>${data.objectId}</td></tr> | |
| <tr><td><strong>System Version</strong></td><td>${data.version}</td></tr> | |
| <tr><td><strong>Num Network Interfaces</strong></td><td>${data.interfaces}</td></tr> | |
| <tr><td><strong>Serial Number</strong></td><td>${data.serialNumber}</td></tr> | |
| <tr><td><strong>MAC Address</strong></td><td>${data.macAddress}</td></tr> | |
| <tr><td><strong>IP Address</strong></td><td>${data.ipAddress}</td></tr> | |
| <tr><td><strong>System Start Time</strong></td><td>${data.startTime}</td></tr> | |
| <tr><td><strong>System Date Time</strong></td> | |
| <td>${data.dateTime} <span style="color:gray;">(Format: Year/Month/Day Hour:Minute:Second)</span></td> | |
| </tr> | |
| <tr><td><strong>System Name</strong></td><td><textarea id="systemName">${data.systemName}</textarea></td></tr> | |
| <tr><td><strong>System Location</strong></td><td><textarea id="systemLocation">${data.systemLocation}</textarea></td></tr> | |
| <tr><td><strong>System Contact</strong></td><td><textarea id="systemContact">${data.systemContact}</textarea></td></tr> | |
| </table> | |
| <br> | |
| <div class="buttons"> | |
| <button onclick="showSystemConfiguration()" id="refreshBtn">Refresh</button> | |
| <button onclick="applySystemConfig()" id="applyBtn">Apply</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| } catch (err) { | |
| showContent('<p style="color:red;">Failed to load system configuration.</p>'); | |
| console.log('Error fetching system config:', err); | |
| } | |
| } | |
| async function applySystemConfig() { | |
| const name = document.getElementById('systemName').value; | |
| const location = document.getElementById('systemLocation').value; | |
| const contact = document.getElementById('systemContact').value; | |
| try { | |
| const response = await fetch(`${BASE_URL}/system-config`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ systemName: name, systemLocation: location, systemContact: contact }) | |
| }); | |
| const result = await response.json(); | |
| alert(result.message || 'System configuration applied successfully.'); | |
| } catch (err) { | |
| console.log('Failed to apply system config:', err); | |
| alert('Failed to apply system configuration.'); | |
| } | |
| } | |
| // Serial Port Configuration | |
| async function showSerialConfiguration() { | |
| showContent('<p>Loading serial port configuration...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/serial-config`); | |
| const data = await response.json(); | |
| showContent(` | |
| <h2>Serial Port Configuration</h2> | |
| <table> | |
| <tr><td><strong>Baud Rate</strong></td><td>${data.baudRate}</td></tr> | |
| <tr><td><strong>Character Size</strong></td><td>${data.charSize}</td></tr> | |
| <tr><td><strong>Parity Code</strong></td><td>${data.parity}</td></tr> | |
| <tr><td><strong>Stop Bits</strong></td><td>${data.stopBits}</td></tr> | |
| <tr><td><strong>Flow Control</strong></td><td>${data.flowControl}</td></tr> | |
| </table> | |
| <br> | |
| <div class="buttons" > | |
| <button onclick="showSerialConfiguration()" id="refreshBtn">Refresh</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button></div> | |
| `); | |
| } catch (err) { | |
| showContent('<p style="color:red;">Failed to load serial port configuration.</p>'); | |
| console.log('Error fetching serial config:', err); | |
| } | |
| } | |
| // Reboot Options | |
| function showRebootOptions() { | |
| showContent(` | |
| <h2>System Reboot</h2> | |
| <div class="buttons"> | |
| <button onclick="confirmReboot()" style="margin-right: 10px;">Reboot</button> | |
| <button onclick="confirmFactoryReset()" style="margin-right: 10px;">Reboot Factory</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| } | |
| async function confirmReboot() { | |
| if (confirm('Do you want to reboot the switch?')) { | |
| try { | |
| const response = await fetch(`${BASE_URL}/reboot`, { method: 'POST' }); | |
| if (!response.ok) throw new Error('Reboot failed'); | |
| alert('System is rebooting...'); | |
| setTimeout(() => { | |
| alert('✅ System rebooted successfully.'); | |
| location.reload(); | |
| }, 5000); | |
| } catch (err) { | |
| console.log('Reboot error:', err); | |
| alert('❌ Failed to reboot the switch.'); | |
| } | |
| } | |
| } | |
| async function confirmFactoryReset() { | |
| if (confirm('Do you want to restore factory settings and reboot the switch?')) { | |
| try { | |
| const response = await fetch(`${BASE_URL}/factory-reset`, { method: 'POST' }); | |
| if (!response.ok) throw new Error('Factory reset failed'); | |
| alert('Factory reset and rebooting...'); | |
| setTimeout(() => { | |
| alert('✅ Factory reset completed and system rebooted.'); | |
| location.reload(); | |
| }, 5000); | |
| } catch (err) { | |
| console.log('Factory reset error:', err); | |
| alert('❌ Failed to perform factory reset.'); | |
| } | |
| } | |
| } | |
| // Ping Diagnosis | |
| function showPingDiagnosis() { | |
| showContent(` | |
| <h2>Ping Diagnosis</h2> | |
| <table> | |
| <tr> | |
| <td><strong>Target IP Address</strong></td> | |
| <td><input type="text" style= "width: 20%"; id="pingIp" placeholder="e.g. 192.168.1.1" /> | |
| <span style="color:gray;">(IP address format: A.B.C.D)</span></td> | |
| </tr> | |
| <tr> | |
| <td><strong>Data Length</strong></td> | |
| <td><input type="number" id="pingLength" placeholder="e.g. 64" min="32" max="4000" /> | |
| <span style="color:gray;">(32-4000)</span></td> | |
| </tr> | |
| <tr> | |
| <td><strong>Count</strong></td> | |
| <td><input type="number" id="pingCount" placeholder="e.g. 4" min="1" max="10" /> | |
| <span style="color:gray;">(1-10)</span></td> | |
| </tr> | |
| </table> | |
| <br> | |
| <div class="buttons"> | |
| <button onclick="runPingDiagnosis()">Diagnosis</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| <div id="pingResult" style="margin-top: 20px; color: green;"></div> | |
| `); | |
| } | |
| async function runPingDiagnosis() { | |
| const ip = document.getElementById('pingIp').value; | |
| const length = document.getElementById('pingLength').value; | |
| const count = document.getElementById('pingCount').value; | |
| if (!ip || !length || !count) { | |
| alert('Please fill in all fields.'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/ping`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ ip, length: parseInt(length), count: parseInt(count) }) | |
| }); | |
| const data = await response.json(); | |
| showPingResponse(data); | |
| } catch (err) { | |
| console.log('Ping error:', err); | |
| alert('Failed to run ping diagnosis.'); | |
| } | |
| } | |
| function showPingResponse(data) { | |
| const responseHtml = ` | |
| <h3>Ping Test Results</h3> | |
| <p><strong>Status:</strong> ${data.status}</p> | |
| <p><strong>Packets Sent:</strong> ${data.packets_sent}</p> | |
| <p><strong>Packets Received:</strong> ${data.packets_received}</p> | |
| <p><strong>Packet Loss:</strong> ${data.packet_loss}</p> | |
| `; | |
| document.getElementById('pingResult').innerHTML = responseHtml; | |
| } | |
| // User Management Configuration | |
| function showUserManagement() { | |
| showContent(` | |
| <h2>Multi-user Management Configuration</h2> | |
| <table> | |
| <thead> | |
| <tr> | |
| <th>Item</th><th>User name</th><th>Old password</th><th>New password</th><th>Re-enter password</th><th>Privilege</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td><strong>New</strong> <input type="checkbox" id="selectAllCheckbox" data-item="new"></td> | |
| <td><input type="text" id="username" placeholder="User name" style="width:90%;"></td> | |
| <td><input type="password" id="oldPassword" placeholder="Old password" style="width:90%;"></td> | |
| <td><input type="password" id="newPassword" placeholder="New password" style="width:90%;"></td> | |
| <td><input type="password" id="reenterPassword" placeholder="Re-enter password" style="width:90%;"></td> | |
| <td><select id="privilege" value = "Privilege"><option>Privilege</option></select></td> | |
| </tr> | |
| </tbody> | |
| <tbody id="userList"> | |
| <tr> | |
| <td>1 <input type="checkbox" class="deleteCheckbox" data-item="1"></td> | |
| <td>admin</td> | |
| <td><input type="password" placeholder="Old password" value="*****" readonly style="width:90%;"></td> | |
| <td><input type="password" placeholder="New password" style="width:90%;"></td> | |
| <td><input type="password" placeholder="Re-enter password" style="width:90%;"></td> | |
| <td><select><option>Privilege</option></select></td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <div class="buttons"> | |
| <button onclick="showUserManagement()" id="refreshBtn">Refresh</button> | |
| <button onclick="applyUserManagement()" id="applyBtn">Apply</button> | |
| <button onclick="deleteUser()" id="deleteBtn" >Delete</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| // Add event listener for Select All | |
| document.getElementById('selectAllCheckbox').addEventListener('change', function () { | |
| const isChecked = this.checked; | |
| document.querySelectorAll('.deleteCheckbox').forEach(cb => cb.checked = isChecked); | |
| }); | |
| } | |
| async function applyUserManagement() { | |
| const item = document.getElementById('userItem').value; // Note: userItem select is missing, added below | |
| const username = document.getElementById('username').value; | |
| const oldPassword = document.getElementById('oldPassword').value; | |
| const newPassword = document.getElementById('newPassword').value; | |
| const reenterPassword = document.getElementById('reenterPassword').value; | |
| const privilege = document.getElementById('privilege').value; | |
| if (newPassword !== reenterPassword) { | |
| alert('New password and re-entered password do not match.'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/user-management`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ item: 'new', username, oldPassword, newPassword, privilege }) | |
| }); | |
| const result = await response.json(); | |
| alert(result.message || 'User configuration applied successfully.'); | |
| if (username && newPassword) { | |
| const userList = document.getElementById('userList'); | |
| const existingRows = document.querySelectorAll('#userList tr'); | |
| const newItemId = (existingRows.length + 1).toString(); | |
| userList.innerHTML += ` | |
| <tr> | |
| <td>${newItemId} <input type="checkbox" class="deleteCheckbox" data-item="${newItemId}"></td> | |
| <td>${username}</td> | |
| <td><input type="password" placeholder="Old password" value="*****" readonly style="width:90%;"></td> | |
| <td><input type="password" placeholder="New password" style="width:90%;"></td> | |
| <td><input type="password" placeholder="Re-enter password" style="width:90%;"></td> | |
| <td> | |
| <select id="privilege"> | |
| <option value="">Privilege</option> | |
| <option value="admin">admin</option> | |
| <option value="normal">normal</option> | |
| </select> | |
| </td> | |
| </tr> | |
| `; | |
| document.getElementById('username').value = ''; | |
| document.getElementById('oldPassword').value = ''; | |
| document.getElementById('newPassword').value = ''; | |
| document.getElementById('reenterPassword').value = ''; | |
| document.getElementById('privilege').value = 'Privilege'; | |
| } | |
| } catch (err) { | |
| console.log('User management error:', err); | |
| alert('Failed to apply user configuration.'); | |
| } | |
| } | |
| async function deleteUser() { | |
| const checkboxes = document.querySelectorAll('.deleteCheckbox:checked'); | |
| if (!isNaN(item)) { | |
| alert('Select at least one user to delete.'); | |
| return; | |
| } | |
| try { | |
| for (const checkbox of checkboxes) { | |
| const item = checkbox.getAttribute('data-item'); | |
| if (!isNaN(item)) { // Only delete numeric item IDs | |
| const response = await fetch(`${BASE_URL}/user-management/${item}`, { method: 'DELETE' }); | |
| const result = await response.json(); | |
| if (!response.ok) throw new Error(result.message || 'Delete failed'); | |
| } | |
| checkbox.parentElement.parentElement.remove(); // Remove the row from UI | |
| } | |
| alert('Selected users deleted successfully.'); | |
| showUserManagement() // Refresh to ensure consistency | |
| } catch (err) { | |
| console.log('Delete user error:', err); | |
| alert('Failed to delete one or more users.'); | |
| } | |
| } | |
| // PoE Query Configuration | |
| let selectedIndex = null; // Keep track of selected row | |
| async function showPoeQueryConfig() { | |
| showContent(` | |
| <h2 style="color:#e55353; font-weight:700; text-align:center;">PD Query Configuration</h2> | |
| <form id="queryForm" style="margin-bottom:10px;"> | |
| <table> | |
| <tr> | |
| <td style="background:#add8ff; font-weight:600;">PoE Port</td> | |
| <td> | |
| <select id="poePort"> | |
| <option value="">Select</option> | |
| <option value="1">1</option> | |
| <option value="2">2</option> | |
| <option value="3">3</option> | |
| <option value="4">4</option> | |
| <option value="5">5</option> | |
| <option value="6">6</option> | |
| <option value="7">7</option> | |
| <option value="8">8</option> | |
| </select> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="background:#add8ff; font-weight:600;">PD IP Address</td> | |
| <td><input type="text" id="pdIpAddress" placeholder="Enter IP Address" /></td> | |
| </tr> | |
| <tr> | |
| <td style="background:#add8ff; font-weight:600;">PD Query Interval</td> | |
| <td><input type="number" id="pdQueryInterval" min="2" max="30" value="5" /> (2~30 Sec)</td> | |
| </tr> | |
| <tr> | |
| <td style="background:#add8ff; font-weight:600;">PD Timeout Number</td> | |
| <td><input type="number" id="pdTimeoutNumber" min="2" max="10" value="3" /> (2~10)</td> | |
| </tr> | |
| <tr> | |
| <td style="background:#add8ff; font-weight:600;">PD Boot Time</td> | |
| <td><input type="number" id="pdBootTime" min="30" max="600" value="120" /> (30~600 Sec)</td> | |
| </tr> | |
| </table> | |
| <div style="margin-top:10px; text-align:center;"> | |
| <button type="button" onclick="submitPoeForm()">Apply</button> | |
| <button type="button" onclick="showPoeQueryConfig()">Refresh</button> | |
| </div> | |
| </form> | |
| <table id="configTable" border="1" cellspacing="0" cellpadding="5" style="width:100%; font-size:14px; border-collapse:collapse;"> | |
| <thead style="background:#add8ff; font-weight:600;"> | |
| <tr> | |
| <th>PoE Port</th> | |
| <th>PD IP Address</th> | |
| <th>PD Query Interval (Sec)</th> | |
| <th>PD Timeout Number</th> | |
| <th>PD Boot Time (Sec)</th> | |
| <th>PD Reboot Times</th> | |
| </tr> | |
| </thead> | |
| <tbody id="poeTableBody"></tbody> | |
| </table> | |
| `); | |
| await loadPoeTableData(); | |
| } | |
| async function loadPoeTableData() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/poe-config`); | |
| const data = await response.json(); | |
| const tbody = document.getElementById('poeTableBody'); | |
| // Cache data globally for form use | |
| window._poeTableCache = data; | |
| tbody.innerHTML = data.map((row, index) => ` | |
| <tr onclick="populateForm(${index})" style="cursor:pointer;"> | |
| <td>${row.port}</td> | |
| <td>${row.pdIpAddress || 'N/A'}</td> | |
| <td>${row.queryInterval}</td> | |
| <td>${row.timeoutNumber}</td> | |
| <td>${row.bootTime}</td> | |
| <td>${row.rebootTimes}</td> | |
| </tr> | |
| `).join(''); | |
| } catch (err) { | |
| console.error('Error loading PoE table:', err); | |
| alert('Failed to load PoE configuration.'); | |
| } | |
| } | |
| function populateForm(index) { | |
| const data = window._poeTableCache; | |
| if (!data || !data[index]) return; | |
| const row = data[index]; | |
| document.getElementById('poePort').value = row.port || ''; | |
| document.getElementById('pdIpAddress').value = row.pdIpAddress || ''; | |
| document.getElementById('pdQueryInterval').value = row.queryInterval || 5; | |
| document.getElementById('pdTimeoutNumber').value = row.timeoutNumber || 3; | |
| document.getElementById('pdBootTime').value = row.bootTime || 120; | |
| } | |
| async function submitPoeForm() { | |
| const poePort = document.getElementById('poePort').value.trim(); | |
| const pdIpAddress = document.getElementById('pdIpAddress').value.trim(); | |
| const pdQueryInterval = parseInt(document.getElementById('pdQueryInterval').value, 10); | |
| const pdTimeoutNumber = parseInt(document.getElementById('pdTimeoutNumber').value, 10); | |
| const pdBootTime = parseInt(document.getElementById('pdBootTime').value, 10); | |
| // Basic validation | |
| if (!poePort) { | |
| alert('Please select a PoE Port.'); | |
| return; | |
| } | |
| if (pdIpAddress && !isValidIpWithCidr(pdIpAddress)) { | |
| alert('Please enter a valid IP address.'); | |
| return; | |
| } | |
| if (isNaN(pdQueryInterval) || pdQueryInterval < 2 || pdQueryInterval > 30) { | |
| alert('PD Query Interval must be between 2 and 30.'); | |
| return; | |
| } | |
| if (isNaN(pdTimeoutNumber) || pdTimeoutNumber < 2 || pdTimeoutNumber > 10) { | |
| alert('PD Timeout Number must be between 2 and 10.'); | |
| return; | |
| } | |
| if (isNaN(pdBootTime) || pdBootTime < 30 || pdBootTime > 600) { | |
| alert('PD Boot Time must be between 30 and 600.'); | |
| return; | |
| } | |
| // Prepare payload | |
| const payload = { | |
| port: poePort, | |
| pdIpAddress, | |
| queryInterval: pdQueryInterval, | |
| timeoutNumber: pdTimeoutNumber, | |
| bootTime: pdBootTime, | |
| }; | |
| try { | |
| const res = await fetch(`${BASE_URL}/poe-config`, { | |
| method: 'POST', // or PUT based on your backend API | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify(payload), | |
| }); | |
| if (!res.ok) { | |
| throw new Error('Failed to save configuration'); | |
| } | |
| alert('Configuration saved successfully.'); | |
| // Refresh data & clear form | |
| await loadPoeTableData(); | |
| clearForm(); | |
| } catch (err) { | |
| console.error('Error saving configuration:', err); | |
| alert('Failed to save configuration.'); | |
| } | |
| } | |
| function clearForm() { | |
| document.getElementById('poePort').value = ''; | |
| document.getElementById('pdIpAddress').value = ''; | |
| document.getElementById('pdQueryInterval').value = 5; | |
| document.getElementById('pdTimeoutNumber').value = 3; | |
| document.getElementById('pdBootTime').value = 120; | |
| } | |
| // User Safety Configuration | |
| async function showUserSafetyConfig() { | |
| showContent('<p>Loading user safety configuration...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/user-safety`); | |
| const data = await response.json(); | |
| const rows = data.map(item => ` | |
| <tr> | |
| <td>${item.serviceType}</td> | |
| <td>${item.managementState}</td> | |
| <td>${item.aclGroup}</td> | |
| </tr> | |
| `).join(''); | |
| showContent(` | |
| <h2 style="color: red;">User Safety Configuration</h2> | |
| <table border="1" cellspacing="0" cellpadding="5"> | |
| <thead> | |
| <tr> | |
| <th>Service Type</th><th>Management State</th><th>Acl Group (1-99)</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td> | |
| <select id="serviceType" style ="width: 50%;"> | |
| <option value="">Select</option> | |
| <option value="HTTP">HTTP</option> | |
| <option value="SNMP">SNMP</option> | |
| <option value="TELNET">TELNET</option> | |
| <option value="SSH">SSH</option> | |
| </select> | |
| </td> | |
| <td> | |
| <select id="managementState" style ="width: 40%;"> | |
| <option value="Enable">Enable</option> | |
| <option value="Disable">Disable</option> | |
| </select> | |
| </td> | |
| <td> | |
| <input type="number" id="aclGroup" min="0" max="99" value="0"> | |
| </td> | |
| </tr> | |
| ${rows} | |
| </tbody> | |
| </table> | |
| <div class="buttons"> | |
| <button id = "refreshBtn" onclick="showUserSafetyConfig()">Refresh</button> | |
| <button id = "applyBtn" onclick="applyUserSafety()">Apply</button> | |
| <button id = "helpBtn" onclick="showHelp()">Help</button> | |
| </div> | |
| `); | |
| } catch (err) { | |
| console.error('Error fetching user safety config:', err); | |
| showContent('<p style="color:red;">Failed to load user safety config.</p>'); | |
| } | |
| } | |
| async function applyUserSafety() { | |
| const serviceType = document.getElementById('serviceType').value; | |
| const managementState = document.getElementById('managementState').value; | |
| const aclGroup = parseInt(document.getElementById('aclGroup').value); | |
| if (isNaN(aclGroup) || aclGroup < 1 || aclGroup > 99) { | |
| alert("ACL Group value must be between 1 and 99."); | |
| return; | |
| } | |
| if (!serviceType) { | |
| alert("Please select a service type."); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/user-safety`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ serviceType, managementState, aclGroup }) | |
| }); | |
| const result = await response.json(); | |
| alert(result.message || 'Updated.'); | |
| showUserSafetyConfig(); // Refresh view | |
| } catch (err) { | |
| console.error('Apply error:', err); | |
| alert('Failed to apply configuration.'); | |
| } | |
| } | |
| // SNTP Configuration | |
| async function showSntpConfig() { | |
| showContent('<p>Loading SNTP Configuration...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/sntp-config`); | |
| const data = await response.json(); | |
| showContent(` | |
| <h2>SNTP Configuration</h2> | |
| <table> | |
| <tr><td style = "font-weight:bold;" >IP Address 1:</td><td><input id="ipAddress1" value="${data.ipAddress1 || ''}"></td></tr> | |
| <tr><td style = "font-weight:bold;">IP Address 2:</td><td><input id="ipAddress2" value="${data.ipAddress2 || ''}"></td></tr> | |
| <tr><td style = "font-weight:bold;">IP Address 3:</td><td><input id="ipAddress3" value="${data.ipAddress3 || ''}"></td></tr> | |
| <tr><td style = "font-weight:bold;">Time Interval (s):</td><td><input id="timeInterval" type="number" min="0" value="${data.timeInterval || 0}"></td></tr> | |
| <tr><td style = "font-weight:bold;">Time Zone:</td><td><input id="timeZone" value="${data.timeZone || 'UTC+0'}"></td></tr> | |
| <tr><td style = "font-weight:bold;">DST Status:</td><td> | |
| <select id="dstStatus"> | |
| <option value="Enable" ${data.dstStatus === 'Enable' ? 'selected' : ''}>Enable</option> | |
| <option value="Disable" ${data.dstStatus === 'Disable' ? 'selected' : ''}>Disable</option> | |
| </select> | |
| </td></tr> | |
| <tr><td style = "font-weight:bold;">DST Start Time:</td><td><input id="dstStartTime" type="datetime-local" value="${data.dstStartTime || ''}"></td></tr> | |
| <tr><td style = "font-weight:bold;">DST End Time:</td><td><input id="dstEndTime" type="datetime-local" value="${data.dstEndTime || ''}"></td></tr> | |
| <tr><td style = "font-weight:bold;">DST Bias (0-99):</td><td><input id="dstBias" type="number" min="0" max="99" value="${data.dstBias || 0}"></td></tr> | |
| <tr><td style = "font-weight:bold;">Enable Status:</td><td> | |
| <select id="enableStatus"> | |
| <option value="Enable" ${data.enableStatus === 'Enable' ? 'selected' : ''}>Enable</option> | |
| <option value="Disable" ${data.enableStatus === 'Disable' ? 'selected' : ''}>Disable</option> | |
| </select> | |
| </td></tr> | |
| <tr><td style = "font-weight:bold;">System Time:</td><td><input id="systemDateTime" type="datetime-local" value="${data.systemDateTime || ''}"></td></tr> | |
| </table> | |
| <div class="buttons" style="margin-top:20px;"> | |
| <button onclick="showSntpConfig()" id = "refreshBtn" >Refresh</button> | |
| <button onclick="applySntpConfig()" id = "helpBtn">Apply</button> | |
| </div> | |
| `); | |
| } catch (err) { | |
| console.error("Failed to fetch SNTP config:", err); | |
| showContent("<p>Error loading SNTP Configuration.</p>"); | |
| } | |
| } | |
| async function applySntpConfig() { | |
| const ipAddress1 = document.getElementById('ipAddress1').value; | |
| const ipAddress2 = document.getElementById('ipAddress2').value; | |
| const ipAddress3 = document.getElementById('ipAddress3').value; | |
| const timeInterval = parseInt(document.getElementById('timeInterval').value); | |
| const timeZone = document.getElementById('timeZone').value; | |
| const dstStatus = document.getElementById('dstStatus').value; | |
| const dstStartTime = document.getElementById('dstStartTime').value; | |
| const dstEndTime = document.getElementById('dstEndTime').value; | |
| const dstBias = parseInt(document.getElementById('dstBias').value); | |
| const enableStatus = document.getElementById('enableStatus').value; | |
| const systemDateTime = document.getElementById('systemDateTime').value; | |
| // ✅ Validation | |
| if (isNaN(dstBias) || dstBias < 0 || dstBias > 99) { | |
| alert("DST Bias must be between 0 and 99"); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/sntp-config`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| ipAddress1, | |
| ipAddress2, | |
| ipAddress3, | |
| timeInterval, | |
| timeZone, | |
| dstStatus, | |
| dstStartTime, | |
| dstEndTime, | |
| dstBias, | |
| enableStatus, | |
| systemDateTime | |
| }) | |
| }); | |
| const result = await response.json(); | |
| alert(result.message || "SNTP Configuration applied successfully"); | |
| showSntpConfig(); // refresh after apply | |
| } catch (err) { | |
| console.error("Error applying SNTP config:", err); | |
| alert("Failed to apply configuration"); | |
| } | |
| } | |
| // Jumbo Frame Configuration | |
| async function showJumboFrameConfig() { | |
| showContent('<p>Loading Jumbo Frame Configuration...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/jumbo-frame`); | |
| const data = await response.json(); // Expected: { frameSize: 6000 } | |
| showContent(` | |
| <h2 style="color: red; text-align:center;">Jumbo Frame Configuration</h2> | |
| <table style="width:60%; border-collapse: collapse; margin:auto;"> | |
| <tr> | |
| <td><strong>Jumbo Frame Size</strong></td> | |
| <td> | |
| <input type="number" id="jumboFrameSize" value="${data.frameSize}" placeholder="in bytes" /> | |
| (1522-16283) | |
| </td> | |
| </tr> | |
| </table> | |
| <br> | |
| <div class="buttons"> | |
| <button onclick="showJumboFrameConfig()" id="refreshBtn">Refresh</button> | |
| <button onclick="applyJumboFrameConfig()" id="applyBtn">Apply</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| } catch (err) { | |
| console.error("Failed to load Jumbo Frame config:", err); | |
| showContent('<p style="color:red;">Failed to load Jumbo Frame Configuration.</p>'); | |
| } | |
| } | |
| async function applyJumboFrameConfig() { | |
| const frameSize = document.getElementById('jumboFrameSize').value; | |
| if (!frameSize || frameSize < 1522 || frameSize > 16283) { | |
| alert('Please enter a valid frame size (1522-16283).'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/jumbo-frame`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ frameSize: parseInt(frameSize) }) | |
| }); | |
| const result = await response.json(); | |
| alert(result.message || 'Jumbo frame configuration applied successfully.'); | |
| showJumboFrameConfig() | |
| } catch (err) { | |
| console.log('Jumbo frame config error:', err); | |
| alert('Failed to apply jumbo frame configuration.'); | |
| } | |
| } | |
| // Save Configuration | |
| function showSaveConfig() { | |
| showContent(` | |
| <h2>Save Current Configuration</h2> | |
| <p>Click the button below to save the current configuration to the switch.</p> | |
| <div class="buttons"> | |
| <button onclick="saveCurrentConfig()">Save Configuration</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| } | |
| async function saveCurrentConfig() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/save-config`, { method: 'POST' }); | |
| if (!response.ok) throw new Error('Failed to save configuration'); | |
| const result = await response.json(); | |
| alert(result.message || 'Configuration saved successfully.'); | |
| } catch (err) { | |
| console.log('Save config error:', err); | |
| alert('Failed to save configuration.'); | |
| } | |
| } | |
| // Configuration File Management | |
| function showConfigFile() { | |
| showContent(` | |
| <h2>Configuration File</h2> | |
| <p>Click the button below to download the current configuration file.</p> | |
| <div class="buttons"> | |
| <button onclick="fetchConfigData()">Show Configuration</button> | |
| <button onclick="downloadConfigFile()">Download</button> | |
| <button onclick="deleteConfigFile()" id="deleteBtn">Delete</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| <div id="configData"></div> | |
| `); | |
| } | |
| async function fetchConfigData() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/download-config`); | |
| const data = await response.text(); | |
| document.getElementById('configData').textContent = data; | |
| } catch (err) { | |
| console.log('Error fetching config:', err); | |
| alert('Failed to fetch configuration data.'); | |
| } | |
| } | |
| async function downloadConfigFile() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/download-config`); | |
| if (!response.ok) throw new Error('Failed to download configuration'); | |
| const blob = await response.blob(); | |
| const url = window.URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = 'switch_config.txt'; | |
| document.body.appendChild(a); | |
| a.click(); | |
| a.remove(); | |
| window.URL.revokeObjectURL(url); | |
| alert('Configuration file downloaded successfully.'); | |
| } catch (err) { | |
| console.log('Download config error:', err); | |
| alert('Failed to download configuration file.'); | |
| } | |
| } | |
| async function deleteConfigFile() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/delete-config`, { method: 'DELETE' }); | |
| if (!response.ok) throw new Error('Failed to delete configuration'); | |
| alert('Configuration file deleted successfully.'); | |
| document.getElementById('configData').textContent = ''; | |
| } catch (err) { | |
| console.log('Delete config error:', err); | |
| alert('Failed to delete configuration file.'); | |
| } | |
| } | |
| // File Upload | |
| function showFileUpload() { | |
| showContent(` | |
| <h2>File Upload</h2> | |
| <p>(Upload the Configuration File or Firmware File from your local computer to the switch)</p> | |
| <div class="warning" style="color: red; margin-bottom: 10px;"> | |
| <strong>Attention:</strong><br> | |
| - The Configuration File must have an <strong>.cfg</strong> extension<br> | |
| - The Firmware File must have an <strong>.img</strong> extension<br> | |
| - Do not interrupt the upload at any time as this may corrupt the Firmware or Configuration and potentially crash the system. | |
| </div> | |
| <input type="file" id="fileInput" accept=".cfg,.img" /> | |
| <div class="buttons"> | |
| <button onclick="uploadFile()">Upload</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| } | |
| async function uploadFile() { | |
| const fileInput = document.getElementById('fileInput'); | |
| const file = fileInput.files[0]; | |
| if (!file) { | |
| alert('Please select a file to upload.'); | |
| return; | |
| } | |
| if (!file.name.endsWith('.cfg') && !file.name.endsWith('.img')) { | |
| alert('Only .cfg or .img files are allowed.'); | |
| return; | |
| } | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| try { | |
| const response = await fetch(`${BASE_URL}/upload`, { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| if (!response.ok) throw new Error('Failed to upload file'); | |
| const result = await response.json(); | |
| alert(result.message || 'File uploaded successfully.'); | |
| } catch (err) { | |
| console.log('Upload error:', err); | |
| alert('Failed to upload file.'); | |
| } | |
| } | |
| // Port Common Configuration | |
| function showPortCommonConfig() { | |
| showContent(` | |
| <h2>Port Common Configuration</h2> | |
| <form id="configForm"> | |
| <div class="selected-ports" style="margin-bottom: 10px;"> | |
| <label for="adminStatus">Admin Status</label> | |
| <select id="adminStatus"> | |
| <option value="Up">Up</option> | |
| <option value="Down">Down</option> | |
| </select> | |
| <label for="configSpeed">Config Speed</label> | |
| <select id="configSpeed"> | |
| <option value="Auto-Negotiate">Auto-Negotiate</option> | |
| </select> | |
| <label for="description">Description</label> | |
| <input style= "width: 20%"; type="text" id="description" placeholder="Enter description"> | |
| </div> | |
| <div class="buttons"> | |
| <button type="button" onclick="showPortCommonConfig()" id="refreshBtn">Refresh</button> | |
| <button type="button" onclick="applyPortConfig()" id="applyBtn">Apply</button> | |
| <button type="button" onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| </form> | |
| <table > | |
| <caption>Port Common Configuration/Show</caption> | |
| <thead> | |
| <tr> | |
| <th><input type="checkbox" id="selectAll" onclick="toggleSelectAll()"></th> | |
| <th>Port</th><th>Description</th><th>Admin Status</th><th>Operate Status</th><th>Duplex & Bandwidth</th> | |
| <th>Config Speed</th><th>VLAN Mode</th><th>Default VLAN</th> | |
| </tr> | |
| </thead> | |
| <tbody id="portTableBody"></tbody> | |
| </table> | |
| `); | |
| loadPortTableData(); | |
| if (!refreshInterval) { | |
| refreshInterval = setInterval(fetchPortStatus, 10000); | |
| } | |
| } | |
| async function loadPortTableData(data = null) { | |
| try { | |
| const response = data ? { json: () => Promise.resolve(data) } : await fetch(`${BASE_URL}/port-config`); | |
| const portData = await response.json(); | |
| const tbody = document.getElementById('portTableBody'); | |
| tbody.innerHTML = portData.map(port => ` | |
| <tr> | |
| <td><input type="checkbox" class="port-checkbox"></td> | |
| <td>${port.port}</td> | |
| <td>${port.description}</td> | |
| <td>${port.adminStatus}</td> | |
| <td>${port.operateStatus}</td> | |
| <td>${port.duplex}</td> | |
| <td>${port.configSpeed}</td> | |
| <td>${port.vlanMode}</td> | |
| <td>${port.defaultVlan}</td> | |
| </tr> | |
| `).join(''); | |
| } catch (err) { | |
| console.log('Error fetching port config:', err); | |
| alert('Failed to load port configuration.'); | |
| } | |
| } | |
| function toggleSelectAll() { | |
| const selectAllCheckbox = document.getElementById('selectAll'); | |
| const checkboxes = document.querySelectorAll('.port-checkbox'); | |
| checkboxes.forEach(checkbox => { | |
| checkbox.checked = selectAllCheckbox.checked; | |
| }); | |
| } | |
| async function applyPortConfig() { | |
| const adminStatus = document.getElementById('adminStatus').value; | |
| const configSpeed = document.getElementById('configSpeed').value; | |
| const description = document.getElementById('description').value; | |
| const selectedPorts = Array.from(document.querySelectorAll('.port-checkbox:checked')).map(cb => cb.closest('tr').children[1].textContent); | |
| if (!selectedPorts.length) { | |
| alert('Please select at least one port.'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/port-config`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ ports: selectedPorts, adminStatus, configSpeed, description }) | |
| }); | |
| const result = await response.json(); | |
| alert(result.message || 'Port configuration applied successfully.'); | |
| loadPortTableData(); | |
| } catch (err) { | |
| console.log('Port config error:', err); | |
| alert('Failed to apply port configuration.'); | |
| } | |
| } | |
| // Help Page | |
| function showHelp() { | |
| showContent(` | |
| <div style="line-height: 1.6; font-size: 15px; padding: 10px;"> | |
| <h2>Online Help</h2> | |
| <h3>System Configuration</h3> | |
| <p>This page shows basic information about the switch and allows configuration of the following:</p> | |
| <ul> | |
| <li><strong>System Description:</strong> The switch's product description string.</li> | |
| <li><strong>System Object ID:</strong> SNMP OID representing the device.</li> | |
| <li><strong>System Version:</strong> The software/firmware version running on the switch.</li> | |
| <li><strong>Number of Network Interfaces:</strong> Total number of physical ports/interfaces.</li> | |
| <li><strong>Serial Number:</strong> The device's factory-encoded unique serial number.</li> | |
| <li><strong>MAC Address:</strong> The MAC address of the switch's main interface.</li> | |
| <li><strong>IP Address:</strong> IP address assigned to VLAN 1 (management VLAN).</li> | |
| <li><strong>System Start Time:</strong> Time elapsed since the device was powered on or last rebooted.</li> | |
| <li><strong>System Date Time:</strong> The current date and time on the system.</li> | |
| <li><strong>System Name:</strong> User-defined name for the switch. Used for identification.</li> | |
| <li><strong>System Location:</strong> Optional field to define where the switch is physically located.</li> | |
| <li><strong>System Contact:</strong> Optional contact information for the network administrator.</li> | |
| </ul> | |
| <p>After making changes to any editable fields, click <strong>Apply</strong> to save. Click <strong>Refresh</strong> to reload current data from the switch.</p> | |
| <hr> | |
| <h3>Serial Port Configuration</h3> | |
| <p>This page allows configuring serial communication parameters for the console port. Parameters include:</p> | |
| <ul> | |
| <li><strong>Baud Rate:</strong> Transmission speed (bits per second). Common values include 9600 and 38400.</li> | |
| <li><strong>Character Size:</strong> Number of data bits per character (typically 8).</li> | |
| <li><strong>Parity Code:</strong> Type of parity check used (None, Even, Odd).</li> | |
| <li><strong>Stop Bits:</strong> Number of stop bits at the end of transmission (usually 1).</li> | |
| <li><strong>Flow Control:</strong> Determines flow control method (None, RTS/CTS, XON/XOFF).</li> | |
| </ul> | |
| <hr> | |
| <h3>Ping Diagnosis</h3> | |
| <p>The ping test is used to check connectivity between the switch and another device. You can configure:</p> | |
| <ul> | |
| <li><strong>Target IP Address:</strong> IP address of the device to ping.</li> | |
| <li><strong>Data Length:</strong> Size (in bytes) of each ICMP ping packet (range: 32–4000).</li> | |
| <li><strong>Count:</strong> Number of ping packets to send (range: 1–10).</li> | |
| </ul> | |
| <p>After configuring the parameters, click <strong>Diagnosis</strong> to run the test. Results will be displayed below the form.</p> | |
| <hr> | |
| <h3>System Reboot</h3> | |
| <p>This page allows you to restart the switch in two ways:</p> | |
| <ul> | |
| <li><strong>Reboot:</strong> Simply restarts the switch. Current configuration is preserved.</li> | |
| <li><strong>Reboot Factory:</strong> Resets all settings to factory defaults and then reboots the switch.</li> | |
| </ul> | |
| <p>A confirmation dialog will appear before either action is performed. Use this function carefully, especially the factory reset option.</p> | |
| </div> | |
| `); | |
| } | |
| async function showPortStats() { | |
| showContent('<p>Loading port statistics...</p>'); | |
| try { | |
| // Step 1: Fetch available ports list | |
| const response = await fetch(`${BASE_URL}/port_stats`); // adjust endpoint if needed | |
| const data = await response.json(); | |
| // Step 2: Create dropdown options with no default selection | |
| let portOptions = `<option value="">-- Select Port --</option>`; | |
| data.ports.forEach(port => { | |
| portOptions += `<option value="${port}">${port}</option>`; | |
| }); | |
| // Step 3: Render the UI with dropdown and empty stat table | |
| showContent(` | |
| <h2>Port Statistics Information</h2> | |
| <div style="margin-bottom: 20px;"> | |
| <label for="portSelect"><strong>Port:</strong></label> | |
| <select id="portSelect" onchange="fetchPortStats(this.value)"> | |
| ${portOptions} | |
| </select> | |
| </div> | |
| <table> | |
| <thead style="background-color: #d9e5ff;"><tr><th colspan="4">Port Statistics Information</th></tr></thead> | |
| <tbody id="portStatsTable"> | |
| ${generateEmptyStatRows()} | |
| </tbody> | |
| </table> | |
| <br> | |
| <div class="buttons"> | |
| <button onclick="showPortStats()" id="refreshBtn">Refresh</button> | |
| <button onclick="showHelp()" id="helpBtn" >Help</button> | |
| </div> | |
| `); | |
| } catch (err) { | |
| showContent('<p style="color:red;">Failed to load ports.</p>'); | |
| console.log('Error fetching port list:', err); | |
| } | |
| } | |
| // Generates empty table rows initially | |
| function generateEmptyStatRows() { | |
| const labels = [ | |
| ['Received Total Bytes (ifInOctets)', 'Received Unicast Packets Num (ifInUcastPkts)'], | |
| ['Received Non-Unicast Packets Num (ifInNUcastPkts)', 'Received Discard Packets Num (ifInDiscards)'], | |
| ['Received Error Packets Num (ifInErrors)', 'Received Unknown Protocol Packets Num (ifInUnknownProtos)'], | |
| ['Send Total Bytes (ifOutOctets)', 'Send Unicast Packets Num (ifOutUcastPkts)'], | |
| ['Send Non-Unicast Packets Num (ifOutNUcastPkts)', 'Send Discard Packets Num (ifOutDiscards)'], | |
| ['Send Error Packets Num (ifOutErrors)', ''] | |
| ]; | |
| return labels.map(pair => ` | |
| <tr> | |
| <td>${pair[0]}</td><td id="${pair[0].split(' ')[0]}">0</td> | |
| <td>${pair[1]}</td><td id="${pair[1]?.split(' ')[0] || ''}">0</td> | |
| </tr> | |
| `).join(''); | |
| } | |
| // When user selects a port | |
| // async function fetchPortStats(port) { | |
| // if (!port) return; // do nothing if nothing selected | |
| // try { | |
| // const response = await fetch(`${BASE_URL}/get_port_stats?port=${port}`); | |
| // const stats = await response.json(); | |
| // // Update table values dynamically | |
| // document.getElementById('Received').textContent = stats.ifInOctets || 0; | |
| // document.getElementById('Received').nextElementSibling.textContent = stats.ifInUcastPkts || 0; | |
| // document.getElementById('Received').parentElement.nextElementSibling.children[1].textContent = stats.ifInNUcastPkts || 0; | |
| // document.getElementById('Received').parentElement.nextElementSibling.children[3].textContent = stats.ifInDiscards || 0; | |
| // document.getElementById('Received').parentElement.nextElementSibling.nextElementSibling.children[1].textContent = stats.ifInErrors || 0; | |
| // document.getElementById('Received').parentElement.nextElementSibling.nextElementSibling.children[3].textContent = stats.ifInUnknownProtos || 0; | |
| // // Continue filling the rest based on IDs or use a better structured ID system | |
| // } catch (err) { | |
| // alert('Failed to fetch port stats.'); | |
| // console.log('Error:', err); | |
| // } | |
| // } | |
| async function fetchPortStats(port) { | |
| if (!port) return; | |
| try { | |
| const response = await fetch(`${BASE_URL}/get_port_stats?port=${port}`); | |
| const stats = await response.json(); | |
| document.getElementById('ifInOctets').textContent = stats.ifInOctets || 0; | |
| document.getElementById('ifInUcastPkts').textContent = stats.ifInUcastPkts || 0; | |
| document.getElementById('ifInNUcastPkts').textContent = stats.ifInNUcastPkts || 0; | |
| document.getElementById('ifInDiscards').textContent = stats.ifInDiscards || 0; | |
| document.getElementById('ifInErrors').textContent = stats.ifInErrors || 0; | |
| document.getElementById('ifInUnknownProtos').textContent = stats.ifInUnknownProtos || 0; | |
| document.getElementById('ifOutOctets').textContent = stats.ifOutOctets || 0; | |
| document.getElementById('ifOutUcastPkts').textContent = stats.ifOutUcastPkts || 0; | |
| document.getElementById('ifOutNUcastPkts').textContent = stats.ifOutNUcastPkts || 0; | |
| document.getElementById('ifOutDiscards').textContent = stats.ifOutDiscards || 0; | |
| document.getElementById('ifOutErrors').textContent = stats.ifOutErrors || 0; | |
| } catch (err) { | |
| alert('Failed to fetch port stats.'); | |
| console.error('Error:', err); | |
| } | |
| } | |
| //BORADCAST STROM | |
| async function showStormControl() { | |
| showContent('<p>Loading storm control data...</p>'); | |
| try { | |
| const res = await fetch(`${BASE_URL}/storm-control`); | |
| const data = await res.json(); | |
| const portOptions = data.map(p => `<option value="${p.port}"> ${p.port}</option>`).join(''); | |
| const firstPort = data[0]; | |
| showContent(` | |
| <h2>Storm Control</h2> | |
| <div class="control-group"> | |
| <label>Port:</label> | |
| <select id="stormPort" onchange="onStormPortChange(this.value)"> | |
| ${portOptions} | |
| </select> | |
| </div> | |
| <div class="control-group"> | |
| <label>Broadcast:</label> | |
| <select id="broadcastSupp"> | |
| <option value="On">On</option> | |
| <option value="Off">Off</option> | |
| </select> | |
| <input id="broadcastRate" type="number" placeholder="Rate (kbps)" /> | |
| </div> | |
| <div class="control-group"> | |
| <label>Multicast:</label> | |
| <select id="multicastSupp"> | |
| <option value="On">On</option> | |
| <option value="Off">Off</option> | |
| </select> | |
| <input id="multicastRate" type="number" placeholder="Rate (kbps)" /> | |
| </div> | |
| <div class="control-group"> | |
| <label>DLF:</label> | |
| <select id="dlfSupp"> | |
| <option value="On">On</option> | |
| <option value="Off">Off</option> | |
| </select> | |
| <input id="dlfRate" type="number" placeholder="Rate (kbps)" /> | |
| </div> | |
| <div class ="buttons"> | |
| <button id="applyBtn" onclick="applyStormControl()">Apply</button> | |
| <button id="refreshBtn" onclick="showStormControl()">Refresh</button> | |
| </div> | |
| <table> | |
| <thead> | |
| <tr> | |
| <th>Port</th><th>Broadcast</th><th>Rate</th> | |
| <th>Multicast</th><th>Rate</th> | |
| <th>DLF</th><th>Rate</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| ${data.map(p => ` | |
| <tr> | |
| <td>${p.port}</td> | |
| <td>${p.broadcastSuppression}</td> | |
| <td>${p.broadcastRate}</td> | |
| <td>${p.multicastSuppression}</td> | |
| <td>${p.multicastRate}</td> | |
| <td>${p.dlfSuppression}</td> | |
| <td>${p.dlfRate}</td> | |
| </tr> | |
| `).join('')} | |
| </tbody> | |
| </table> | |
| `); | |
| onStormPortChange(firstPort.port, data); // auto-fill first one | |
| } catch (err) { | |
| console.error("Failed:", err); | |
| showContent('<p style="color:red;">Failed to load storm control info.</p>'); | |
| } | |
| } | |
| function onStormPortChange(port, dataCache = null) { | |
| const portData = (dataCache || window.cachedStormData || []).find(p => p.port === port); | |
| if (!portData) return; | |
| document.getElementById("broadcastSupp").value = portData.broadcastSuppression; | |
| document.getElementById("broadcastRate").value = portData.broadcastRate; | |
| document.getElementById("multicastSupp").value = portData.multicastSuppression; | |
| document.getElementById("multicastRate").value = portData.multicastRate; | |
| document.getElementById("dlfSupp").value = portData.dlfSuppression; | |
| document.getElementById("dlfRate").value = portData.dlfRate; | |
| } | |
| async function applyStormControl() { | |
| const port = document.getElementById("stormPort").value; | |
| const payload = { | |
| port, | |
| broadcastSuppression: document.getElementById("broadcastSupp").value, | |
| broadcastRate: parseInt(document.getElementById("broadcastRate").value), | |
| multicastSuppression: document.getElementById("multicastSupp").value, | |
| multicastRate: parseInt(document.getElementById("multicastRate").value), | |
| dlfSuppression: document.getElementById("dlfSupp").value, | |
| dlfRate: parseInt(document.getElementById("dlfRate").value), | |
| }; | |
| try { | |
| const res = await fetch(`${BASE_URL}/storm-control`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload) | |
| }); | |
| const result = await res.json(); | |
| alert(result.message); | |
| showStormControl(); // refresh table | |
| } catch (err) { | |
| console.error("POST failed:", err); | |
| alert("Failed to apply changes."); | |
| } | |
| } | |
| //PORT RATE LIMIT | |
| async function showPortRateLimit() { | |
| showContent('<p>Loading port rate limit data...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/port-rate-limit`); | |
| const data = await response.json(); | |
| const portOptions = Array.from({ length: 10 }, (_, i) => | |
| `<option value="${i + 1}">${i + 1}</option>` | |
| ).join(''); | |
| showContent(` | |
| <h2 style="color: #d9534f;">Port Rate Limit</h2> | |
| <div style="margin-bottom: 20px;"> | |
| <label><strong>Port:</strong></label> | |
| <select id="portSelect">${portOptions}</select> | |
| </div> | |
| <div style="margin-bottom: 20px;"> | |
| <strong>Send Packets Rate Control:</strong> | |
| <button id="sendToggleBtn" onclick="toggleControl('send')" class="toggle-btn off">Off</button> | |
| <input type="number" id="sendRate" class="rate-input hidden" placeholder="kbps (1-1024000)" /> | |
| </div> | |
| <div style="margin-bottom: 20px;"> | |
| <strong>Receive Packets Rate Control:</strong> | |
| <button id="recvToggleBtn" onclick="toggleControl('recv')" class="toggle-btn off">Off</button> | |
| <input type="number" id="recvRate" class="rate-input hidden" placeholder="kbps (1-1024000)" /> | |
| </div> | |
| <div> | |
| <button id="refreshBtn" onclick="showPortRateLimit()">Refresh</button> | |
| <button id="applyBtn" onclick="applyPortRateLimit()">Apply</button> | |
| <button id="helpBtn" onclick="showHelp()">Help</button> | |
| </div> | |
| <table> | |
| <thead style="background-color: #cce5ff;"> | |
| <tr><th>Port Name</th><th>Send Packets Rate (kbps)</th><th>Receive Packets Rate (kbps)</th></tr> | |
| </thead> | |
| <tbody id="rateTableBody"> | |
| ${data.map(p => ` | |
| <tr> | |
| <td>${p.port}</td> | |
| <td>${p.egressRate}</td> | |
| <td>${p.ingressRate}</td> | |
| </tr> | |
| `).join('')} | |
| </tbody> | |
| </table> | |
| `); | |
| } catch (err) { | |
| showContent('<p style="color:red;">Failed to load port rate limit info.</p>'); | |
| console.error("Error:", err); | |
| } | |
| } | |
| function toggleControl(type) { | |
| const btn = document.getElementById(`${type}ToggleBtn`); | |
| const input = document.getElementById(`${type}Rate`); | |
| const isOn = btn.classList.contains('on'); | |
| if (isOn) { | |
| btn.classList.remove('on'); | |
| btn.classList.add('off'); | |
| btn.textContent = 'Off'; | |
| input.classList.add('hidden'); | |
| input.value = ''; | |
| } else { | |
| btn.classList.remove('off'); | |
| btn.classList.add('on'); | |
| btn.textContent = 'On'; | |
| input.classList.remove('hidden'); | |
| } | |
| } | |
| async function applyPortRateLimit() { | |
| const port = document.getElementById('portSelect').value; | |
| const sendOn = document.getElementById('sendToggleBtn').classList.contains('on'); | |
| const recvOn = document.getElementById('recvToggleBtn').classList.contains('on'); | |
| const egressRate = sendOn ? Number(document.getElementById('sendRate').value) : 0; | |
| const ingressRate = recvOn ? Number(document.getElementById('recvRate').value) : 0; | |
| try { | |
| const response = await fetch(`${BASE_URL}/port-rate-limit`, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ port, egressRate, ingressRate }) | |
| }); | |
| if (response.ok) { | |
| showPortRateLimit() | |
| } else { | |
| alert("Failed to apply rate limit."); | |
| } | |
| } catch (err) { | |
| console.error("Apply error:", err); | |
| alert("Error applying port rate limit."); | |
| } | |
| } | |
| //FLOW CONTROL | |
| async function showFlowControl() { | |
| showContent('<p>Loading flow control data...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/flow-control`); | |
| const data = await response.json(); // Expected: [{port: ' 1', state: 'Off'}, ...] | |
| const portOptions = data.map(p => `<option value="${p.port}">${p.port}</option>`).join(''); | |
| // Pre-select first port's current state | |
| const firstPortState = data[0]?.state || 'Off'; | |
| showContent(` | |
| <h2 >Flow Control</h2> | |
| <div style="margin-bottom: 20px;"> | |
| <label for="flowPort" style="font-weight: bold;">Port:</label> | |
| <select style = "width:20%;" id="flowPort" onchange="onPortChange()" style="margin-right: 10px;"> | |
| ${portOptions} | |
| </select> | |
| <label for="flowState" style="font-weight: bold;">Flow Control</label> | |
| <select style = "width:20%;" id="flowState"> | |
| <option value="On" ${firstPortState === 'On' ? 'selected' : ''}>On</option> | |
| <option value="Off" ${firstPortState === 'Off' ? 'selected' : ''}>Off</option> | |
| </select> | |
| </div> | |
| <div class="buttons"> | |
| <button onclick="showFlowControl()" id="refreshBtn">Refresh</button> | |
| <button onclick="applyFlowControl()" id="applyBtn">Apply</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| <table> | |
| <thead style="background-color: #dce6f1;"> | |
| <tr><th>Port Name</th><th>Flow Control State</th></tr> | |
| </thead> | |
| <tbody> | |
| ${data.map(p => ` | |
| <tr> | |
| <td>${p.port}</td> | |
| <td>${p.state}</td> | |
| </tr> | |
| `).join('')} | |
| </tbody> | |
| </table> | |
| `); | |
| } catch (err) { | |
| console.error("Failed to load flow control info:", err); | |
| showContent('<p style="color:red;">Failed to load flow control info.</p>'); | |
| } | |
| } | |
| async function applyFlowControl() { | |
| const port = document.getElementById("flowPort").value; | |
| const state = document.getElementById("flowState").value; | |
| try { | |
| const response = await fetch(`${BASE_URL}/flow-control`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ port, state }) | |
| }); | |
| const result = await response.json(); | |
| alert(result.message || "Flow control settings applied."); | |
| showFlowControl(); // reload updated table | |
| } catch (err) { | |
| console.error("Apply failed:", err); | |
| alert("Failed to apply flow control."); | |
| } | |
| } | |
| // PROTECTED PORT = | |
| async function showProtectedPort() { | |
| showContent('<p>Loading protected port data...</p>'); | |
| try { | |
| const res = await fetch(`${BASE_URL}/protected-port`); | |
| const data = await res.json(); | |
| let tableRows = ''; | |
| for (let i = 1; i <= 10; i++) { | |
| const isProtected = data.find(p => p.port === i)?.protected || false; | |
| tableRows += ` | |
| <tr> | |
| <td><input type="checkbox" id="port${i}" ${isProtected ? 'checked' : ''} onchange="updateState(${i})"></td> | |
| <td>${i}</td> | |
| <td id="state${i}">${isProtected ? 'Protected' : 'Unprotected'}</td> | |
| </tr> | |
| `; | |
| } | |
| showContent(` | |
| <h2 style="color:red">Protected Port</h2> | |
| <table> | |
| <thead> | |
| <tr> | |
| <th>Protected Port</th><th>Port Name</th><th>State</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| ${tableRows} | |
| </tbody> | |
| </table> | |
| <div class="buttons"> | |
| <button onclick="applyProtectedPort()" id = "applyBtn" >Apply</button> | |
| <button onclick="showProtectedPort()" id="refreshBtn" >Refresh</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| } catch (err) { | |
| console.error("Failed:", err); | |
| showContent('<p style="color:red;">Failed to load protected port info.</p>'); | |
| } | |
| } | |
| function updateState(port) { | |
| const checkbox = document.getElementById(`port${port}`); | |
| const stateCell = document.getElementById(`state${port}`); | |
| stateCell.textContent = checkbox.checked ? 'Protected' : 'Unprotected'; | |
| } | |
| async function applyProtectedPort() { | |
| const protectedPorts = []; | |
| for (let i = 1; i <= 10; i++) { | |
| const checkbox = document.getElementById(`port${i}`); | |
| if (checkbox.checked) { | |
| protectedPorts.push(i); | |
| } | |
| } | |
| try { | |
| await fetch(`${BASE_URL}/protected-port`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ protectedPorts }) | |
| }); | |
| alert('Protected port settings applied successfully.'); | |
| } catch (err) { | |
| console.error("Failed:", err); | |
| alert('Failed to apply protected port settings.'); | |
| } | |
| } | |
| // PORT LEARN LIMIT | |
| // Learn Limit Configuration | |
| async function showLearnLimit() { | |
| showContent('<p>Loading Learn Limit Configuration...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/learn-limit`); | |
| const data = await response.json(); | |
| const tableRows = data.map((item, index) => ` | |
| <tr> | |
| <td>${item.port}</td> | |
| <td> | |
| <input type="number" id="macLimit_${item.port}" value="${item.macLimit}" min="0" max="8191" style="width: 90%;"> | |
| </td> | |
| <td><button id = "applyBtn" onclick="applyLearnLimit(${item.port})">Apply</button></td> | |
| <td><button id = "deleteBtn" onclick="cancelLearnLimit(${item.port})">Cancel</button></td> | |
| </tr> | |
| `).join(''); | |
| showContent(` | |
| <h2 style="color: red;">Learn Limit Configuration</h2> | |
| <table border="1" cellspacing="0" cellpadding="5" style="width: 100%; border-collapse: collapse;"> | |
| <thead> | |
| <tr style="background-color: #add8e6;"> | |
| <th>Port</th> | |
| <th>MAC Limit (0-8191)</th> | |
| <th>Apply</th> | |
| <th>Cancel</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| ${tableRows} | |
| </tbody> | |
| </table> | |
| <div class="buttons" style="margin-top: 10px;"> | |
| <button onclick="showLearnLimit()" id="refreshBtn">Refresh</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| } catch (err) { | |
| console.error('Error fetching learn limit data:', err); | |
| showContent('<p style="color: red;">Failed to load Learn Limit Configuration.</p>'); | |
| } | |
| } | |
| async function applyLearnLimit(port) { | |
| const macLimit = parseInt(document.getElementById(`macLimit_${port}`).value); | |
| if (isNaN(macLimit) || macLimit < 0 || macLimit > 8191) { | |
| alert('MAC Limit must be between 0 and 8191.'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/learn-limit`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ port: parseInt(port), macLimit }) // Ensure port is a number | |
| }); | |
| const result = await response.json(); | |
| alert(result.message || 'Learn limit applied successfully.'); | |
| showLearnLimit(); // Refresh the table | |
| } catch (err) { | |
| console.error('Error applying learn limit:', err); | |
| alert('Failed to apply learn limit.'); | |
| } | |
| } | |
| async function cancelLearnLimit(port) { | |
| try { | |
| const response = await fetch(`${BASE_URL}/learn-limit`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ port: parseInt(port), macLimit: 8191, cancel: true }) // Ensure port is a number | |
| }); | |
| const result = await response.json(); | |
| alert(result.message || 'Learn limit reverted to default (8191).'); | |
| showLearnLimit(); // Refresh the table | |
| } catch (err) { | |
| console.error('Error canceling learn limit:', err); | |
| alert('Failed to revert learn limit.'); | |
| } | |
| } | |
| //SNMP COMMUNITY NAME | |
| async function showCommunityName() { | |
| showContent('<p>Loading SNMP Community Configuration...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/snmp-community`); | |
| const data = await response.json(); | |
| const tableRows = data.map((item, index) => ` | |
| <tr> | |
| <td>${index + 1}</td> | |
| <td>${item.communityName}</td> | |
| <td>${item.accessType}</td> | |
| <td>${item.state}</td> | |
| </tr> | |
| `).join(''); | |
| const dropdownOptions = data.map((item, index) => ` | |
| <option value="${index}">${index + 1}</option> | |
| `).join(''); | |
| showContent(` | |
| <h2 style="color: red;">SNMP Community Configuration</h2> | |
| <table border="1" cellspacing="0" cellpadding="5" style="width: 100%; border-collapse: collapse;"> | |
| <thead> | |
| <tr style="background-color: #add8e6;"> | |
| <th>Item</th> | |
| <th>Community Name</th> | |
| <th>Read/Write</th> | |
| <th>State</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td> | |
| <select id="communitySelect" onchange="loadCommunityData()" style="width: 90%;"> | |
| <option value="new">New</option> | |
| ${dropdownOptions} | |
| </select> | |
| </td> | |
| <td><input type="text" id="communityName" placeholder="Enter community name" style="width: 90%;"></td> | |
| <td> | |
| <select id="accessType" style="width: 90%;"> | |
| <option value="Read/Write">Read/Write</option> | |
| <option value="Read Only">Read Only</option> | |
| </select> | |
| </td> | |
| <td><span id="newStateDisplay"></span></td> | |
| </tr> | |
| ${tableRows} | |
| </tbody> | |
| </table> | |
| <div class="buttons" style="margin-top: 10px;"> | |
| <button onclick="showCommunityName()" id="refreshBtn">Refresh</button> | |
| <button onclick="applyCommunityName()" id="applyBtn">Apply</button> | |
| <button onclick="deleteCommunityName()" id="deleteBtn">Delete</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| } catch (err) { | |
| console.error('Error fetching SNMP community data:', err); | |
| showContent('<p style="color: red;">Failed to load SNMP Community Configuration.</p>'); | |
| } | |
| } | |
| async function loadCommunityData() { | |
| const select = document.getElementById('communitySelect'); | |
| const communityNameInput = document.getElementById('communityName'); | |
| const accessTypeSelect = document.getElementById('accessType'); | |
| const stateDisplay = document.getElementById('newStateDisplay'); | |
| if (select.value === 'new' || select.value === '') { | |
| communityNameInput.value = ''; | |
| accessTypeSelect.value = 'Read/Write'; | |
| stateDisplay.textContent = ''; | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/snmp-community`); | |
| const data = await response.json(); | |
| const item = data[select.value]; | |
| communityNameInput.value = item.communityName; | |
| accessTypeSelect.value = item.accessType; | |
| stateDisplay.textContent = item.state; | |
| } catch (err) { | |
| console.error('Error loading community data:', err); | |
| alert('Failed to load community data.'); | |
| } | |
| } | |
| async function applyCommunityName() { | |
| const select = document.getElementById('communitySelect'); | |
| const communityName = document.getElementById('communityName').value; | |
| const accessType = document.getElementById('accessType').value; | |
| if (!communityName) { | |
| alert('Please enter a community name.'); | |
| return; | |
| } | |
| try { | |
| let response; | |
| if (select.value === 'new') { | |
| response = await fetch(`${BASE_URL}/snmp-community`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ communityName, accessType }) | |
| }); | |
| } else { | |
| response = await fetch(`${BASE_URL}/snmp-community/${select.value}`, { | |
| method: 'PATCH', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ communityName, accessType }) | |
| }); | |
| } | |
| const result = await response.json(); | |
| if (!response.ok) throw new Error(result.message || 'Operation failed'); | |
| alert(result.message || 'SNMP community name updated successfully.'); | |
| showCommunityName(); | |
| } catch (err) { | |
| console.error('Error applying SNMP community name:', err); | |
| alert('Failed to apply SNMP community name.'); | |
| } | |
| } | |
| async function deleteCommunityName() { | |
| const select = document.getElementById('communitySelect'); | |
| if (!select.value || select.value === 'new') { | |
| alert('Please select a community name to delete.'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/snmp-community/${select.value}`, { | |
| method: 'DELETE' | |
| }); | |
| const result = await response.json(); | |
| if (!response.ok) throw new Error(result.message || 'Delete failed'); | |
| alert('Community name deleted successfully.'); | |
| showCommunityName(); | |
| } catch (err) { | |
| console.error('Error deleting SNMP community name:', err); | |
| alert('Failed to delete community name.'); | |
| } | |
| } | |
| // SNMP TRAP TARGET | |
| async function showTrapTargets() { | |
| showContent('<p>Loading Trap Targets Configuration...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/trap-targets`); | |
| const data = await response.json(); | |
| const tableRows = data.map((item, index) => ` | |
| <tr> | |
| <td>${index + 1}</td> | |
| <td>${item.trapIp}</td> | |
| <td>${item.trapCommunity}</td> | |
| <td>${item.trapVersion}</td> | |
| <td>${item.state}</td> | |
| </tr> | |
| `).join(''); | |
| const dropdownOptions = data.map((item, index) => ` | |
| <option value="${index}">${index + 1}</option> | |
| `).join(''); | |
| showContent(` | |
| <h2 style="color: red;">Trap Targets Configuration</h2> | |
| <table border="1" cellspacing="0" cellpadding="5" style="width: 100%; border-collapse: collapse;"> | |
| <thead> | |
| <tr style="background-color: #add8e6;"> | |
| <th>Item</th> | |
| <th>Trap Target IP</th> | |
| <th>Community</th> | |
| <th>Version</th> | |
| <th>State</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td> | |
| <select id="trapSelect" onchange="loadTrapData()" style="width: 90%;"> | |
| <option value="new">New</option> | |
| ${dropdownOptions} | |
| </select> | |
| </td> | |
| <td><input type="text" id="trapIp" placeholder="Enter IP address" style="width: 90%;"></td> | |
| <td><input type="text" id="trapCommunity" placeholder="Enter community" style="width: 90%;"></td> | |
| <td> | |
| <select id="trapVersion" style="width: 90%;"> | |
| <option value="">Select Version</option> | |
| <option value="v1">v1</option> | |
| <option value="v2c">v2c</option> | |
| <option value="v3">v3</option> | |
| </select> | |
| </td> | |
| <td><span id="newStateDisplay"></span></td> | |
| </tr> | |
| ${tableRows} | |
| </tbody> | |
| </table> | |
| <div class="buttons" style="margin-top: 10px;"> | |
| <button onclick="showTrapTargets()" id="refreshBtn">Refresh</button> | |
| <button onclick="applyTrapTarget()" id="applyBtn">Apply</button> | |
| <button onclick="deleteTrapTarget()" id="deleteBtn">Delete</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| } catch (err) { | |
| console.error('Error fetching trap targets data:', err); | |
| showContent('<p style="color: red;">Failed to load Trap Targets Configuration. Error: ' + err.message + '</p>'); | |
| } | |
| } | |
| async function loadTrapData() { | |
| const select = document.getElementById('trapSelect'); | |
| const ipInput = document.getElementById('trapIp'); | |
| const communityInput = document.getElementById('trapCommunity'); | |
| const versionSelect = document.getElementById('trapVersion'); | |
| const stateDisplay = document.getElementById('newStateDisplay'); | |
| if (select.value === 'new' || select.value === '') { | |
| ipInput.value = ''; | |
| communityInput.value = ''; | |
| versionSelect.value = ''; | |
| stateDisplay.textContent = ''; | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/trap-targets`); | |
| const data = await response.json(); | |
| const item = data[select.value]; | |
| ipInput.value = item.trapIp; | |
| communityInput.value = item.trapCommunity; | |
| versionSelect.value = item.trapVersion; | |
| stateDisplay.textContent = item.state; | |
| } catch (err) { | |
| console.error('Error loading trap target data:', err); | |
| alert('Failed to load trap target data.'); | |
| } | |
| } | |
| async function applyTrapTarget() { | |
| const select = document.getElementById('trapSelect'); | |
| const ip = document.getElementById('trapIp').value; | |
| const community = document.getElementById('trapCommunity').value; | |
| const version = document.getElementById('trapVersion').value; | |
| if (!ip || !community || !version) { | |
| alert('Please enter IP address, community, and select a version.'); | |
| return; | |
| } | |
| try { | |
| let response; | |
| if (select.value === 'new') { | |
| response = await fetch(`${BASE_URL}/trap-targets`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ trapIp: ip, trapCommunity: community, trapVersion: version }) | |
| }); | |
| } else { | |
| response = await fetch(`${BASE_URL}/trap-targets/${select.value}`, { | |
| method: 'PATCH', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ trapIp: ip, trapCommunity: community, trapVersion: version }) | |
| }); | |
| } | |
| const result = await response.json(); | |
| if (!response.ok) throw new Error(result.message || 'Operation failed'); | |
| alert(result.message || 'Trap target updated successfully.'); | |
| showTrapTargets(); | |
| } catch (err) { | |
| console.error('Error applying trap target:', err); | |
| alert('Failed to apply trap target. Error: ' + err.message); | |
| } | |
| } | |
| async function deleteTrapTarget() { | |
| const select = document.getElementById('trapSelect'); | |
| if (!select.value || select.value === 'new') { | |
| alert('Please select a trap target to delete.'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/trap-targets/${select.value}`, { | |
| method: 'DELETE' | |
| }); | |
| const result = await response.json(); | |
| if (!response.ok) throw new Error(result.message || 'Delete failed'); | |
| alert('Trap target deleted successfully.'); | |
| showTrapTargets(); | |
| } catch (err) { | |
| console.error('Error deleting trap target:', err); | |
| alert('Failed to delete trap target. Error: ' + err.message); | |
| } | |
| } | |
| //QOS APPLY | |
| let qosData = []; // global data store | |
| async function showQoS() { | |
| showContent('<p>Loading QoS Configuration...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/qos-config?t=${new Date().getTime()}`); | |
| const data = await response.json(); | |
| qosData = data; // store globally | |
| const tableRows = Array.from({ length: 10 }, (_, index) => { | |
| const item = data.find(item => item.port === (index + 1)) || { port: index + 1, qosType: "NO QOS", priority: 0 }; | |
| return ` | |
| <tr> | |
| <td>${item.port}</td> | |
| <td>${item.qosType}</td> | |
| <td>${item.priority}</td> | |
| </tr> | |
| `; | |
| }).join(''); | |
| showContent(` | |
| <h2 style="color: red;">QoS Configuration</h2> | |
| <div style="margin-bottom: 10px;"> | |
| <label for="selectedPort">Select Port: </label> | |
| <select id="selectedPort" style="width: 100px;" onchange="updateFormFields()"> | |
| ${Array.from({ length: 10 }, (_, i) => `<option value="${i + 1}">${i + 1}</option>`).join('')} | |
| </select> | |
| <label for="selectedQoS" style="margin-left: 10px;">QoS Type: </label> | |
| <select id="selectedQoS" style="width: 150px;"> | |
| <option value="NO QOS">NO QOS</option> | |
| <option value="COS based">COS based</option> | |
| <option value="DSCP based">DSCP based</option> | |
| <option value="DSCP/COS based">DSCP/COS based</option> | |
| </select> | |
| <label for="selectedPriority" style="margin-left: 10px;">Priority: </label> | |
| <input type="number" id="selectedPriority" min="0" max="7" value="0" style="width: 70px;"> | |
| <button onclick="applyQoS()" id="applyBtn" style="margin-left: 10px;">Apply</button> | |
| </div> | |
| <table border="1" cellspacing="0" cellpadding="5" style="width: 100%; border-collapse: collapse;"> | |
| <thead> | |
| <tr style="background-color: #add8e6;"> | |
| <th>Port</th> | |
| <th>QoS</th> | |
| <th>Priority</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| ${tableRows} | |
| </tbody> | |
| </table> | |
| <div class="buttons" style="margin-top: 10px;"> | |
| <button onclick="showQoS()" id="refreshBtn">Refresh</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| updateFormFields(); // set form values for initially selected port | |
| } catch (err) { | |
| console.error('Error fetching QoS data:', err); | |
| showContent('<p style="color: red;">Failed to load QoS Configuration. Error: ' + err.message + '</p>'); | |
| } | |
| } | |
| function updateFormFields() { | |
| const selectedPort = parseInt(document.getElementById('selectedPort').value); | |
| const item = qosData.find(entry => entry.port === selectedPort); | |
| document.getElementById('selectedQoS').value = item?.qosType || 'NO QOS'; | |
| document.getElementById('selectedPriority').value = item?.priority || 0; | |
| } | |
| async function applyQoS() { | |
| const port = parseInt(document.getElementById('selectedPort').value); | |
| const qosType = document.getElementById('selectedQoS').value; | |
| const priority = parseInt(document.getElementById('selectedPriority').value); | |
| if (isNaN(priority) || priority < 0 || priority > 7) { | |
| alert('Priority must be between 0 and 7.'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/qos-config`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ port, qosType, priority }) | |
| }); | |
| const result = await response.json(); | |
| if (!response.ok) throw new Error(result.message || 'Failed to apply QoS configuration.'); | |
| alert(result.message || 'QoS configuration applied successfully.'); | |
| await showQoS(); // Refresh UI | |
| setTimeout(updateFormFields, 100); // Ensure form syncs with new data | |
| } catch (err) { | |
| console.error('Error applying QoS configuration:', err); | |
| alert('Failed to apply QoS configuration. Error: ' + err.message); | |
| } | |
| } | |
| //QOS SCHEDULE | |
| async function showQoSSchedule() { | |
| showContent(` | |
| <h2 style="text-align: center; color: #e84a5f;">QoS Schedule</h2> | |
| <div style="margin: 20px;"> | |
| <label for="portSelect"><strong>Port:</strong></label> | |
| <select id="portSelect" onchange="populateQoSInputs()"> | |
| ${[...Array(10)].map((_, i) => `<option value="${i + 1}">${i + 1}</option>`).join('')} | |
| </select> | |
| </div> | |
| <div style="display: flex; flex-wrap: wrap; gap: 20px;"> | |
| <div> | |
| <label><strong>QoS Schedule Mode:</strong></label> | |
| <select id="qosMode"> | |
| <option value="WRR">WRR</option> | |
| <option value="SP">SP</option> | |
| <option value="WFQ">WFQ</option> | |
| </select> | |
| </div> | |
| ${[...Array(8)].map((_, i) => ` | |
| <div id="qos-grid"> | |
| <label>Weight of queue ${i} (1~127):</label><br> | |
| <input type="number" id="weight${i}" min="1" max="127" style="width: 80px;" value="1"> | |
| </div> | |
| `).join('')} | |
| </div> | |
| <div style="margin-top: 20px;"> | |
| <button onclick="showQoSSchedule()" id="refreshBtn">Refresh</button> | |
| <button onclick="applyQoSSchedule()" id="applyBtn">Apply</button> | |
| <button onclick="alert('Help info coming soon')" id="helpBtn">Help</button> | |
| </div> | |
| <br><br> | |
| <table border="1" style="width: 100%; border-collapse: collapse; text-align: center;"> | |
| <thead style="background-color: #d0e4f5;"> | |
| <tr> | |
| <th>Port Name</th> | |
| <th>QoS Schedule Mode</th> | |
| ${[...Array(8)].map((_, i) => `<th>Weight of queue ${i}</th>`).join('')} | |
| </tr> | |
| </thead> | |
| <tbody id="qosScheduleTableBody"></tbody> | |
| </table> | |
| `); | |
| await loadQoSScheduleData(); | |
| } | |
| async function loadQoSScheduleData() { | |
| try { | |
| const res = await fetch(`${BASE_URL}/get_qos_schedule`); | |
| const data = await res.json(); | |
| window.qosScheduleData = data; // Store globally for reuse | |
| updateQoSTable(data); | |
| populateQoSInputs(); // Set form to first port | |
| } catch (err) { | |
| alert("Failed to load QoS Schedule data."); | |
| console.error(err); | |
| } | |
| } | |
| function populateQoSInputs() { | |
| const port = document.getElementById("portSelect").value; | |
| const entry = window.qosScheduleData.find(p => p.port === port); | |
| if (!entry) return; | |
| document.getElementById("qosMode").value = entry.mode || "WRR"; | |
| entry.weights.forEach((weight, i) => { | |
| document.getElementById(`weight${i}`).value = weight || 1; | |
| }); | |
| } | |
| async function applyQoSSchedule() { | |
| const port = document.getElementById("portSelect").value; | |
| const mode = document.getElementById("qosMode").value; | |
| const weights = [...Array(8)].map((_, i) => parseInt(document.getElementById(`weight${i}`).value) || 1); | |
| const body = { port, mode, weights }; | |
| try { | |
| const res = await fetch(`${BASE_URL}/set_qos_schedule`, { | |
| method: 'POST', | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(body) | |
| }); | |
| if (res.ok) { | |
| alert("QoS Schedule applied."); | |
| await loadQoSScheduleData(); // Refresh table with updated data | |
| } else { | |
| const result = await res.json(); | |
| alert(result.message || "Apply failed."); | |
| } | |
| } catch (err) { | |
| alert("Error applying settings."); | |
| console.error(err); | |
| } | |
| } | |
| function updateQoSTable(data) { | |
| const tbody = document.getElementById("qosScheduleTableBody"); | |
| tbody.innerHTML = ""; | |
| data.forEach(entry => { | |
| const row = ` | |
| <tr> | |
| <td>${entry.port}</td> | |
| <td>${entry.mode}</td> | |
| ${entry.weights.map(weight => `<td>${weight}</td>`).join('')} | |
| </tr> | |
| `; | |
| tbody.innerHTML += row; | |
| }); | |
| } | |
| // AAA Configuration Functions | |
| // AAA Configuration Validation Functions | |
| function validateIPAddress(ip) { | |
| // Regular expression for IPv4 validation | |
| const ipRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; | |
| return ipRegex.test(ip); | |
| } | |
| function validatePort(port) { | |
| // Port must be a number between 1-65535 | |
| const portNum = parseInt(port, 10); | |
| return !isNaN(portNum) && portNum >= 1 && portNum <= 65535; | |
| } | |
| function validatePositiveInteger(value) { | |
| const num = parseInt(value, 10); | |
| return !isNaN(num) && num >= 0 && Number.isInteger(num); | |
| } | |
| // AAA Authentication Configuration | |
| function showAAAAuthentication() { | |
| showContent(` | |
| <h2 style="color: red; text-decoration: underline dotted;">AAA Authentication Configuration</h2> | |
| <div style="margin: 20px 0; display: flex; justify-content: center;"> | |
| <div style="display: flex; align-items: center;"> | |
| <div style="background-color: #d0e4f5; padding: 8px 15px; min-width: 200px; font-weight: bold; text-align: left;"> | |
| AAA Authentication Type | |
| </div> | |
| <select id="aaaAuthType" style="margin-left: 20px; padding: 5px; width: 150px;"> | |
| <option value="local" selected>Local</option> | |
| <option value="tacacs">TACACS+</option> | |
| <option value="radius">RADIUS</option> | |
| <option value="none">None</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div style="text-align: center; margin-top: 20px;"> | |
| <button onclick="refreshAAAAuth()" id="refreshBtn">Refresh</button> | |
| <button onclick="applyAAAAuth()" id="applyBtn">Apply</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| refreshAAAAuth(); | |
| } | |
| // TACACS+ Configuration | |
| function showTacacsConfig() { | |
| showContent(` | |
| <h2 style="color: red;">Tacacs+ Configuration</h2> | |
| <div style="margin: 20px 0;"> | |
| <table style="width: 100%; border-collapse: collapse;"> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold; width: 200px;">Server IP</td> | |
| <td style="padding: 8px 15px;"><input type="text" id="tacacsServerIP" value="0.0.0.0" style="width: 200px; padding: 5px;"></td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Option Server IP</td> | |
| <td style="padding: 8px 15px;"><input type="text" id="tacacsOptionServerIP" value="0.0.0.0" style="width: 200px; padding: 5px;"></td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Authentication Type</td> | |
| <td style="padding: 8px 15px;"> | |
| <select id="tacacsAuthType" style="width: 200px; padding: 5px;"> | |
| <option value="pap" selected>PAP</option> | |
| <option value="ascii">ASCII</option> | |
| <option value="chap">CHAP</option> | |
| </select> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Shared Secret</td> | |
| <td style="padding: 8px 15px;"><input type="password" id="tacacsSharedSecret" style="width: 200px; padding: 5px;"></td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Authorization</td> | |
| <td style="padding: 8px 15px;"> | |
| <select id="tacacsAuthorization" style="width: 200px; padding: 5px;"> | |
| <option value="disable" selected>Disable</option> | |
| <option value="enable">Enable</option> | |
| </select> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Accounting</td> | |
| <td style="padding: 8px 15px;"> | |
| <select id="tacacsAccounting" style="width: 200px; padding: 5px;"> | |
| <option value="disable" selected>Disable</option> | |
| <option value="enable">Enable</option> | |
| </select> | |
| </td> | |
| </tr> | |
| </table> | |
| </div> | |
| <div style="text-align: center; margin-top: 20px;"> | |
| <button onclick="refreshTacacs()" id="refreshBtn">Refresh</button> | |
| <button onclick="applyTacacs()" id="applyBtn" >Apply</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| refreshTacacs(); | |
| } | |
| // RADIUS Configuration | |
| function showRadiusConfig() { | |
| showContent(` | |
| <h2 style="color: red;">Radius Configuration</h2> | |
| <div style="margin: 20px 0;"> | |
| <table style="width: 100%; border-collapse: collapse;"> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold; width: 200px;">Primary Server</td> | |
| <td style="padding: 8px 15px;"><input type="text" id="radiusPrimaryServer" value="0.0.0.0" style="width: 200px; padding: 5px;"></td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Option Server</td> | |
| <td style="padding: 8px 15px;"><input type="text" id="radiusOptionServer" value="0.0.0.0" style="width: 200px; padding: 5px;"></td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">UDP Port</td> | |
| <td style="padding: 8px 15px;"><input type="text" id="radiusUDPPort" value="1812" style="width: 200px; padding: 5px;"></td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Accounting</td> | |
| <td style="padding: 8px 15px;"> | |
| <select id="radiusAccounting" style="width: 200px; padding: 5px;"> | |
| <option value="enable" selected>Enable</option> | |
| <option value="disable">Disable</option> | |
| </select> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Accounting UDP Port</td> | |
| <td style="padding: 8px 15px;"><input type="text" id="radiusAccountingUDPPort" value="1813" style="width: 200px; padding: 5px;"></td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Shared Key</td> | |
| <td style="padding: 8px 15px;"><input type="password" id="radiusSharedKey" style="width: 200px; padding: 5px;"></td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Vendor</td> | |
| <td style="padding: 8px 15px;"><input type="text" id="radiusVendor" style="width: 200px; padding: 5px;"></td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">NAS Port</td> | |
| <td style="padding: 8px 15px;"><input type="text" id="radiusNASPort" value="50003" style="width: 200px; padding: 5px;"></td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">NAS Port Type</td> | |
| <td style="padding: 8px 15px;"><input type="text" id="radiusNASPortType" value="15" style="width: 200px; padding: 5px;"></td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">NAS Service Type</td> | |
| <td style="padding: 8px 15px;"><input type="text" id="radiusNASServiceType" value="2" style="width: 200px; padding: 5px;"></td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Roaming</td> | |
| <td style="padding: 8px 15px;"> | |
| <select id="radiusRoaming" style="width: 200px; padding: 5px;"> | |
| <option value="disable" selected>Disable</option> | |
| <option value="enable">Enable</option> | |
| </select> | |
| </td> | |
| </tr> | |
| </table> | |
| </div> | |
| <div style="text-align: center; margin-top: 20px;"> | |
| <button onclick="refreshRadius()" id="refreshBtn">Refresh</button> | |
| <button onclick="applyRadius()" id="applyBtn">Apply</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| refreshRadius(); | |
| } | |
| // 802.1x Configuration | |
| function showDot1xConfig() { | |
| showContent(` | |
| <h2 style="color: red;">802.1x Configuration</h2> | |
| <div style="margin: 20px 0;"> | |
| <table style="width: 100%; border-collapse: collapse;"> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold; width: 200px;">802.1x</td> | |
| <td style="padding: 8px 15px;"> | |
| <select id="dot1xEnable" style="width: 200px; padding: 5px;"> | |
| <option value="disable" selected>Disable</option> | |
| <option value="enable">Enable</option> | |
| </select> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Reauthentication</td> | |
| <td style="padding: 8px 15px;"> | |
| <select id="dot1xReauth" style="width: 200px; padding: 5px;"> | |
| <option value="disable" selected>Disable</option> | |
| <option value="enable">Enable</option> | |
| </select> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Reauthentication Period</td> | |
| <td style="padding: 8px 15px;"><input type="text" id="dot1xReauthPeriod" value="3600" style="width: 200px; padding: 5px;"></td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Quiet Period</td> | |
| <td style="padding: 8px 15px;"><input type="text" id="dot1xQuietPeriod" value="60" style="width: 200px; padding: 5px;"></td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Tx-Period</td> | |
| <td style="padding: 8px 15px;"><input type="text" id="dot1xTxPeriod" value="30" style="width: 200px; padding: 5px;"></td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Server Timeout</td> | |
| <td style="padding: 8px 15px;"><input type="text" id="dot1xServerTimeout" value="10" style="width: 200px; padding: 5px;"></td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Supplicant Timeout</td> | |
| <td style="padding: 8px 15px;"><input type="text" id="dot1xSupplicantTimeout" value="30" style="width: 200px; padding: 5px;"></td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Max Request</td> | |
| <td style="padding: 8px 15px;"><input type="text" id="dot1xMaxRequest" value="3" style="width: 200px; padding: 5px;"></td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Reauth Max</td> | |
| <td style="padding: 8px 15px;"><input type="text" id="dot1xReauthMax" value="3" style="width: 200px; padding: 5px;"></td> | |
| </tr> | |
| </table> | |
| </div> | |
| <div style="text-align: center; margin-top: 20px;"> | |
| <button onclick="refreshDot1x()" id="refreshBtn">Refresh</button> | |
| <button onclick="applyDot1x()" id="applyBtn">Apply</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| refreshDot1x(); | |
| } | |
| // 802.1x Port Configuration | |
| async function showDot1xPortConfig() { | |
| showContent(` | |
| <h2 style="color: red; text-align: center;">802.1x Port Configuration</h2> | |
| <div style="margin: 20px 0;"> | |
| <table style="width: 100%; border-collapse: collapse; text-align: center;"> | |
| <thead style="background-color: #d0e4f5;"> | |
| <tr> | |
| <th> | |
| <select id="portSelect" style="width: 100px; padding: 5px;"> | |
| <option value="">Port Num</option> | |
| ${[...Array(10)].map((_, i) => `<option value="${i + 1}">${i + 1}</option>`).join('')} | |
| </select> | |
| </th> | |
| <th> | |
| <select id="portModeSelect" style="width: 150px; padding: 5px;"> | |
| <option value="">Port Mode</option> | |
| <option value="auto">Auto</option> | |
| <option value="force-authorized">Force Authorized</option> | |
| <option value="force-unauthorized">Force Unauthorized</option> | |
| </select> | |
| </th> | |
| <th> | |
| <input type="text" id="supportHostNum" value="0" style="width: 80px; padding: 5px; text-align: center;"> | |
| </th> | |
| </tr> | |
| </thead> | |
| <tbody id="dot1x-port-table-body"> | |
| <tr><td colspan="3">Loading...</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| <div style="text-align: center; margin-top: 20px;"> | |
| <button onclick="refreshDot1xPort()" id="refreshBtn">Refresh</button> | |
| <button onclick="applyDot1xPort()" id="applyBtn">Apply</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| // Call the function to fetch and populate table | |
| await populateDot1xPortTable(); | |
| refreshDot1xPort(); | |
| } | |
| async function populateDot1xPortTable() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/DOT1X-PORT-CONFIG`); | |
| const data = await response.json(); | |
| const tbody = document.getElementById("dot1x-port-table-body"); | |
| tbody.innerHTML = ""; | |
| data.forEach(entry => { | |
| const row = document.createElement("tr"); | |
| row.innerHTML = ` | |
| <td>${entry.port}</td> | |
| <td>${entry.portMode}</td> | |
| <td>${entry.supportHostNum}</td> | |
| `; | |
| tbody.appendChild(row); | |
| }); | |
| } catch (error) { | |
| console.error("Error fetching 802.1x config:", error); | |
| const tbody = document.getElementById("dot1x-port-table-body"); | |
| tbody.innerHTML = `<tr><td colspan="3">Failed to load data</td></tr>`; | |
| } | |
| } | |
| // 802.1x User Auth-Information | |
| function showDot1xUserAuth() { | |
| showContent(` | |
| <h2 style="color: red;">802.1x User Auth-Information</h2> | |
| <div style="margin: 20px 0;"> | |
| <div style="display: flex; align-items: center; margin-bottom: 10px;"> | |
| <div style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold; min-width: 150px;"> | |
| Port: | |
| </div> | |
| <select id="userAuthPort" style="margin-left: 10px; padding: 5px; width: 150px;" onchange="refreshDot1xUserAuth()"> | |
| ${[...Array(10)].map((_, i) => `<option value="${i + 1}">${i + 1}</option>`).join('')} | |
| </select> | |
| <div style="margin-left: 20px;"> | |
| <span style="font-weight: bold;">Port Mode:</span> | |
| <input type="text" id="userAuthPortMode" style="margin-left: 10px; padding: 5px; width: 150px;" readonly> | |
| </div> | |
| <div style="margin-left: 20px;"> | |
| <span style="font-weight: bold;" id="acceptedHostText">Accepted Host Num: 0</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div style="margin: 20px 0;"> | |
| <table style="width: 100%; border-collapse: collapse; text-align: center;"> | |
| <thead style="background-color: #d0e4f5;"> | |
| <tr> | |
| <th>User name</th> | |
| <th>MAC Address</th> | |
| <th>Request State</th> | |
| <th colspan="2">Applicant State Machine</th> | |
| <th colspan="2">Back-End State Machine</th> | |
| <th>Retry Request State</th> | |
| </tr> | |
| <tr> | |
| <th></th> | |
| <th></th> | |
| <th></th> | |
| <th>State</th> | |
| <th>Retry Request Num</th> | |
| <th>State</th> | |
| <th>Request Num</th> | |
| <th>State</th> | |
| </tr> | |
| </thead> | |
| <tbody id="userAuthTableBody"> | |
| <tr><td colspan="8">Please select a port above to load data.</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| <div style="text-align: center; margin-top: 20px;"> | |
| <button onclick="refreshDot1xUserAuth()">Refresh</button> | |
| <button onclick="showHelp()">Help</button> | |
| </div> | |
| `); | |
| refreshDot1xUserAuth(); | |
| } | |
| async function refreshDot1xUserAuth() { | |
| const port = document.getElementById("userAuthPort").value; | |
| const portModeInput = document.getElementById("userAuthPortMode"); | |
| const acceptedHostText = document.getElementById("acceptedHostText"); | |
| const tableBody = document.getElementById("userAuthTableBody"); | |
| if (!port) { | |
| alert("Please select a port first."); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/dot1x_user_auth?port=${port}`); | |
| if (!response.ok) throw new Error("Failed to fetch data"); | |
| const data = await response.json(); | |
| // Update top section | |
| portModeInput.value = data.port_mode || "N/A"; | |
| acceptedHostText.textContent = `Accepted Host Num: ${data.accepted_host_num ?? 0}`; | |
| // Update table | |
| if (Array.isArray(data.users) && data.users.length > 0) { | |
| const rows = data.users.map(user => ` | |
| <tr> | |
| <td>${user.username}</td> | |
| <td>${user.mac}</td> | |
| <td>${user.request_state}</td> | |
| <td>${user.applicant_state}</td> | |
| <td>${user.applicant_retry}</td> | |
| <td>${user.backend_state}</td> | |
| <td>${user.backend_request_num}</td> | |
| <td>${user.retry_request_state}</td> | |
| </tr> | |
| `).join(''); | |
| tableBody.innerHTML = rows; | |
| } else { | |
| tableBody.innerHTML = `<tr><td colspan="8">No users found for selected port.</td></tr>`; | |
| } | |
| } catch (err) { | |
| console.error("Error fetching user auth info:", err); | |
| tableBody.innerHTML = `<tr><td colspan="8" style="color:red;">Error loading data</td></tr>`; | |
| } | |
| } | |
| // Optional: update port-specific info when selected | |
| function updatePortInfo(port) { | |
| // Use port value to display or filter info | |
| document.getElementById("userAuthPortMode").value = "Auto"; // Example | |
| } | |
| // AAA Configuration Helper Functions | |
| async function refreshAAAAuth() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/aaa-authentication`); | |
| const data = await response.json(); | |
| document.getElementById('aaaAuthType').value = data.authType || 'local'; | |
| } catch (err) { | |
| console.log('Error refreshing AAA authentication:', err); | |
| } | |
| } | |
| async function applyAAAAuth() { | |
| const authType = document.getElementById('aaaAuthType').value; | |
| try { | |
| const response = await fetch(`${BASE_URL}/aaa-authentication`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ authType }) | |
| }); | |
| if (response.ok) { | |
| alert('AAA Authentication configuration applied successfully.'); | |
| } else { | |
| alert('Failed to apply AAA Authentication configuration.'); | |
| } | |
| } catch (err) { | |
| alert('Error applying AAA Authentication configuration.'); | |
| console.error(err); | |
| } | |
| } | |
| async function refreshTacacs() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/tacacs-config`); | |
| const data = await response.json(); | |
| document.getElementById('tacacsServerIP').value = data.serverIP || '0.0.0.0'; | |
| document.getElementById('tacacsOptionServerIP').value = data.optionServerIP || '0.0.0.0'; | |
| document.getElementById('tacacsAuthType').value = data.authType || 'pap'; | |
| document.getElementById('tacacsAuthorization').value = data.authorization || 'disable'; | |
| document.getElementById('tacacsAccounting').value = data.accounting || 'disable'; | |
| } catch (err) { | |
| console.log('Error refreshing TACACS+ configuration:', err); | |
| } | |
| } | |
| async function applyTacacs() { | |
| const serverIP = document.getElementById('tacacsServerIP').value; | |
| const optionServerIP = document.getElementById('tacacsOptionServerIP').value; | |
| const authType = document.getElementById('tacacsAuthType').value; | |
| const sharedSecret = document.getElementById('tacacsSharedSecret').value; | |
| const authorization = document.getElementById('tacacsAuthorization').value; | |
| const accounting = document.getElementById('tacacsAccounting').value; | |
| // Validate IP addresses | |
| if (!validateIPAddress(serverIP)) { | |
| alert('Invalid Server IP address format. Please use format: xxx.xxx.xxx.xxx'); | |
| return; | |
| } | |
| if (!validateIPAddress(optionServerIP)) { | |
| alert('Invalid Option Server IP address format. Please use format: xxx.xxx.xxx.xxx'); | |
| return; | |
| } | |
| const config = { | |
| serverIP, | |
| optionServerIP, | |
| authType, | |
| sharedSecret, | |
| authorization, | |
| accounting | |
| }; | |
| try { | |
| const response = await fetch(`${BASE_URL}/tacacs-config`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(config) | |
| }); | |
| if (response.ok) { | |
| alert('TACACS+ configuration applied successfully.'); | |
| } else { | |
| alert('Failed to apply TACACS+ configuration.'); | |
| } | |
| } catch (err) { | |
| alert('Error applying TACACS+ configuration.'); | |
| console.error(err); | |
| } | |
| } | |
| async function refreshRadius() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/radius-config`); | |
| const data = await response.json(); | |
| document.getElementById('radiusPrimaryServer').value = data.primaryServer || '0.0.0.0'; | |
| document.getElementById('radiusOptionServer').value = data.optionServer || '0.0.0.0'; | |
| document.getElementById('radiusUDPPort').value = data.udpPort || '1812'; | |
| document.getElementById('radiusAccounting').value = data.accounting || 'enable'; | |
| document.getElementById('radiusAccountingUDPPort').value = data.accountingUDPPort || '1813'; | |
| document.getElementById('radiusNASPort').value = data.nasPort || '50003'; | |
| document.getElementById('radiusNASPortType').value = data.nasPortType || '15'; | |
| document.getElementById('radiusNASServiceType').value = data.nasServiceType || '2'; | |
| document.getElementById('radiusRoaming').value = data.roaming || 'disable'; | |
| } catch (err) { | |
| console.log('Error refreshing RADIUS configuration:', err); | |
| } | |
| } | |
| async function applyRadius() { | |
| const primaryServer = document.getElementById('radiusPrimaryServer').value; | |
| const optionServer = document.getElementById('radiusOptionServer').value; | |
| const udpPort = document.getElementById('radiusUDPPort').value; | |
| const accounting = document.getElementById('radiusAccounting').value; | |
| const accountingUDPPort = document.getElementById('radiusAccountingUDPPort').value; | |
| const sharedKey = document.getElementById('radiusSharedKey').value; | |
| const vendor = document.getElementById('radiusVendor').value; | |
| const nasPort = document.getElementById('radiusNASPort').value; | |
| const nasPortType = document.getElementById('radiusNASPortType').value; | |
| const nasServiceType = document.getElementById('radiusNASServiceType').value; | |
| const roaming = document.getElementById('radiusRoaming').value; | |
| // Validate IP addresses | |
| if (!validateIPAddress(primaryServer)) { | |
| alert('Invalid Primary Server IP address format. Please use format: xxx.xxx.xxx.xxx'); | |
| return; | |
| } | |
| if (!validateIPAddress(optionServer)) { | |
| alert('Invalid Option Server IP address format. Please use format: xxx.xxx.xxx.xxx'); | |
| return; | |
| } | |
| // Validate ports | |
| if (!validatePort(udpPort)) { | |
| alert('Invalid UDP Port. Port must be a number between 1-65535.'); | |
| return; | |
| } | |
| if (!validatePort(accountingUDPPort)) { | |
| alert('Invalid Accounting UDP Port. Port must be a number between 1-65535.'); | |
| return; | |
| } | |
| // Validate NAS port and types | |
| if (!validatePositiveInteger(nasPort)) { | |
| alert('Invalid NAS Port. Must be a positive integer.'); | |
| return; | |
| } | |
| if (!validatePositiveInteger(nasPortType)) { | |
| alert('Invalid NAS Port Type. Must be a positive integer.'); | |
| return; | |
| } | |
| if (!validatePositiveInteger(nasServiceType)) { | |
| alert('Invalid NAS Service Type. Must be a positive integer.'); | |
| return; | |
| } | |
| const config = { | |
| primaryServer, | |
| optionServer, | |
| udpPort, | |
| accounting, | |
| accountingUDPPort, | |
| sharedKey, | |
| vendor, | |
| nasPort, | |
| nasPortType, | |
| nasServiceType, | |
| roaming | |
| }; | |
| try { | |
| const response = await fetch(`${BASE_URL}/radius-config`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(config) | |
| }); | |
| if (response.ok) { | |
| alert('RADIUS configuration applied successfully.'); | |
| } else { | |
| alert('Failed to apply RADIUS configuration.'); | |
| } | |
| } catch (err) { | |
| alert('Error applying RADIUS configuration.'); | |
| console.error(err); | |
| } | |
| } | |
| async function refreshDot1x() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/dot1x-config`); | |
| const data = await response.json(); | |
| document.getElementById('dot1xEnable').value = data.enable || 'disable'; | |
| document.getElementById('dot1xReauth').value = data.reauth || 'disable'; | |
| document.getElementById('dot1xReauthPeriod').value = data.reauthPeriod || '3600'; | |
| document.getElementById('dot1xQuietPeriod').value = data.quietPeriod || '60'; | |
| document.getElementById('dot1xTxPeriod').value = data.txPeriod || '30'; | |
| document.getElementById('dot1xServerTimeout').value = data.serverTimeout || '10'; | |
| document.getElementById('dot1xSupplicantTimeout').value = data.supplicantTimeout || '30'; | |
| document.getElementById('dot1xMaxRequest').value = data.maxRequest || '3'; | |
| document.getElementById('dot1xReauthMax').value = data.reauthMax || '3'; | |
| } catch (err) { | |
| console.log('Error refreshing 802.1x configuration:', err); | |
| } | |
| } | |
| async function applyDot1x() { | |
| const enable = document.getElementById('dot1xEnable').value; | |
| const reauth = document.getElementById('dot1xReauth').value; | |
| const reauthPeriod = document.getElementById('dot1xReauthPeriod').value; | |
| const quietPeriod = document.getElementById('dot1xQuietPeriod').value; | |
| const txPeriod = document.getElementById('dot1xTxPeriod').value; | |
| const serverTimeout = document.getElementById('dot1xServerTimeout').value; | |
| const supplicantTimeout = document.getElementById('dot1xSupplicantTimeout').value; | |
| const maxRequest = document.getElementById('dot1xMaxRequest').value; | |
| const reauthMax = document.getElementById('dot1xReauthMax').value; | |
| // Validate time periods and counts | |
| if (!validatePositiveInteger(reauthPeriod)) { | |
| alert('Invalid Reauthentication Period. Must be a positive integer.'); | |
| return; | |
| } | |
| if (!validatePositiveInteger(quietPeriod)) { | |
| alert('Invalid Quiet Period. Must be a positive integer.'); | |
| return; | |
| } | |
| if (!validatePositiveInteger(txPeriod)) { | |
| alert('Invalid Tx Period. Must be a positive integer.'); | |
| return; | |
| } | |
| if (!validatePositiveInteger(serverTimeout)) { | |
| alert('Invalid Server Timeout. Must be a positive integer.'); | |
| return; | |
| } | |
| if (!validatePositiveInteger(supplicantTimeout)) { | |
| alert('Invalid Supplicant Timeout. Must be a positive integer.'); | |
| return; | |
| } | |
| if (!validatePositiveInteger(maxRequest)) { | |
| alert('Invalid Max Request. Must be a positive integer.'); | |
| return; | |
| } | |
| if (!validatePositiveInteger(reauthMax)) { | |
| alert('Invalid Reauth Max. Must be a positive integer.'); | |
| return; | |
| } | |
| const config = { | |
| enable, | |
| reauth, | |
| reauthPeriod, | |
| quietPeriod, | |
| txPeriod, | |
| serverTimeout, | |
| supplicantTimeout, | |
| maxRequest, | |
| reauthMax | |
| }; | |
| try { | |
| const response = await fetch(`${BASE_URL}/dot1x-config`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(config) | |
| }); | |
| if (response.ok) { | |
| alert('802.1x configuration applied successfully.'); | |
| } else { | |
| alert('Failed to apply 802.1x configuration.'); | |
| } | |
| } catch (err) { | |
| alert('Error applying 802.1x configuration.'); | |
| console.error(err); | |
| } | |
| } | |
| async function refreshDot1xPort() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/dot1x-port-config`); | |
| const data = await response.json(); | |
| // Update port configuration data | |
| console.log('802.1x port configuration refreshed'); | |
| } catch (err) { | |
| console.log('Error refreshing 802.1x port configuration:', err); | |
| } | |
| } | |
| async function applyDot1xPort() { | |
| const port = document.getElementById('portSelect').value; | |
| const portMode = document.getElementById('portModeSelect').value; | |
| const supportHostNum = document.getElementById('supportHostNum').value; | |
| if (!port) { | |
| alert('Please select a port.'); | |
| return; | |
| } | |
| if (!portMode) { | |
| alert('Please select a port mode.'); | |
| return; | |
| } | |
| if (!validatePositiveInteger(supportHostNum)) { | |
| alert('Invalid Support Host Number. Must be a positive integer.'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/dot1x-port-config`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ port, portMode, supportHostNum }) | |
| }); | |
| if (response.ok) { | |
| alert('802.1x port configuration applied successfully.'); | |
| } else { | |
| alert('Failed to apply 802.1x port configuration.'); | |
| } | |
| } catch (err) { | |
| alert('Error applying 802.1x port configuration.'); | |
| console.error(err); | |
| } | |
| showDot1xPortConfig(); | |
| } | |
| async function refreshDot1xUserAuth() { | |
| try { | |
| const port = document.getElementById('userAuthPort').value; | |
| if (!port) { | |
| alert('Please select a port first.'); | |
| return; | |
| } | |
| const response = await fetch(`${BASE_URL}/dot1x-user-auth?port=${port}`); | |
| const data = await response.json(); | |
| document.getElementById('userAuthPortMode').value = data.port_mode || ''; | |
| document.getElementById('acceptedHostText').textContent = `Accepted Host Num: ${data.accepted_host_num || 0}`; | |
| // Update the table with user authentication data | |
| const tbody = document.getElementById('userAuthTableBody'); | |
| tbody.innerHTML = ''; | |
| if (data.users && data.users.length > 0) { | |
| data.users.forEach(user => { | |
| const row = ` | |
| <tr> | |
| <td>${user.username || ''}</td> | |
| <td>${user.mac || ''}</td> | |
| <td>${user.request_state || ''}</td> | |
| <td>${user.applicant_state || ''}</td> | |
| <td>${user.applicant_retry || ''}</td> | |
| <td>${user.backend_state || ''}</td> | |
| <td>${user.backend_request_num || ''}</td> | |
| <td>${user.retry_request_state || ''}</td> | |
| </tr> | |
| `; | |
| tbody.innerHTML += row; | |
| }); | |
| } | |
| } catch (err) { | |
| console.log('Error refreshing 802.1x user auth information:', err); | |
| } | |
| } | |
| // IGMP SNOOPING Configuration Functions | |
| // IGMP SNOOPING Configuration | |
| function showIgmpSnoopingConfig() { | |
| showContent(` | |
| <h2>IGMP SNOOPING Configuration</h2> | |
| <div style="margin: 20px 0;"> | |
| <table style="width: 100%; border-collapse: collapse;"> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold; width: 200px;">Global IGMP SNOOPING</td> | |
| <td style="padding: 8px 15px;"> | |
| <select id="globalIgmpSnooping" style="width: 200px; padding: 5px;"> | |
| <option value="disable" selected>Disable</option> | |
| <option value="enable">Enable</option> | |
| </select> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">VLAN ID</td> | |
| <td style="padding: 8px 15px;"> | |
| <select id="vlanId" style="width: 200px; padding: 5px;"> | |
| <option value="vlan1" selected>vlan1</option> | |
| <option value="vlan2">vlan2</option> | |
| <option value="vlan3">vlan3</option> | |
| <option value="vlan4">vlan4</option> | |
| <option value="vlan5">vlan5</option> | |
| </select> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">VLAN IGMP SNOOPING</td> | |
| <td style="padding: 8px 15px;"> | |
| <select id="vlanIgmpSnooping" style="width: 200px; padding: 5px;"> | |
| <option value="disable" selected>Disable</option> | |
| <option value="enable">Enable</option> | |
| </select> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Fast Leave</td> | |
| <td style="padding: 8px 15px;"> | |
| <select id="fastLeave" style="width: 200px; padding: 5px;"> | |
| <option value="disable" selected>Disable</option> | |
| <option value="enable">Enable</option> | |
| </select> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Fast Leave Timeout</td> | |
| <td style="padding: 8px 15px;"> | |
| <input type="text" id="fastLeaveTimeout" value="300000" style="width: 200px; padding: 5px;"> | |
| <span style="margin-left: 5px;">(ms)</span> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Query Membership Timeout</td> | |
| <td style="padding: 8px 15px;"> | |
| <input type="text" id="queryMembershipTimeout" value="300000" style="width: 200px; padding: 5px;"> | |
| <span style="margin-left: 5px;">(ms)</span> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Group Membership Timeout</td> | |
| <td style="padding: 8px 15px;"> | |
| <input type="text" id="groupMembershipTimeout" value="400000" style="width: 200px; padding: 5px;"> | |
| <span style="margin-left: 5px;">(ms)</span> | |
| </td> | |
| </tr> | |
| </table> | |
| </div> | |
| <div style="text-align: center; margin-top: 20px;"> | |
| <button onclick="refreshIgmpSnooping()" id="refreshBtn" >Refresh</button> | |
| <button onclick="applyIgmpSnooping()" id="applyBtn">Apply</button> | |
| <button onclick="showHelp()" id = "helpBtn" >Help</button> | |
| </div> | |
| `); | |
| } | |
| // Multicast Group Information | |
| function showMulticastGroupInfo() { | |
| showContent(` | |
| <h2>Multicast Group Information</h2> | |
| <div style="margin: 20px 0;"> | |
| <table style="width: 100%; border-collapse: collapse;"> | |
| <thead style="background-color: #d0e4f5;"> | |
| <tr> | |
| <th style="padding: 8px 15px; text-align: left;">VLAN ID</th> | |
| <th style="padding: 8px 15px; text-align: center;">Multicast Address</th> | |
| <th style="padding: 8px 15px; text-align: center;">Member Ports</th> | |
| </tr> | |
| </thead> | |
| <tbody id="multicastGroupTableBody"> | |
| <!-- Empty table body - no data currently --> | |
| </tbody> | |
| </table> | |
| </div> | |
| <div style="text-align: center; margin-top: 20px;"> | |
| <button onclick="refreshMulticastGroupInfo()" id="refreshBtn">Refresh</button> | |
| <button onclick="showHelp()" id = "helpBtn" >Help</button> | |
| </div> | |
| `); | |
| // Load multicast group data when page loads | |
| refreshMulticastGroupInfo(); | |
| } | |
| // IGMP Configuration Helper Functions | |
| async function refreshIgmpSnooping() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/igmp-snooping-config`); | |
| const data = await response.json(); | |
| document.getElementById('globalIgmpSnooping').value = data.globalIgmpSnooping || 'disable'; | |
| document.getElementById('vlanId').value = data.vlanId || 'vlan1'; | |
| document.getElementById('vlanIgmpSnooping').value = data.vlanIgmpSnooping || 'disable'; | |
| document.getElementById('fastLeave').value = data.fastLeave || 'disable'; | |
| document.getElementById('fastLeaveTimeout').value = data.fastLeaveTimeout || '300000'; | |
| document.getElementById('queryMembershipTimeout').value = data.queryMembershipTimeout || '300000'; | |
| document.getElementById('groupMembershipTimeout').value = data.groupMembershipTimeout || '400000'; | |
| } catch (err) { | |
| console.log('Error refreshing IGMP SNOOPING configuration:', err); | |
| } | |
| } | |
| async function applyIgmpSnooping() { | |
| const config = { | |
| globalIgmpSnooping: document.getElementById('globalIgmpSnooping').value, | |
| vlanId: document.getElementById('vlanId').value, | |
| vlanIgmpSnooping: document.getElementById('vlanIgmpSnooping').value, | |
| fastLeave: document.getElementById('fastLeave').value, | |
| fastLeaveTimeout: document.getElementById('fastLeaveTimeout').value, | |
| queryMembershipTimeout: document.getElementById('queryMembershipTimeout').value, | |
| groupMembershipTimeout: document.getElementById('groupMembershipTimeout').value | |
| }; | |
| try { | |
| const response = await fetch(`${BASE_URL}/igmp-snooping-config`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(config) | |
| }); | |
| if (response.ok) { | |
| alert('IGMP SNOOPING configuration applied successfully.'); | |
| } else { | |
| alert('Failed to apply IGMP SNOOPING configuration.'); | |
| } | |
| } catch (err) { | |
| alert('Error applying IGMP SNOOPING configuration.'); | |
| console.error(err); | |
| } | |
| } | |
| async function refreshMulticastGroupInfo() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/multicast-group-info`); | |
| const data = await response.json(); | |
| const tbody = document.getElementById('multicastGroupTableBody'); | |
| tbody.innerHTML = ''; | |
| if (data.groups && data.groups.length > 0) { | |
| data.groups.forEach(group => { | |
| const row = ` | |
| <tr> | |
| <td style="padding: 8px 15px;">${group.vlanId || ''}</td> | |
| <td style="padding: 8px 15px; text-align: center;">${group.multicastAddress || ''}</td> | |
| <td style="padding: 8px 15px; text-align: center;">${group.memberPorts || ''}</td> | |
| </tr> | |
| `; | |
| tbody.innerHTML += row; | |
| }); | |
| } else { | |
| tbody.innerHTML = '<tr><td colspan="3" style="text-align: center; padding: 20px; color: #666;">No multicast group information available</td></tr>'; | |
| } | |
| } catch (err) { | |
| console.log('Error refreshing multicast group information:', err); | |
| const tbody = document.getElementById('multicastGroupTableBody'); | |
| tbody.innerHTML = '<tr><td colspan="3" style="text-align: center; padding: 20px; color: #666;">Error loading multicast group information</td></tr>'; | |
| } | |
| } | |
| // GMRP Configuration Functions | |
| // GMRP Global Configuration | |
| function showGmrpGlobalConfig() { | |
| showContent(` | |
| <h2 style="color: red; text-align: center; text-decoration: underline dotted;">GMRP Global Configuration</h2> | |
| <div style="margin: 20px 0; display: flex; justify-content: center;"> | |
| <div style="display: flex; align-items: center;"> | |
| <div style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold; min-width: 150px; text-align: left;"> | |
| Global GMRP | |
| </div> | |
| <select id="globalGmrp" style="margin-left: 20px; padding: 5px; width: 150px;"> | |
| <option value="disable" selected>Disable</option> | |
| <option value="enable">Enable</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div style="text-align: center; margin-top: 20px;"> | |
| <button onclick="refreshGmrpGlobal()" id="refreshBtn">Refresh</button> | |
| <button onclick="applyGmrpGlobal()" id="applyBtn">Apply</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| refreshGmrpGlobal(); | |
| } | |
| // GMRP Ports Configuration | |
| function showGmrpPortsConfig() { | |
| showContent(` | |
| <h2 style="color: red;">GMRP Ports Configuration</h2> | |
| <div style="margin: 20px 0;"> | |
| <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;"> | |
| <div style="display: flex; align-items: center;"> | |
| <div style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold; min-width: 100px;"> | |
| Port: | |
| </div> | |
| <select id="gmrpPortSelect" style="margin-left: 10px; padding: 5px; width: 120px;"> | |
| <option value="">Select Port</option> | |
| <option value="1">1</option> | |
| <option value="2">2</option> | |
| <option value="3">3</option> | |
| <option value="4">4</option> | |
| <option value="5">5</option> | |
| <option value="6">6</option> | |
| <option value="7">7</option> | |
| <option value="8">8</option> | |
| <option value="9">9</option> | |
| <option value="10">10</option> | |
| </select> | |
| </div> | |
| <div style="display: flex; align-items: center;"> | |
| <div style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold; min-width: 120px;"> | |
| GMRP Status: | |
| </div> | |
| <select id="gmrpStatus" style="margin-left: 10px; padding: 5px; width: 120px;"> | |
| <option value="disable" selected>Disable</option> | |
| <option value="enable">Enable</option> | |
| </select> | |
| </div> | |
| <div> | |
| <button onclick="refreshGmrpPorts()" id="refreshBtn">Refresh</button> | |
| <button onclick="applyGmrpPorts()" id="applyBtn">Apply</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| </div> | |
| <table style="width: 100%; border-collapse: collapse;"> | |
| <thead style="background-color: #d0e4f5;"> | |
| <tr> | |
| <th style="padding: 8px 15px; text-align: left;">Port Name</th> | |
| <th style="padding: 8px 15px; text-align: center;">GMRP Status</th> | |
| <th style="padding: 8px 15px; text-align: center;">Join Timer(centiseconds)</th> | |
| <th style="padding: 8px 15px; text-align: center;">Leave Timer(centiseconds)</th> | |
| <th style="padding: 8px 15px; text-align: center;">LeaveAll Timer(centiseconds)</th> | |
| </tr> | |
| </thead> | |
| <tbody id="gmrpPortsTableBody"> | |
| <tr><td> 1</td><td>Disable</td><td>---</td><td>---</td><td>---</td></tr> | |
| <tr><td> 2</td><td>Disable</td><td>---</td><td>---</td><td>---</td></tr> | |
| <tr><td> 3</td><td>Disable</td><td>---</td><td>---</td><td>---</td></tr> | |
| <tr><td> 4</td><td>Disable</td><td>---</td><td>---</td><td>---</td></tr> | |
| <tr><td> 5</td><td>Disable</td><td>---</td><td>---</td><td>---</td></tr> | |
| <tr><td> 6</td><td>Disable</td><td>---</td><td>---</td><td>---</td></tr> | |
| <tr><td> 7</td><td>Disable</td><td>---</td><td>---</td><td>---</td></tr> | |
| <tr><td> 8</td><td>Disable</td><td>---</td><td>---</td><td>---</td></tr> | |
| <tr><td> 9</td><td>Disable</td><td>---</td><td>---</td><td>---</td></tr> | |
| <tr><td> 10</td><td>Disable</td><td>---</td><td>---</td><td>---</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| `); | |
| refreshGmrpPorts(); | |
| } | |
| // GMRP State Machine | |
| function showGmrpStateMachine() { | |
| showContent(` | |
| <h2 style="color: red;">GMRP State Machine</h2> | |
| <div style="margin: 20px 0;"> | |
| <table style="width: 100%; border-collapse: collapse;"> | |
| <thead style="background-color: #d0e4f5;"> | |
| <tr> | |
| <th style="padding: 8px 15px; text-align: left;">Port Name</th> | |
| <th style="padding: 8px 15px; text-align: center;">VLAN ID</th> | |
| <th style="padding: 8px 15px; text-align: center;">Multicast MAC Address</th> | |
| <th style="padding: 8px 15px; text-align: center;">Applicant State</th> | |
| <th style="padding: 8px 15px; text-align: center;">Registrar State</th> | |
| </tr> | |
| </thead> | |
| <tbody id="gmrpStateMachineTableBody"> | |
| <!-- Empty table body - no data currently --> | |
| </tbody> | |
| </table> | |
| </div> | |
| <div style="text-align: center; margin-top: 20px;"> | |
| <button onclick="refreshGmrpStateMachine()" >Refresh</button> | |
| <button onclick="showHelp()" >Help</button> | |
| </div> | |
| `); | |
| // Load GMRP state machine data when page loads | |
| refreshGmrpStateMachine(); | |
| } | |
| // GMRP Configuration Helper Functions | |
| async function refreshGmrpGlobal() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/gmrp-global-config`); | |
| const data = await response.json(); | |
| document.getElementById('globalGmrp').value = data.globalGmrp || 'disable'; | |
| } catch (err) { | |
| console.log('Error refreshing GMRP global configuration:', err); | |
| } | |
| } | |
| async function applyGmrpGlobal() { | |
| const globalGmrp = document.getElementById('globalGmrp').value; | |
| try { | |
| const response = await fetch(`${BASE_URL}/gmrp-global-config`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ globalGmrp }) | |
| }); | |
| if (response.ok) { | |
| alert('GMRP global configuration applied successfully.'); | |
| } else { | |
| alert('Failed to apply GMRP global configuration.'); | |
| } | |
| } catch (err) { | |
| alert('Error applying GMRP global configuration.'); | |
| console.error(err); | |
| } | |
| } | |
| async function refreshGmrpPorts() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/gmrp-ports-config`); | |
| const data = await response.json(); | |
| // Update the table with GMRP ports data | |
| const tbody = document.getElementById('gmrpPortsTableBody'); | |
| tbody.innerHTML = ''; | |
| if (data.ports && data.ports.length > 0) { | |
| data.ports.forEach(port => { | |
| const row = ` | |
| <tr> | |
| <td>${port.portName || ''}</td> | |
| <td>${port.gmrpStatus || 'Disable'}</td> | |
| <td>${port.joinTimer || '---'}</td> | |
| <td>${port.leaveTimer || '---'}</td> | |
| <td>${port.leaveAllTimer || '---'}</td> | |
| </tr> | |
| `; | |
| tbody.innerHTML += row; | |
| }); | |
| } else { | |
| // Default table with all ports disabled | |
| for (let i = 1; i <= 10; i++) { | |
| const row = ` | |
| <tr> | |
| <td> ${i}</td> | |
| <td>Disable</td> | |
| <td>---</td> | |
| <td>---</td> | |
| <td>---</td> | |
| </tr> | |
| `; | |
| tbody.innerHTML += row; | |
| } | |
| } | |
| } catch (err) { | |
| console.log('Error refreshing GMRP ports configuration:', err); | |
| } | |
| } | |
| async function applyGmrpPorts() { | |
| const port = document.getElementById('gmrpPortSelect').value; | |
| const gmrpStatus = document.getElementById('gmrpStatus').value; | |
| if (!port) { | |
| alert('Please select a port.'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/gmrp-ports-config`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ port, gmrpStatus }) | |
| }); | |
| if (response.ok) { | |
| alert('GMRP ports configuration applied successfully.'); | |
| refreshGmrpPorts(); // Refresh the table | |
| } else { | |
| alert('Failed to apply GMRP ports configuration.'); | |
| } | |
| } catch (err) { | |
| alert('Error applying GMRP ports configuration.'); | |
| console.error(err); | |
| } | |
| } | |
| async function refreshGmrpStateMachine() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/gmrp-state-machine`); | |
| const data = await response.json(); | |
| const tbody = document.getElementById('gmrpStateMachineTableBody'); | |
| tbody.innerHTML = ''; | |
| if (data.entries && data.entries.length > 0) { | |
| data.entries.forEach(entry => { | |
| const row = ` | |
| <tr> | |
| <td style="padding: 8px 15px;">${entry.portName || ''}</td> | |
| <td style="padding: 8px 15px; text-align: center;">${entry.vlanId || ''}</td> | |
| <td style="padding: 8px 15px; text-align: center;">${entry.multicastMacAddress || ''}</td> | |
| <td style="padding: 8px 15px; text-align: center;">${entry.applicantState || ''}</td> | |
| <td style="padding: 8px 15px; text-align: center;">${entry.registrarState || ''}</td> | |
| </tr> | |
| `; | |
| tbody.innerHTML += row; | |
| }); | |
| } else { | |
| tbody.innerHTML = '<tr><td colspan="5" style="text-align: center; padding: 20px; color: #666;">No GMRP state machine data available</td></tr>'; | |
| } | |
| } catch (err) { | |
| console.log('Error refreshing GMRP state machine:', err); | |
| const tbody = document.getElementById('gmrpStateMachineTableBody'); | |
| tbody.innerHTML = '<tr><td colspan="5" style="text-align: center; padding: 20px; color: #666;">Error loading GMRP state machine data</td></tr>'; | |
| } | |
| } | |
| // GVRP Configuration Functions | |
| // GVRP Global Configuration | |
| function showGvrpGlobalConfig() { | |
| showContent(` | |
| <h2 style="color: red; text-align: center; text-decoration: underline dotted;">GVRP Global Configuration</h2> | |
| <div style="margin: 20px 0; display: flex; justify-content: center;"> | |
| <div style="display: flex; align-items: center;"> | |
| <div style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold; min-width: 150px; text-align: left;"> | |
| Global GVRP | |
| </div> | |
| <select id="globalGvrp" style="margin-left: 20px; padding: 5px; width: 150px;"> | |
| <option value="disable" selected>Disable</option> | |
| <option value="enable">Enable</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div style="text-align: center; margin-top: 20px;"> | |
| <button onclick="refreshGvrpGlobal()" id="refreshBtn">Refresh</button> | |
| <button onclick="applyGvrpGlobal()" id="applyBtn">Apply</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| refreshGvrpGlobal(); // Load data on page load | |
| } | |
| // GVRP Ports Configuration | |
| function showGvrpPortsConfig() { | |
| showContent(` | |
| <h2 style="color: red;">GVRP Ports Configuration</h2> | |
| <div style="margin: 20px 0;"> | |
| <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;"> | |
| <div style="display: flex; align-items: center;"> | |
| <div style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold; min-width: 100px;"> | |
| Port: | |
| </div> | |
| <select id="gvrpPortSelect" style="margin-left: 10px; padding: 5px; width: 120px;"> | |
| <option value="">Select Port</option> | |
| <option value="1"> 1</option> | |
| <option value="2"> 2</option> | |
| <option value="3"> 3</option> | |
| <option value="4"> 4</option> | |
| <option value="5"> 5</option> | |
| <option value="6"> 6</option> | |
| <option value="7"> 7</option> | |
| <option value="8"> 8</option> | |
| <option value="9"> 9</option> | |
| <option value="10"> 10</option> | |
| </select> | |
| </div> | |
| <div style="display: flex; align-items: center;"> | |
| <div style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold; min-width: 120px;"> | |
| GVRP Status: | |
| </div> | |
| <select id="gvrpStatus" style="margin-left: 10px; padding: 5px; width: 120px;"> | |
| <option value="disable" selected>Disable</option> | |
| <option value="enable">Enable</option> | |
| </select> | |
| </div> | |
| <div> | |
| <button onclick="refreshGvrpPorts()" id="refreshBtn">Refresh</button> | |
| <button onclick="applyGvrpPorts()" id="applyBtn">Apply</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| </div> | |
| <table style="width: 100%; border-collapse: collapse;"> | |
| <thead style="background-color: #d0e4f5;"> | |
| <tr> | |
| <th style="padding: 8px 15px; text-align: left;">Port Name</th> | |
| <th style="padding: 8px 15px; text-align: center;">GVRP Status</th> | |
| <th style="padding: 8px 15px; text-align: center;">Join Timer(centiseconds)</th> | |
| <th style="padding: 8px 15px; text-align: center;">Leave Timer(centiseconds)</th> | |
| <th style="padding: 8px 15px; text-align: center;">LeaveAll Timer(centiseconds)</th> | |
| </tr> | |
| </thead> | |
| <tbody id="gvrpPortsTableBody"> | |
| <tr><td> 1</td><td>Disable</td><td>---</td><td>---</td><td>---</td></tr> | |
| <tr><td> 2</td><td>Disable</td><td>---</td><td>---</td><td>---</td></tr> | |
| <tr><td> 3</td><td>Disable</td><td>---</td><td>---</td><td>---</td></tr> | |
| <tr><td> 4</td><td>Disable</td><td>---</td><td>---</td><td>---</td></tr> | |
| <tr><td> 5</td><td>Disable</td><td>---</td><td>---</td><td>---</td></tr> | |
| <tr><td> 6</td><td>Disable</td><td>---</td><td>---</td><td>---</td></tr> | |
| <tr><td> 7</td><td>Disable</td><td>---</td><td>---</td><td>---</td></tr> | |
| <tr><td> 8</td><td>Disable</td><td>---</td><td>---</td><td>---</td></tr> | |
| <tr><td> 9</td><td>Disable</td><td>---</td><td>---</td><td>---</td></tr> | |
| <tr><td> 10</td><td>Disable</td><td>---</td><td>---</td><td>---</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| `); | |
| refreshGvrpPorts(); // Load data on page load | |
| } | |
| // GVRP State Machine | |
| function showGvrpStateMachine() { | |
| showContent(` | |
| <h2 style="color: red;">GVRP State Machine</h2> | |
| <div style="margin: 20px 0;"> | |
| <table style="width: 100%; border-collapse: collapse;"> | |
| <thead style="background-color: #d0e4f5;"> | |
| <tr> | |
| <th style="padding: 8px 15px; text-align: left;">Port Name</th> | |
| <th style="padding: 8px 15px; text-align: center;">VLAN ID</th> | |
| <th style="padding: 8px 15px; text-align: center;">Multicast MAC Address</th> | |
| <th style="padding: 8px 15px; text-align: center;">Applicant State</th> | |
| <th style="padding: 8px 15px; text-align: center;">Registrar State</th> | |
| </tr> | |
| </thead> | |
| <tbody id="gvrpStateMachineTableBody"> | |
| <!-- Empty table body - no data currently --> | |
| </tbody> | |
| </table> | |
| </div> | |
| <div style="text-align: center; margin-top: 20px;"> | |
| <button onclick="refreshGvrpStateMachine()" >Refresh</button> | |
| <button onclick="showHelp()" >Help</button> | |
| </div> | |
| `); | |
| // Load GVRP state machine data when page loads | |
| refreshGvrpStateMachine(); | |
| } | |
| // GVRP Configuration Helper Functions | |
| async function refreshGvrpGlobal() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/gvrp-global-config`); | |
| const data = await response.json(); | |
| document.getElementById('globalGvrp').value = data.globalGvrp || 'disable'; | |
| } catch (err) { | |
| console.log('Error refreshing GVRP global configuration:', err); | |
| } | |
| } | |
| async function applyGvrpGlobal() { | |
| const globalGvrp = document.getElementById('globalGvrp').value; | |
| try { | |
| const response = await fetch(`${BASE_URL}/gvrp-global-config`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ globalGvrp }) | |
| }); | |
| if (response.ok) { | |
| alert('GVRP global configuration applied successfully.'); | |
| } else { | |
| alert('Failed to apply GVRP global configuration.'); | |
| } | |
| } catch (err) { | |
| alert('Error applying GVRP global configuration.'); | |
| console.error(err); | |
| } | |
| } | |
| async function refreshGvrpPorts() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/gvrp-ports-config`); | |
| const data = await response.json(); | |
| // Update the table with GVRP ports data | |
| const tbody = document.getElementById('gvrpPortsTableBody'); | |
| tbody.innerHTML = ''; | |
| if (data.ports && data.ports.length > 0) { | |
| data.ports.forEach(port => { | |
| const row = ` | |
| <tr> | |
| <td>${port.portName || ''}</td> | |
| <td>${port.gvrpStatus || 'Disable'}</td> | |
| <td>${port.joinTimer || '---'}</td> | |
| <td>${port.leaveTimer || '---'}</td> | |
| <td>${port.leaveAllTimer || '---'}</td> | |
| </tr> | |
| `; | |
| tbody.innerHTML += row; | |
| }); | |
| } else { | |
| // Default table with all ports disabled | |
| for (let i = 1; i <= 10; i++) { | |
| const row = ` | |
| <tr> | |
| <td> ${i}</td> | |
| <td>Disable</td> | |
| <td>---</td> | |
| <td>---</td> | |
| <td>---</td> | |
| </tr> | |
| `; | |
| tbody.innerHTML += row; | |
| } | |
| } | |
| } catch (err) { | |
| console.log('Error refreshing GVRP ports configuration:', err); | |
| } | |
| } | |
| async function applyGvrpPorts() { | |
| const port = document.getElementById('gvrpPortSelect').value; | |
| const gvrpStatus = document.getElementById('gvrpStatus').value; | |
| if (!port) { | |
| alert('Please select a port.'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/gvrp-ports-config`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ port, gvrpStatus }) | |
| }); | |
| if (response.ok) { | |
| alert('GVRP ports configuration applied successfully.'); | |
| refreshGvrpPorts(); // Refresh the table | |
| } else { | |
| alert('Failed to apply GVRP ports configuration.'); | |
| } | |
| } catch (err) { | |
| alert('Error applying GVRP ports configuration.'); | |
| console.error(err); | |
| } | |
| } | |
| async function refreshGvrpStateMachine() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/gvrp-state-machine`); | |
| const data = await response.json(); | |
| const tbody = document.getElementById('gvrpStateMachineTableBody'); | |
| tbody.innerHTML = ''; | |
| if (data.entries && data.entries.length > 0) { | |
| data.entries.forEach(entry => { | |
| const row = ` | |
| <tr> | |
| <td style="padding: 8px 15px;">${entry.portName || ''}</td> | |
| <td style="padding: 8px 15px; text-align: center;">${entry.vlanId || ''}</td> | |
| <td style="padding: 8px 15px; text-align: center;">${entry.multicastMacAddress || ''}</td> | |
| <td style="padding: 8px 15px; text-align: center;">${entry.applicantState || ''}</td> | |
| <td style="padding: 8px 15px; text-align: center;">${entry.registrarState || ''}</td> | |
| </tr> | |
| `; | |
| tbody.innerHTML += row; | |
| }); | |
| } else { | |
| tbody.innerHTML = '<tr><td colspan="5" style="text-align: center; padding: 20px; color: #666;">No GVRP state machine data available</td></tr>'; | |
| } | |
| } catch (err) { | |
| console.log('Error refreshing GVRP state machine:', err); | |
| const tbody = document.getElementById('gvrpStateMachineTableBody'); | |
| tbody.innerHTML = '<tr><td colspan="5" style="text-align: center; padding: 20px; color: #666;">Error loading GVRP state machine data</td></tr>'; | |
| } | |
| } | |
| //EAPS COnfiguration | |
| function showEapsConfig() { | |
| let ringIdOptions = ''; | |
| for (let i = 1; i <= 20; i++) { | |
| ringIdOptions += `<option value="${i}">${i}</option>`; | |
| } | |
| showContent(` | |
| <h2 style="color: red;">EAPS Configuration</h2> | |
| <div style="margin: 20px 0;"> | |
| <table style="width: 100%; border-collapse: collapse;"> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold; width: 200px;">EAPS Ring ID</td> | |
| <td style="padding: 8px 15px;"> | |
| <select id="eapsRingId" style="width: 100px; padding: 5px;" onchange="loadEapsRingConfig()"> | |
| <option value="">Select Ring ID</option> | |
| ${ringIdOptions} | |
| </select> | |
| <input type="text" id="eapsRingIdInput" style="width: 100px; padding: 5px; margin-left: 10px;"> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Create Status</td> | |
| <td style="padding: 8px 15px;"> | |
| <input type="text" id="createStatus" value="Not Created" readonly style="width: 200px; padding: 5px; background-color: #f0f0f0;"> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Mode</td> | |
| <td style="padding: 8px 15px;"> | |
| <select id="eapsMode" style="width: 200px; padding: 5px;"> | |
| <option value="None">None</option> | |
| <option value="Master">Master</option> | |
| <option value="Transit">Transit</option> | |
| </select> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Primary Port</td> | |
| <td style="padding: 8px 15px;"> | |
| <select id="primaryPort" style="width: 200px; padding: 5px;"> | |
| <option value="">Select Port</option> | |
| <option value="1">1</option> | |
| <option value="2">2</option> | |
| <option value="3">3</option> | |
| <option value="4">4</option> | |
| <option value="5">5</option> | |
| <option value="6">6</option> | |
| <option value="7">7</option> | |
| <option value="8">8</option> | |
| <option value="9">9</option> | |
| <option value="10">10</option> | |
| </select> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Secondary Port</td> | |
| <td style="padding: 8px 15px;"> | |
| <select id="secondaryPort" style="width: 200px; padding: 5px;"> | |
| <option value="">Select Port</option> | |
| <option value="1">1</option> | |
| <option value="2">2</option> | |
| <option value="3">3</option> | |
| <option value="4">4</option> | |
| <option value="5">5</option> | |
| <option value="6">6</option> | |
| <option value="7">7</option> | |
| <option value="8">8</option> | |
| <option value="9">9</option> | |
| <option value="10">10</option> | |
| </select> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Control VLAN</td> | |
| <td style="padding: 8px 15px;"> | |
| <input type="number" id="controlVlan" value="0" min="0" max="4094" style="width: 200px; padding: 5px;"> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Protected VLANs</td> | |
| <td style="padding: 8px 15px;"> | |
| <input type="text" id="protectedVlans" placeholder="Format: 2,4,6 or 3-10" style="width: 200px; padding: 5px;"> | |
| <span style="margin-left: 10px; color: #666; font-size: 12px;">Format: 2,4,6 or 3-10</span> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Hello Time Interval</td> | |
| <td style="padding: 8px 15px;"> | |
| <input type="number" id="helloTimeInterval" value="1" min="1" max="10" style="width: 200px; padding: 5px;"> | |
| <span style="margin-left: 10px;">s</span> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Fail Time</td> | |
| <td style="padding: 8px 15px;"> | |
| <input type="number" id="failTime" value="3" min="1" max="10" style="width: 200px; padding: 5px;"> | |
| <span style="margin-left: 10px;">s</span> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Data Span</td> | |
| <td style="padding: 8px 15px;"> | |
| <select id="dataSpan" style="width: 200px; padding: 5px;"> | |
| <option value="Disable">Disable</option> | |
| <option value="Enable">Enable</option> | |
| </select> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Extreme Interoperability</td> | |
| <td style="padding: 8px 15px;"> | |
| <select id="extremeInteroperability" style="width: 200px; padding: 5px;"> | |
| <option value="Disable">Disable</option> | |
| <option value="Enable">Enable</option> | |
| </select> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold;">Enable Status</td> | |
| <td style="padding: 8px 15px;"> | |
| <select id="enableStatus" style="width: 200px; padding: 5px;"> | |
| <option value="Disable">Disable</option> | |
| <option value="Enable">Enable</option> | |
| </select> | |
| </td> | |
| </tr> | |
| </table> | |
| </div> | |
| <div style="text-align: center; margin-top: 20px;"> | |
| <button onclick="refreshEaps()" id="refreshBtn">Refresh</button> | |
| <button onclick="createEaps()" id="applyBtn">Create</button> | |
| <button onclick="applyEaps()" id="applyBtn">Apply</button> | |
| <button onclick="removeEaps()" id="deleteBtn">Remove</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| refreshEaps(); // Load data on page load | |
| } | |
| async function loadEapsRingConfig() { | |
| const eapsRingId = document.getElementById('eapsRingId').value; | |
| if (!eapsRingId) { | |
| document.getElementById('createStatus').value = 'Not Created'; | |
| clearConfigFields(); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/eaps-config?ringId=${eapsRingId}`); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| document.getElementById('createStatus').value = 'Created'; | |
| document.getElementById('eapsRingIdInput').value = data.eapsRingIdInput || ''; | |
| document.getElementById('eapsMode').value = data.eapsMode || 'None'; | |
| document.getElementById('primaryPort').value = data.primaryPort || ''; | |
| document.getElementById('secondaryPort').value = data.secondaryPort || ''; | |
| document.getElementById('controlVlan').value = data.controlVlan || '0'; | |
| document.getElementById('protectedVlans').value = data.protectedVlans || ''; | |
| document.getElementById('helloTimeInterval').value = data.helloTimeInterval || '1'; | |
| document.getElementById('failTime').value = data.failTime || '3'; | |
| document.getElementById('dataSpan').value = data.dataSpan || 'Disable'; | |
| document.getElementById('extremeInteroperability').value = data.extremeInteroperability || 'Enable'; | |
| document.getElementById('enableStatus').value = data.enableStatus || 'Disable'; | |
| } else { | |
| document.getElementById('createStatus').value = 'Not Created'; | |
| clearConfigFields(); | |
| } | |
| } catch (err) { | |
| console.log('Error loading EAPS ring configuration:', err); | |
| document.getElementById('createStatus').value = 'Not Created'; | |
| clearConfigFields(); | |
| } | |
| } | |
| function clearConfigFields() { | |
| document.getElementById('eapsRingIdInput').value = ''; | |
| document.getElementById('eapsMode').value = 'None'; | |
| document.getElementById('primaryPort').value = ''; | |
| document.getElementById('secondaryPort').value = ''; | |
| document.getElementById('controlVlan').value = '0'; | |
| document.getElementById('protectedVlans').value = ''; | |
| document.getElementById('helloTimeInterval').value = '1'; | |
| document.getElementById('failTime').value = '3'; | |
| document.getElementById('dataSpan').value = 'Disable'; | |
| document.getElementById('extremeInteroperability').value = 'Enable'; | |
| document.getElementById('enableStatus').value = 'Disable'; | |
| } | |
| async function refreshEaps() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/eaps-config`); | |
| const data = await response.json(); | |
| const ringIdSelect = document.getElementById('eapsRingId'); | |
| const selectedRingId = ringIdSelect.value; | |
| // Load configuration for selected ring if any | |
| if (selectedRingId) { | |
| await loadEapsRingConfig(); | |
| } else { | |
| document.getElementById('createStatus').value = 'Not Created'; | |
| clearConfigFields(); | |
| } | |
| } catch (err) { | |
| console.log('Error refreshing EAPS configuration:', err); | |
| document.getElementById('createStatus').value = 'Not Created'; | |
| clearConfigFields(); | |
| } | |
| } | |
| async function createEaps() { | |
| const eapsRingId = document.getElementById('eapsRingId').value; | |
| const eapsRingIdInput = document.getElementById('eapsRingIdInput').value; | |
| const eapsMode = document.getElementById('eapsMode').value; | |
| const primaryPort = document.getElementById('primaryPort').value; | |
| const secondaryPort = document.getElementById('secondaryPort').value; | |
| const controlVlan = document.getElementById('controlVlan').value; | |
| const protectedVlans = document.getElementById('protectedVlans').value; | |
| const helloTimeInterval = document.getElementById('helloTimeInterval').value; | |
| const failTime = document.getElementById('failTime').value; | |
| const dataSpan = document.getElementById('dataSpan').value; | |
| const extremeInteroperability = document.getElementById('extremeInteroperability').value; | |
| const enableStatus = document.getElementById('enableStatus').value; | |
| // Validation | |
| if (!eapsRingId) { | |
| alert('EAPS Ring ID is required.'); | |
| return; | |
| } | |
| if (!eapsMode) { | |
| alert('EAPS mode is required.'); | |
| return; | |
| } | |
| if (!['None', 'Master', 'Transit'].includes(eapsMode)) { | |
| alert('EAPS mode must be None, Master, or Transit.'); | |
| return; | |
| } | |
| if (controlVlan && !validatePositiveInteger(controlVlan)) { | |
| alert('Control VLAN must be a positive integer.'); | |
| return; | |
| } | |
| if (helloTimeInterval && !validatePositiveInteger(helloTimeInterval)) { | |
| alert('Hello Time Interval must be a positive integer.'); | |
| return; | |
| } | |
| if (failTime && !validatePositiveInteger(failTime)) { | |
| alert('Fail Time must be a positive integer.'); | |
| return; | |
| } | |
| const config = { | |
| eapsRingId, | |
| eapsRingIdInput, | |
| eapsMode, | |
| primaryPort, | |
| secondaryPort, | |
| controlVlan, | |
| protectedVlans, | |
| helloTimeInterval, | |
| failTime, | |
| dataSpan, | |
| extremeInteroperability, | |
| enableStatus | |
| }; | |
| try { | |
| const response = await fetch(`${BASE_URL}/eaps-config/create`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(config) | |
| }); | |
| if (response.ok) { | |
| alert('EAPS configuration created successfully.'); | |
| document.getElementById('createStatus').value = 'Created'; | |
| refreshEaps(); | |
| } else { | |
| const errorData = await response.json(); | |
| alert(errorData.message || 'Failed to create EAPS configuration.'); | |
| } | |
| } catch (err) { | |
| alert('Error creating EAPS configuration.'); | |
| console.error(err); | |
| } | |
| } | |
| async function applyEaps() { | |
| const eapsRingId = document.getElementById('eapsRingId').value; | |
| const eapsRingIdInput = document.getElementById('eapsRingIdInput').value; | |
| const eapsMode = document.getElementById('eapsMode').value; | |
| const primaryPort = document.getElementById('primaryPort').value; | |
| const secondaryPort = document.getElementById('secondaryPort').value; | |
| const controlVlan = document.getElementById('controlVlan').value; | |
| const protectedVlans = document.getElementById('protectedVlans').value; | |
| const helloTimeInterval = document.getElementById('helloTimeInterval').value; | |
| const failTime = document.getElementById('failTime').value; | |
| const dataSpan = document.getElementById('dataSpan').value; | |
| const extremeInteroperability = document.getElementById('extremeInteroperability').value; | |
| const enableStatus = document.getElementById('enableStatus').value; | |
| // Validation | |
| if (!eapsRingId) { | |
| alert('EAPS Ring ID is required.'); | |
| return; | |
| } | |
| if (!eapsMode) { | |
| alert('EAPS mode is required.'); | |
| return; | |
| } | |
| if (!['None', 'Master', 'Transit'].includes(eapsMode)) { | |
| alert('EAPS mode must be None, Master, or Transit.'); | |
| return; | |
| } | |
| if (controlVlan && !validatePositiveInteger(controlVlan)) { | |
| alert('Control VLAN must be a positive integer.'); | |
| return; | |
| } | |
| if (helloTimeInterval && !validatePositiveInteger(helloTimeInterval)) { | |
| alert('Hello Time Interval must be a positive integer.'); | |
| return; | |
| } | |
| if (failTime && !validatePositiveInteger(failTime)) { | |
| alert('Fail Time must be a positive integer.'); | |
| return; | |
| } | |
| const config = { | |
| eapsRingId, | |
| eapsRingIdInput, | |
| eapsMode, | |
| primaryPort, | |
| secondaryPort, | |
| controlVlan, | |
| protectedVlans, | |
| helloTimeInterval, | |
| failTime, | |
| dataSpan, | |
| extremeInteroperability, | |
| enableStatus | |
| }; | |
| try { | |
| const response = await fetch(`${BASE_URL}/eaps-config`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(config) | |
| }); | |
| if (response.ok) { | |
| alert('EAPS configuration applied successfully.'); | |
| document.getElementById('createStatus').value = 'Created'; | |
| refreshEaps(); | |
| } else { | |
| const errorData = await response.json(); | |
| alert(errorData.message || 'Failed to apply EAPS configuration.'); | |
| } | |
| } catch (err) { | |
| alert('Error applying EAPS configuration.'); | |
| console.error(err); | |
| } | |
| } | |
| async function removeEaps() { | |
| const eapsRingId = document.getElementById('eapsRingId').value; | |
| if (!eapsRingId) { | |
| alert('Please select an EAPS Ring ID to remove.'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/eaps-config/remove`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ eapsRingId }) | |
| }); | |
| if (response.ok) { | |
| alert('EAPS configuration removed successfully.'); | |
| document.getElementById('createStatus').value = 'Not Created'; | |
| clearConfigFields(); | |
| refreshEaps(); | |
| } else { | |
| const errorData = await response.json(); | |
| alert(errorData.message || 'Failed to remove EAPS configuration.'); | |
| } | |
| } catch (err) { | |
| alert('Error removing EAPS configuration.'); | |
| console.error(err); | |
| } | |
| } | |
| function showEapsInfo() { | |
| showContent(` | |
| <h2 style="color: red;">EAPS Information</h2> | |
| <div style="margin: 20px 0;"> | |
| <div id="eapsInfoContent" style="font-family: monospace; white-space: pre-line; line-height: 1.5;"> | |
| <!-- EAPS information will be loaded here --> | |
| </div> | |
| </div> | |
| <div style="text-align: center; margin-top: 20px;"> | |
| <button onclick="refreshEapsInfo()">Refresh</button> | |
| <button onclick="showHelp()">Help</button> | |
| </div> | |
| `); | |
| refreshEapsInfo(); | |
| } | |
| async function refreshEapsInfo() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/eaps-config`); | |
| const data = await response.json(); | |
| let text = ''; | |
| if (Array.isArray(data) && data.length > 0) { | |
| data.forEach(config => { | |
| text += `Ring ID: ${config.eapsRingId}\n`; | |
| text += `Create Status: Created\n`; | |
| text += `Mode: ${config.eapsMode}\n`; | |
| text += `Primary Port: ${config.primaryPort || 'None'}\n`; | |
| text += `Secondary Port: ${config.secondaryPort || 'None'}\n`; | |
| text += `Control VLAN: ${config.controlVlan || '0'}\n`; | |
| text += `Protected VLANs: ${config.protectedVlans || 'None'}\n`; | |
| text += `Hello Time Interval: ${config.helloTimeInterval || '1'}s\n`; | |
| text += `Fail Time: ${config.failTime || '3'}s\n`; | |
| text += `Data Span: ${config.dataSpan || 'Disable'}\n`; | |
| text += `Extreme Interoperability: ${config.extremeInteroperability || 'Enable'}\n`; | |
| text += `Enable Status: ${config.enableStatus || 'Disable'}\n`; | |
| text += '\n'; | |
| }); | |
| } else { | |
| text = 'No EAPS configurations available.'; | |
| } | |
| document.getElementById('eapsInfoContent').textContent = text; | |
| } catch (err) { | |
| document.getElementById('eapsInfoContent').textContent = 'Failed to load EAPS information.'; | |
| console.error('Error loading EAPS info:', err); | |
| } | |
| } | |
| // RMON Configuration Functions | |
| // RMON Statistics | |
| async function showRmonStatistics() { | |
| showContent(` | |
| <h2 style="color: red;">RMON Statistics</h2> | |
| <div style="margin: 20px 0;"> | |
| <div style="display: flex; align-items: center; margin-bottom: 20px;"> | |
| <div style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold; min-width: 100px;"> | |
| Port: | |
| </div> | |
| <select id="rmonPortSelect" onchange="refreshRmonStatistics()" style="margin-left: 10px; padding: 5px; width: 120px;"> | |
| <option value="">Select Port</option> | |
| <option value="1">1</option> | |
| <option value="2">2</option> | |
| <option value="3">3</option> | |
| <option value="4">4</option> | |
| <option value="5">5</option> | |
| <option value="6">6</option> | |
| <option value="7">7</option> | |
| <option value="8">8</option> | |
| <option value="9">9</option> | |
| <option value="10">10</option> | |
| </select> | |
| </div> | |
| <div style="background-color: #d0e4f5; padding: 10px; margin-bottom: 20px;"> | |
| <h3 style="margin: 0 0 15px 0;">RMON Statistics</h3> | |
| <div style="display: flex; align-items: center; margin-bottom: 10px;"> | |
| <div style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold; min-width: 100px;"> | |
| Index: | |
| </div> | |
| <input type="number" id="rmonIndex" value="0" style="margin-left: 10px; padding: 5px; width: 100px;"> | |
| </div> | |
| <div style="display: flex; align-items: center;"> | |
| <div style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold; min-width: 100px;"> | |
| Owner: | |
| </div> | |
| <input type="text" id="rmonOwner" style="margin-left: 10px; padding: 5px; width: 200px;"> | |
| </div> | |
| </div> | |
| <div style="text-align: center; margin-bottom: 20px;"> | |
| <button onclick="refreshRmonStatistics()">Refresh</button> | |
| <button onclick="applyRmonStatistics()">Apply</button> | |
| <button onclick="deleteRmonStatistics()">Delete</button> | |
| <button onclick="showHelp()">Help</button> | |
| </div> | |
| <div style="background-color: #d0e4f5; padding: 10px;"> | |
| <h3 style="margin: 0 0 15px 0;">Statistics Data</h3> | |
| <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;"> | |
| <div> | |
| <div style="display: flex; justify-content: space-between; margin-bottom: 5px;"> | |
| <span>etherStatsDropEvents:</span> | |
| <span id="etherStatsDropEvents">0</span> | |
| </div> | |
| <div style="display: flex; justify-content: space-between; margin-bottom: 5px;"> | |
| <span>etherStatsPkts:</span> | |
| <span id="etherStatsPkts">0</span> | |
| </div> | |
| <div style="display: flex; justify-content: space-between; margin-bottom: 5px;"> | |
| <span>etherStatsMulticastPkts:</span> | |
| <span id="etherStatsMulticastPkts">0</span> | |
| </div> | |
| <div style="display: flex; justify-content: space-between; margin-bottom: 5px;"> | |
| <span>etherStatsUndersizePkts:</span> | |
| <span id="etherStatsUndersizePkts">0</span> | |
| </div> | |
| <div style="display: flex; justify-content: space-between; margin-bottom: 5px;"> | |
| <span>etherStatsFragments:</span> | |
| <span id="etherStatsFragments">0</span> | |
| </div> | |
| <div style="display: flex; justify-content: space-between; margin-bottom: 5px;"> | |
| <span>etherStatsCollisions:</span> | |
| <span id="etherStatsCollisions">0</span> | |
| </div> | |
| <div style="display: flex; justify-content: space-between; margin-bottom: 5px;"> | |
| <span>etherStatsPkts65to127Octets:</span> | |
| <span id="etherStatsPkts65to127Octets">0</span> | |
| </div> | |
| <div style="display: flex; justify-content: space-between; margin-bottom: 5px;"> | |
| <span>etherStatsPkts256to511Octets:</span> | |
| <span id="etherStatsPkts256to511Octets">0</span> | |
| </div> | |
| <div style="display: flex; justify-content: space-between; margin-bottom: 5px;"> | |
| <span>etherStatsPkts1024to1518Octets:</span> | |
| <span id="etherStatsPkts1024to1518Octets">0</span> | |
| </div> | |
| </div> | |
| <div> | |
| <div style="display: flex; justify-content: space-between; margin-bottom: 5px;"> | |
| <span>etherStatsOctets:</span> | |
| <span id="etherStatsOctets">0</span> | |
| </div> | |
| <div style="display: flex; justify-content: space-between; margin-bottom: 5px;"> | |
| <span>etherStatsBroadcastPkts:</span> | |
| <span id="etherStatsBroadcastPkts">0</span> | |
| </div> | |
| <div style="display: flex; justify-content: space-between; margin-bottom: 5px;"> | |
| <span>etherStatsCRCAlignErrors:</span> | |
| <span id="etherStatsCRCAlignErrors">0</span> | |
| </div> | |
| <div style="display: flex; justify-content: space-between; margin-bottom: 5px;"> | |
| <span>etherStatsOversizePkts:</span> | |
| <span id="etherStatsOversizePkts">0</span> | |
| </div> | |
| <div style="display: flex; justify-content: space-between; margin-bottom: 5px;"> | |
| <span>etherStatsJabbers:</span> | |
| <span id="etherStatsJabbers">0</span> | |
| </div> | |
| <div style="display: flex; justify-content: space-between; margin-bottom: 5px;"> | |
| <span>etherStatsPkts64Octets:</span> | |
| <span id="etherStatsPkts64Octets">0</span> | |
| </div> | |
| <div style="display: flex; justify-content: space-between; margin-bottom: 5px;"> | |
| <span>etherStatsPkts128to255Octets:</span> | |
| <span id="etherStatsPkts128to255Octets">0</span> | |
| </div> | |
| <div style="display: flex; justify-content: space-between; margin-bottom: 5px;"> | |
| <span>etherStatsPkts512to1023Octets:</span> | |
| <span id="etherStatsPkts512to1023Octets">0</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| `); | |
| await refreshRmonStatistics(); | |
| } | |
| async function refreshRmonStatistics() { | |
| try { | |
| const port = document.getElementById('rmonPortSelect').value; | |
| if (!port) { | |
| // Reset fields if no port is selected | |
| document.getElementById('rmonIndex').value = '0'; | |
| document.getElementById('rmonOwner').value = ''; | |
| document.getElementById('etherStatsDropEvents').textContent = '0'; | |
| document.getElementById('etherStatsPkts').textContent = '0'; | |
| document.getElementById('etherStatsMulticastPkts').textContent = '0'; | |
| document.getElementById('etherStatsUndersizePkts').textContent = '0'; | |
| document.getElementById('etherStatsFragments').textContent = '0'; | |
| document.getElementById('etherStatsCollisions').textContent = '0'; | |
| document.getElementById('etherStatsPkts65to127Octets').textContent = '0'; | |
| document.getElementById('etherStatsPkts256to511Octets').textContent = '0'; | |
| document.getElementById('etherStatsPkts1024to1518Octets').textContent = '0'; | |
| document.getElementById('etherStatsOctets').textContent = '0'; | |
| document.getElementById('etherStatsBroadcastPkts').textContent = '0'; | |
| document.getElementById('etherStatsCRCAlignErrors').textContent = '0'; | |
| document.getElementById('etherStatsOversizePkts').textContent = '0'; | |
| document.getElementById('etherStatsJabbers').textContent = '0'; | |
| document.getElementById('etherStatsPkts64Octets').textContent = '0'; | |
| document.getElementById('etherStatsPkts128to255Octets').textContent = '0'; | |
| document.getElementById('etherStatsPkts512to1023Octets').textContent = '0'; | |
| return; | |
| } | |
| const response = await fetch(`${BASE_URL}/rmon-statistics?port=${port}`); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| const data = await response.json(); | |
| if (data) { | |
| document.getElementById('rmonIndex').value = data.index || '0'; | |
| document.getElementById('rmonOwner').value = data.owner || ''; | |
| document.getElementById('etherStatsDropEvents').textContent = data.stats?.etherStatsDropEvents || '0'; | |
| document.getElementById('etherStatsPkts').textContent = data.stats?.etherStatsPkts || '0'; | |
| document.getElementById('etherStatsMulticastPkts').textContent = data.stats?.etherStatsMulticastPkts || '0'; | |
| document.getElementById('etherStatsUndersizePkts').textContent = data.stats?.etherStatsUndersizePkts || '0'; | |
| document.getElementById('etherStatsFragments').textContent = data.stats?.etherStatsFragments || '0'; | |
| document.getElementById('etherStatsCollisions').textContent = data.stats?.etherStatsCollisions || '0'; | |
| document.getElementById('etherStatsPkts65to127Octets').textContent = data.stats?.etherStatsPkts65to127Octets || '0'; | |
| document.getElementById('etherStatsPkts256to511Octets').textContent = data.stats?.etherStatsPkts256to511Octets || '0'; | |
| document.getElementById('etherStatsPkts1024to1518Octets').textContent = data.stats?.etherStatsPkts1024to1518Octets || '0'; | |
| document.getElementById('etherStatsOctets').textContent = data.stats?.etherStatsOctets || '0'; | |
| document.getElementById('etherStatsBroadcastPkts').textContent = data.stats?.etherStatsBroadcastPkts || '0'; | |
| document.getElementById('etherStatsCRCAlignErrors').textContent = data.stats?.etherStatsCRCAlignErrors || '0'; | |
| document.getElementById('etherStatsOversizePkts').textContent = data.stats?.etherStatsOversizePkts || '0'; | |
| document.getElementById('etherStatsJabbers').textContent = data.stats?.etherStatsJabbers || '0'; | |
| document.getElementById('etherStatsPkts64Octets').textContent = data.stats?.etherStatsPkts64Octets || '0'; | |
| document.getElementById('etherStatsPkts128to255Octets').textContent = data.stats?.etherStatsPkts128to255Octets || '0'; | |
| document.getElementById('etherStatsPkts512to1023Octets').textContent = data.stats?.etherStatsPkts512to1023Octets || '0'; | |
| } | |
| } catch (err) { | |
| console.error('Error refreshing RMON statistics:', err); | |
| alert('Failed to refresh RMON statistics.'); | |
| } | |
| } | |
| async function applyRmonStatistics() { | |
| const config = { | |
| port: document.getElementById('rmonPortSelect').value, | |
| index: document.getElementById('rmonIndex').value, | |
| owner: document.getElementById('rmonOwner').value | |
| }; | |
| if (!config.port) { | |
| alert('Please select a port.'); | |
| return; | |
| } | |
| if (!config.index || config.index <= 0) { | |
| alert('Please enter a valid index greater than 0.'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/rmon-statistics`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(config) | |
| }); | |
| const result = await response.json(); | |
| if (response.ok) { | |
| alert('RMON statistics configuration applied successfully.'); | |
| document.getElementById('rmonIndex').value = '0'; | |
| document.getElementById('rmonOwner').value = ''; | |
| await refreshRmonStatistics(); | |
| } else { | |
| alert(result.error || 'Failed to apply RMON statistics configuration.'); | |
| } | |
| } catch (err) { | |
| alert('Error applying RMON statistics configuration.'); | |
| console.error(err); | |
| } | |
| } | |
| async function deleteRmonStatistics() { | |
| const port = document.getElementById('rmonPortSelect').value; | |
| if (!port) { | |
| alert('Please select a port.'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/rmon-statistics/delete`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ port }) | |
| }); | |
| if (response.ok) { | |
| alert('RMON statistics configuration deleted successfully.'); | |
| document.getElementById('rmonIndex').value = '0'; | |
| document.getElementById('rmonOwner').value = ''; | |
| await refreshRmonStatistics(); | |
| } else { | |
| alert('Failed to delete RMON statistics configuration.'); | |
| } | |
| } catch (err) { | |
| alert('Error deleting RMON statistics configuration.'); | |
| console.error(err); | |
| } | |
| } | |
| // RMON History | |
| async function showRmonHistory() { | |
| showContent(` | |
| <h2 style="color: red;">RMON History</h2> | |
| <div style="margin: 20px 0;"> | |
| <div style="display: flex; align-items: center; margin-bottom: 20px;"> | |
| <div style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold; min-width: 100px;"> | |
| Port: | |
| </div> | |
| <select id="rmonHistoryPortSelect" onchange="refreshRmonHistory()" style="margin-left: 10px; padding: 5px; width: 120px;"> | |
| <option value="">Select Port</option> | |
| <option value="1">1</option> | |
| <option value="2">2</option> | |
| <option value="3">3</option> | |
| <option value="4">4</option> | |
| <option value="5">5</option> | |
| <option value="6">6</option> | |
| <option value="7">7</option> | |
| <option value="8">8</option> | |
| <option value="9">9</option> | |
| <option value="10">10</option> | |
| </select> | |
| </div> | |
| <div style="background-color: #d0e4f5; padding: 10px; margin-bottom: 20px;"> | |
| <h3 style="margin: 0 0 15px 0;">RMON History</h3> | |
| <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;"> | |
| <div style="display: flex; align-items: center; margin-bottom: 10px;"> | |
| <div style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold; min-width: 100px;"> | |
| Index: | |
| </div> | |
| <input type="number" id="rmonHistoryIndex" value="0" style="margin-left: 10px; padding: 5px; width: 100px;"> | |
| </div> | |
| <div style="display: flex; align-items: center; margin-bottom: 10px;"> | |
| <div style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold; min-width: 100px;"> | |
| Interval: | |
| </div> | |
| <input type="number" id="rmonHistoryInterval" value="0" style="margin-left: 10px; padding: 5px; width: 100px;"> | |
| </div> | |
| <div style="display: flex; align-items: center; margin-bottom: 10px;"> | |
| <div style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold; min-width: 100px;"> | |
| Request Buckets: | |
| </div> | |
| <input type="number" id="rmonHistoryBuckets" value="0" style="margin-left: 10px; padding: 5px; width: 100px;"> | |
| </div> | |
| <div style="display: flex; align-items: center; margin-bottom: 10px;"> | |
| <div style="background-color: #d0e4f5; padding: 8px 15px; font-weight: bold; min-width: 100px;"> | |
| Owner: | |
| </div> | |
| <input type="text" id="rmonHistoryOwner" style="margin-left: 10px; padding: 5px; width: 200px;"> | |
| </div> | |
| </div> | |
| </div> | |
| <div style="text-align: center; margin-bottom: 20px;"> | |
| <button onclick="refreshRmonHistory()">Refresh</button> | |
| <button onclick="applyRmonHistory()">Apply</button> | |
| <button onclick="deleteRmonHistory()">Delete</button> | |
| <button onclick="showHelp()">Help</button> | |
| </div> | |
| <div style="background-color: #d0e4f5; padding: 10px;"> | |
| <h3 style="margin: 0 0 15px 0;">History Data</h3> | |
| <table style="width: 100%; border-collapse: collapse;"> | |
| <thead style="background-color: #d0e4f5;"> | |
| <tr> | |
| <th style="padding: 8px 15px; text-align: center;">Index</th> | |
| <th style="padding: 8px 15px; text-align: center;">Time Interval Start</th> | |
| <th style="padding: 8px 15px; text-align: center;">Drop Events</th> | |
| <th style="padding: 8px 15px; text-align: center;">Octets</th> | |
| <th style="padding: 8px 15px; text-align: center;">Pkts</th> | |
| <th style="padding: 8px 15px; text-align: center;">Broadcast Pkts</th> | |
| <th style="padding: 8px 15px; text-align: center;">Multicast Pkts</th> | |
| <th style="padding: 8px 15px; text-align: center;">CRC Align Errors</th> | |
| <th style="padding: 8px 15px; text-align: center;">Undersize Pkts</th> | |
| <th style="padding: 8px 15px; text-align: center;">Oversize Pkts</th> | |
| <th style="padding: 8px 15px; text-align: center;">Fragments</th> | |
| <th style="padding: 8px 15px; text-align: center;">Jabbers</th> | |
| <th style="padding: 8px 15px; text-align: center;">Collisions</th> | |
| <th style="padding: 8px 15px; text-align: center;">Utilization</th> | |
| </tr> | |
| </thead> | |
| <tbody id="rmonHistoryTableBody"></tbody> | |
| </table> | |
| </div> | |
| <div style="text-align: center; margin-top: 20px;"> | |
| <button onclick="firstPage()">First</button> | |
| <button onclick="prevPage()">Prev</button> | |
| <button onclick="nextPage()">Next</button> | |
| <button onclick="lastPage()">Last</button> | |
| </div> | |
| <div style="text-align: center; margin-top: 10px;"> | |
| <span id="pageInfo">Total: 0 pages, Current Page is No. 1</span> | |
| </div> | |
| </div> | |
| `); | |
| await refreshRmonHistory(); | |
| } | |
| let currentHistoryPage = 1; | |
| const ITEMS_PER_PAGE = 5; | |
| async function refreshRmonHistory() { | |
| try { | |
| const port = document.getElementById('rmonHistoryPortSelect').value; | |
| if (!port) { | |
| // Reset fields if no port is selected | |
| document.getElementById('rmonHistoryIndex').value = '0'; | |
| document.getElementById('rmonHistoryInterval').value = '0'; | |
| document.getElementById('rmonHistoryBuckets').value = '0'; | |
| document.getElementById('rmonHistoryOwner').value = ''; | |
| document.getElementById('rmonHistoryTableBody').innerHTML = ''; | |
| document.getElementById('pageInfo').textContent = 'Total: 0 pages, Current Page is No. 1'; | |
| return; | |
| } | |
| const response = await fetch(`${BASE_URL}/rmon-history?port=${port}&page=${currentHistoryPage}&limit=${ITEMS_PER_PAGE}`); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| const data = await response.json(); | |
| if (data) { | |
| document.getElementById('rmonHistoryIndex').value = data.index || '0'; | |
| document.getElementById('rmonHistoryInterval').value = data.interval || '0'; | |
| document.getElementById('rmonHistoryBuckets').value = data.buckets || '0'; | |
| document.getElementById('rmonHistoryOwner').value = data.owner || ''; | |
| const tbody = document.getElementById('rmonHistoryTableBody'); | |
| tbody.innerHTML = ''; | |
| if (data.historyData && data.historyData.length > 0) { | |
| data.historyData.forEach(item => { | |
| const row = ` | |
| <tr> | |
| <td style="padding: 8px 15px; text-align: center;">${item.index || ''}</td> | |
| <td style="padding: 8px 15px; text-align: center;">${item.timeIntervalStart || ''}</td> | |
| <td style="padding: 8px 15px; text-align: center;">${item.dropEvents || '0'}</td> | |
| <td style="padding: 8px 15px; text-align: center;">${item.octets || '0'}</td> | |
| <td style="padding: 8px 15px; text-align: center;">${item.pkts || '0'}</td> | |
| <td style="padding: 8px 15px; text-align: center;">${item.broadcastPkts || '0'}</td> | |
| <td style="padding: 8px 15px; text-align: center;">${item.multicastPkts || '0'}</td> | |
| <td style="padding: 8px 15px; text-align: center;">${item.crcAlignErrors || '0'}</td> | |
| <td style="padding: 8px 15px; text-align: center;">${item.undersizePkts || '0'}</td> | |
| <td style="padding: 8px 15px; text-align: center;">${item.oversizePkts || '0'}</td> | |
| <td style="padding: 8px 15px; text-align: center;">${item.fragments || '0'}</td> | |
| <td style="padding: 8px 15px; text-align: center;">${item.jabbers || '0'}</td> | |
| <td style="padding: 8px 15px; text-align: center;">${item.collisions || '0'}</td> | |
| <td style="padding: 8px 15px; text-align: center;">${item.utilization || '0'}</td> | |
| </tr> | |
| `; | |
| tbody.innerHTML += row; | |
| }); | |
| } | |
| document.getElementById('pageInfo').textContent = `Total: ${data.totalPages || 0} pages, Current Page is No. ${data.currentPage || 1}`; | |
| } | |
| } catch (err) { | |
| console.error('Error refreshing RMON history:', err); | |
| alert('Failed to refresh RMON history.'); | |
| } | |
| } | |
| async function applyRmonHistory() { | |
| const config = { | |
| port: document.getElementById('rmonHistoryPortSelect').value, | |
| index: document.getElementById('rmonHistoryIndex').value, | |
| interval: document.getElementById('rmonHistoryInterval').value, | |
| buckets: document.getElementById('rmonHistoryBuckets').value, | |
| owner: document.getElementById('rmonHistoryOwner').value | |
| }; | |
| if (!config.port) { | |
| alert('Please select a port.'); | |
| return; | |
| } | |
| if (!config.index || config.index <= 0) { | |
| alert('Please enter a valid index greater than 0.'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/rmon-history`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(config) | |
| }); | |
| const result = await response.json(); | |
| if (response.ok) { | |
| alert('RMON history configuration applied successfully.'); | |
| document.getElementById('rmonHistoryIndex').value = '0'; | |
| document.getElementById('rmonHistoryInterval').value = '0'; | |
| document.getElementById('rmonHistoryBuckets').value = '0'; | |
| document.getElementById('rmonHistoryOwner').value = ''; | |
| await refreshRmonHistory(); | |
| } else { | |
| alert(result.error || 'Failed to apply RMON history configuration.'); | |
| } | |
| } catch (err) { | |
| alert('Error applying RMON history configuration.'); | |
| console.error(err); | |
| } | |
| } | |
| async function deleteRmonHistory() { | |
| const port = document.getElementById('rmonHistoryPortSelect').value; | |
| if (!port) { | |
| alert('Please select a port.'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/rmon-history/delete`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ port }) | |
| }); | |
| if (response.ok) { | |
| alert('RMON history configuration deleted successfully.'); | |
| document.getElementById('rmonHistoryIndex').value = '0'; | |
| document.getElementById('rmonHistoryInterval').value = '0'; | |
| document.getElementById('rmonHistoryBuckets').value = '0'; | |
| document.getElementById('rmonHistoryOwner').value = ''; | |
| await refreshRmonHistory(); | |
| } else { | |
| alert('Failed to delete RMON history configuration.'); | |
| } | |
| } catch (err) { | |
| alert('Error deleting RMON history configuration.'); | |
| console.error(err); | |
| } | |
| } | |
| async function firstPage() { | |
| currentHistoryPage = 1; | |
| await refreshRmonHistory(); | |
| } | |
| async function prevPage() { | |
| if (currentHistoryPage > 1) { | |
| currentHistoryPage--; | |
| await refreshRmonHistory(); | |
| } | |
| } | |
| async function nextPage() { | |
| const port = document.getElementById('rmonHistoryPortSelect').value; | |
| const response = await fetch(`${BASE_URL}/rmon-history?port=${port}&page=${currentHistoryPage}&limit=${ITEMS_PER_PAGE}`); | |
| const data = await response.json(); | |
| if (currentHistoryPage < data.totalPages) { | |
| currentHistoryPage++; | |
| await refreshRmonHistory(); | |
| } | |
| } | |
| async function lastPage() { | |
| const port = document.getElementById('rmonHistoryPortSelect').value; | |
| const response = await fetch(`${BASE_URL}/rmon-history?port=${port}&page=${currentHistoryPage}&limit=${ITEMS_PER_PAGE}`); | |
| const data = await response.json(); | |
| currentHistoryPage = data.totalPages || 1; | |
| await refreshRmonHistory(); | |
| } | |
| // RMON Alarm | |
| async function showRmonAlarm() { | |
| showContent(` | |
| <h2 style="color: red;">RMON Alarm</h2> | |
| <div style="margin: 20px 0;"> | |
| <div style="background-color: #d0e4f5; padding: 10px; margin-bottom: 20px;"> | |
| <div style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; margin-bottom: 10px;"> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold;">Index</div> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold;">Interval</div> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold;">Variable</div> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold;">Sample Type</div> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold;">Alarm Value</div> | |
| </div> | |
| <div style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; margin-bottom: 10px;"> | |
| <input type="number" id="alarmIndex" value="0" style="padding: 5px;"> | |
| <input type="number" id="alarmInterval" value="0" style="padding: 5px;"> | |
| <input type="text" id="alarmVariable" placeholder="" style="padding: 5px;"> | |
| <select id="alarmSampleType" style="padding: 5px;"> | |
| <option value="absolute" selected>absolute</option> | |
| <option value="delta">delta</option> | |
| </select> | |
| <input type="number" id="alarmValue" value="0" style="padding: 5px;"> | |
| </div> | |
| <div style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; margin-bottom: 10px;"> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold;">Rising Threshold</div> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold;">Falling Threshold</div> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold;">Rising Event Index</div> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold;">Falling Event Index</div> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold;">Owner</div> | |
| </div> | |
| <div style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px;"> | |
| <input type="number" id="alarmRisingThreshold" value="0" style="padding: 5px;"> | |
| <input type="number" id="alarmFallingThreshold" value="0" style="padding: 5px;"> | |
| <input type="number" id="alarmRisingEventIndex" value="0" style="padding: 5px;"> | |
| <input type="number" id="alarmFallingEventIndex" value="0" style="padding: 5px;"> | |
| <input type="text" id="alarmOwner" placeholder="" style="padding: 5px;"> | |
| </div> | |
| </div> | |
| <div style="text-align: center; margin-bottom: 20px;"> | |
| <button onclick="refreshRmonAlarm()">Refresh</button> | |
| <button onclick="applyRmonAlarm()">Apply</button> | |
| <button onclick="deleteRmonAlarm()">Delete</button> | |
| <button onclick="showHelp()">Help</button> | |
| </div> | |
| <div style="background-color: #d0e4f5; padding: 10px;"> | |
| <div style="display: grid; grid-template-columns: repeat(10, 1fr); gap: 5px; margin-bottom: 10px;"> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold; font-size: 12px;">Index</div> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold; font-size: 12px;">Interval</div> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold; font-size: 12px;">Variable</div> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold; font-size: 12px;">Sample Type</div> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold; font-size: 12px;">Alarm Value</div> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold; font-size: 12px;">Rising Threshold</div> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold; font-size: 12px;">Falling Threshold</div> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold; font-size: 12px;">Rising Event Index</div> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold; font-size: 12px;">Falling Event Index</div> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold; font-size: 12px;">Owner</div> | |
| </div> | |
| <div id="rmonAlarmTableBody"></div> | |
| </div> | |
| </div> | |
| `); | |
| await refreshRmonAlarm(); | |
| } | |
| async function refreshRmonAlarm() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/rmon-alarm?page=${currentHistoryPage}&limit=${ITEMS_PER_PAGE}`); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| const data = await response.json(); | |
| if (data) { | |
| document.getElementById('alarmIndex').value = '0'; | |
| document.getElementById('alarmInterval').value = '0'; | |
| document.getElementById('alarmVariable').value = ''; | |
| document.getElementById('alarmSampleType').value = 'absolute'; | |
| document.getElementById('alarmValue').value = '0'; | |
| document.getElementById('alarmRisingThreshold').value = '0'; | |
| document.getElementById('alarmFallingThreshold').value = '0'; | |
| document.getElementById('alarmRisingEventIndex').value = '0'; | |
| document.getElementById('alarmFallingEventIndex').value = '0'; | |
| document.getElementById('alarmOwner').value = ''; | |
| const tbody = document.getElementById('rmonAlarmTableBody'); | |
| tbody.innerHTML = ''; | |
| if (data.alarms && data.alarms.length > 0) { | |
| data.alarms.forEach(alarm => { | |
| const row = ` | |
| <div style="display: grid; grid-template-columns: repeat(10, 1fr); gap: 5px; margin-bottom: 5px;"> | |
| <div style="text-align: center; padding: 8px; font-size: 12px;">${alarm.index || ''}</div> | |
| <div style="text-align: center; padding: 8px; font-size: 12px;">${alarm.interval || ''}</div> | |
| <div style="text-align: center; padding: 8px; font-size: 12px;">${alarm.variable || ''}</div> | |
| <div style="text-align: center; padding: 8px; font-size: 12px;">${alarm.sampleType || ''}</div> | |
| <div style="text-align: center; padding: 8px; font-size: 12px;">${alarm.alarmValue || ''}</div> | |
| <div style="text-align: center; padding: 8px; font-size: 12px;">${alarm.risingThreshold || ''}</div> | |
| <div style="text-align: center; padding: 8px; font-size: 12px;">${alarm.fallingThreshold || ''}</div> | |
| <div style="text-align: center; padding: 8px; font-size: 12px;">${alarm.risingEventIndex || ''}</div> | |
| <div style="text-align: center; padding: 8px; font-size: 12px;">${alarm.fallingEventIndex || ''}</div> | |
| <div style="text-align: center; padding: 8px; font-size: 12px;">${alarm.owner || ''}</div> | |
| </div> | |
| `; | |
| tbody.innerHTML += row; | |
| }); | |
| } | |
| } | |
| } catch (err) { | |
| console.error('Error refreshing RMON alarm:', err); | |
| alert('Failed to refresh RMON alarm.'); | |
| } | |
| } | |
| async function applyRmonAlarm() { | |
| const config = { | |
| index: document.getElementById('alarmIndex').value, | |
| interval: document.getElementById('alarmInterval').value, | |
| variable: document.getElementById('alarmVariable').value, | |
| sampleType: document.getElementById('alarmSampleType').value, | |
| alarmValue: document.getElementById('alarmValue').value, | |
| risingThreshold: document.getElementById('alarmRisingThreshold').value, | |
| fallingThreshold: document.getElementById('alarmFallingThreshold').value, | |
| risingEventIndex: document.getElementById('alarmRisingEventIndex').value, | |
| fallingEventIndex: document.getElementById('alarmFallingEventIndex').value, | |
| owner: document.getElementById('alarmOwner').value | |
| }; | |
| if (!config.index || config.index <= 0) { | |
| alert('Please enter a valid index greater than 0.'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/rmon-alarm`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(config) | |
| }); | |
| const result = await response.json(); | |
| if (response.ok) { | |
| alert('RMON alarm configuration applied successfully.'); | |
| document.getElementById('alarmIndex').value = '0'; | |
| document.getElementById('alarmInterval').value = '0'; | |
| document.getElementById('alarmVariable').value = ''; | |
| document.getElementById('alarmSampleType').value = 'absolute'; | |
| document.getElementById('alarmValue').value = '0'; | |
| document.getElementById('alarmRisingThreshold').value = '0'; | |
| document.getElementById('alarmFallingThreshold').value = '0'; | |
| document.getElementById('alarmRisingEventIndex').value = '0'; | |
| document.getElementById('alarmFallingEventIndex').value = '0'; | |
| document.getElementById('alarmOwner').value = ''; | |
| await refreshRmonAlarm(); | |
| } else { | |
| alert(result.error || 'Failed to apply RMON alarm configuration.'); | |
| } | |
| } catch (err) { | |
| alert('Error applying RMON alarm configuration.'); | |
| console.error(err); | |
| } | |
| } | |
| async function deleteRmonAlarm() { | |
| const index = document.getElementById('alarmIndex').value; | |
| if (!index || index <= 0) { | |
| alert('Please enter a valid index greater than 0.'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/rmon-alarm/delete`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ index }) | |
| }); | |
| if (response.ok) { | |
| alert('RMON alarm configuration deleted successfully.'); | |
| document.getElementById('alarmIndex').value = '0'; | |
| document.getElementById('alarmInterval').value = '0'; | |
| document.getElementById('alarmVariable').value = ''; | |
| document.getElementById('alarmSampleType').value = 'absolute'; | |
| document.getElementById('alarmValue').value = '0'; | |
| document.getElementById('alarmRisingThreshold').value = '0'; | |
| document.getElementById('alarmFallingThreshold').value = '0'; | |
| document.getElementById('alarmRisingEventIndex').value = '0'; | |
| document.getElementById('alarmFallingEventIndex').value = '0'; | |
| document.getElementById('alarmOwner').value = ''; | |
| await refreshRmonAlarm(); | |
| } else { | |
| alert('Failed to delete RMON alarm configuration.'); | |
| } | |
| } catch (err) { | |
| alert('Error deleting RMON alarm configuration.'); | |
| console.error(err); | |
| } | |
| } | |
| // RMON Event | |
| async function showRmonEvent() { | |
| showContent(` | |
| <h2 style="color: red;">RMON Event</h2> | |
| <div style="margin: 20px 0;"> | |
| <div style="background-color: #d0e4f5; padding: 15px; margin-bottom: 20px; border-radius: 5px;"> | |
| <h3 style="margin: 0 0 15px 0;">Configure RMON Event</h3> | |
| <div style="display: grid; grid-template-columns: repeat(6, 1fr); gap: 15px; align-items: center;"> | |
| <div style="text-align: center; font-weight: bold;">Index</div> | |
| <div style="text-align: center; font-weight: bold;">Description</div> | |
| <div style="text-align: center; font-weight: bold;">Type</div> | |
| <div style="text-align: center; font-weight: bold;">Community</div> | |
| <div style="text-align: center; font-weight: bold;">Last Time Sent</div> | |
| <div style="text-align: center; font-weight: bold;">Owner</div> | |
| <input type="number" id="eventIndex" value="0" style="padding: 5px; width: 100%;"> | |
| <input type="text" id="eventDescription" placeholder="Enter description" style="padding: 5px; width: 100%;"> | |
| <select id="eventType" style="padding: 5px; width: 100%;"> | |
| <option value="none" selected>none</option> | |
| <option value="log">log</option> | |
| <option value="snmptrap">snmptrap</option> | |
| <option value="mail">mail</option> | |
| </select> | |
| <input type="text" id="eventCommunity" placeholder="Enter community" style="padding: 5px; width: 100%;"> | |
| <input type="text" id="eventLastTimeSent" value="1970/01/01 00:00:00" readonly style="padding: 5px; width: 100%; background-color: #f0f0f0;"> | |
| <input type="text" id="eventOwner" placeholder="Enter owner" style="padding: 5px; width: 100%;"> | |
| </div> | |
| </div> | |
| <div style="text-align: center; margin-bottom: 20px;"> | |
| <button onclick="refreshRmonEvent()">Refresh</button> | |
| <button onclick="applyRmonEvent()">Apply</button> | |
| <button onclick="deleteRmonEvent()">Delete</button> | |
| <button onclick="showHelp()">Help</button> | |
| </div> | |
| <div style="background-color: #d0e4f5; padding: 10px;"> | |
| <div style="display: grid; grid-template-columns: repeat(6, 1fr); gap: 5px; margin-bottom: 10px;"> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold; font-size: 12px;">Index</div> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold; font-size: 12px;">Description</div> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold; font-size: 12px;">Type</div> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold; font-size: 12px;">Community</div> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold; font-size: 12px;">Last Time Sent</div> | |
| <div style="text-align: center; background-color: #d0e4f5; padding: 8px; font-weight: bold; font-size: 12px;">Owner</div> | |
| </div> | |
| <div id="rmonEventTableBody"></div> | |
| </div> | |
| </div> | |
| `); | |
| await refreshRmonEvent(); | |
| } | |
| async function refreshRmonEvent() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/rmon-event?page=${currentHistoryPage}&limit=${ITEMS_PER_PAGE}`); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| const data = await response.json(); | |
| if (data) { | |
| document.getElementById('eventIndex').value = '0'; | |
| document.getElementById('eventDescription').value = ''; | |
| document.getElementById('eventType').value = 'none'; | |
| document.getElementById('eventCommunity').value = ''; | |
| document.getElementById('eventLastTimeSent').value = '1970/01/01 00:00:00'; | |
| document.getElementById('eventOwner').value = ''; | |
| const tbody = document.getElementById('rmonEventTableBody'); | |
| tbody.innerHTML = ''; | |
| if (data.events && data.events.length > 0) { | |
| data.events.forEach(event => { | |
| const row = ` | |
| <div style="display: grid; grid-template-columns: repeat(6, 1fr); gap: 5px; margin-bottom: 5px;"> | |
| <div style="text-align: center; padding: 8px; font-size: 12px;">${event.index || ''}</div> | |
| <div style="text-align: center; padding: 8px; font-size: 12px;">${event.description || ''}</div> | |
| <div style="text-align: center; padding: 8px; font-size: 12px;">${event.type || ''}</div> | |
| <div style="text-align: center; padding: 8px; font-size: 12px;">${event.community || ''}</div> | |
| <div style="text-align: center; padding: 8px; font-size: 12px;">${event.lastTimeSent || ''}</div> | |
| <div style="text-align: center; padding: 8px; font-size: 12px;">${event.owner || ''}</div> | |
| </div> | |
| `; | |
| tbody.innerHTML += row; | |
| }); | |
| } | |
| } | |
| } catch (err) { | |
| console.error('Error refreshing RMON event:', err); | |
| alert('Failed to refresh RMON event.'); | |
| } | |
| } | |
| async function applyRmonEvent() { | |
| const config = { | |
| index: document.getElementById('eventIndex').value, | |
| description: document.getElementById('eventDescription').value, | |
| type: document.getElementById('eventType').value, | |
| community: document.getElementById('eventCommunity').value, | |
| lastTimeSent: document.getElementById('eventLastTimeSent').value, | |
| owner: document.getElementById('eventOwner').value | |
| }; | |
| if (!config.index || config.index <= 0) { | |
| alert('Please enter a valid index greater than 0.'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/rmon-event`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(config) | |
| }); | |
| const result = await response.json(); | |
| if (response.ok) { | |
| alert('RMON event configuration applied successfully.'); | |
| document.getElementById('eventIndex').value = '0'; | |
| document.getElementById('eventDescription').value = ''; | |
| document.getElementById('eventType').value = 'none'; | |
| document.getElementById('eventCommunity').value = ''; | |
| document.getElementById('eventLastTimeSent').value = '1970/01/01 00:00:00'; | |
| document.getElementById('eventOwner').value = ''; | |
| await refreshRmonEvent(); | |
| } else { | |
| alert(result.error || 'Failed to apply RMON event configuration.'); | |
| } | |
| } catch (err) { | |
| alert('Error applying RMON event configuration.'); | |
| console.error(err); | |
| } | |
| } | |
| async function deleteRmonEvent() { | |
| const index = document.getElementById('eventIndex').value; | |
| if (!index || index <= 0) { | |
| alert('Please enter a valid index greater than 0.'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/rmon-event/delete`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ index }) | |
| }); | |
| if (response.ok) { | |
| alert('RMON event configuration deleted successfully.'); | |
| document.getElementById('eventIndex').value = '0'; | |
| document.getElementById('eventDescription').value = ''; | |
| document.getElementById('eventType').value = 'none'; | |
| document.getElementById('eventCommunity').value = ''; | |
| document.getElementById('eventLastTimeSent').value = '1970/01/01 00:00:00'; | |
| document.getElementById('eventOwner').value = ''; | |
| await refreshRmonEvent(); | |
| } else { | |
| alert('Failed to delete RMON event configuration.'); | |
| } | |
| } catch (err) { | |
| alert('Error deleting RMON event configuration.'); | |
| console.error(err); | |
| } | |
| } | |
| func | |
| // ERPS Configuration Page | |
| function showErpsConfig() { | |
| showContent(` | |
| <style> | |
| .erps-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| font-family: sans-serif; | |
| } | |
| .erps-table td { | |
| padding: 4px 6px; | |
| vertical-align: middle; | |
| } | |
| .erps-table td.label { | |
| background-color: #d0e4f5; | |
| font-weight: bold; | |
| width: 180px; | |
| white-space: nowrap; | |
| text-align: right; | |
| padding-right: 10px; | |
| } | |
| .erps-table td.input-cell { | |
| text-align: left; | |
| } | |
| .erps-table input, | |
| .erps-table select { | |
| width: 240px; | |
| padding: 3px 5px; | |
| margin-bottom: 2px; | |
| } | |
| .erps-table button { | |
| padding: 4px 8px; | |
| margin-left: 6px; | |
| margin-top: 2px; | |
| } | |
| .erps-center-buttons { | |
| text-align: center; | |
| margin-top: 18px; | |
| } | |
| .erps-center-buttons button { | |
| margin: 0 8px; | |
| padding: 6px 16px; | |
| } | |
| </style> | |
| <h2 style="color: red;">ERPS Configuration</h2> | |
| <div style="margin: 0 0 20px;"> | |
| <table class="erps-table"> | |
| <tr><td class="label">ERPS Domain</td><td class="input-cell"><select id="erpsDomain"><option value="1">1</option></select></td></tr> | |
| <tr><td class="label">ERPS Domain Status</td><td class="input-cell"><input id="erpsDomainStatus" value="Not Created" readonly></td></tr> | |
| <tr> | |
| <td></td> | |
| <td class="input-cell"> | |
| <button onclick="createErpsDomain()">Create ERPS Domain</button> | |
| <button onclick="deleteErpsDomain()">Delete ERPS Domain</button> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td class="label">ERPS Domain Node Role</td> | |
| <td class="input-cell"> | |
| <select id="erpsDomainNodeRole"> | |
| <option value="none-interconnection">none-interconnection</option> | |
| <option value="interconnection">interconnection</option> | |
| </select> | |
| <button onclick="applyErpsDomainNodeRole()" id = "applyBtn">Apply</button> | |
| </td> | |
| </tr> | |
| <tr><td class="label">ERPS Ring</td><td class="input-cell"><select id="erpsRing"><option value="1">1</option></select></td></tr> | |
| <tr><td class="label">ERPS Ring Status</td><td class="input-cell"><input id="erpsRingStatus" value="Not Created" readonly></td></tr> | |
| <tr> | |
| <td></td> | |
| <td class="input-cell"> | |
| <button onclick="createErpsRing()">Create ERPS Ring</button> | |
| <button onclick="deleteErpsRing()">Delete ERPS Ring</button> | |
| </td> | |
| </tr> | |
| </table> | |
| <table class="erps-table" style="margin-top: 10px;"> | |
| <tr><td class="label">Ring Mode</td><td class="input-cell"><input id="ringMode"></td></tr> | |
| <tr><td class="label">Node Mode</td><td class="input-cell"><input id="nodeMode"></td></tr> | |
| <tr> | |
| <td class="label">Raps VLAN</td> | |
| <td class="input-cell"><input id="rapsVlan" value="0"><button>Delete Raps VLAN</button></td> | |
| </tr> | |
| <tr><td class="label">Traffic VLAN</td><td class="input-cell"><input id="trafficVlan"></td></tr> | |
| <tr> | |
| <td class="label">RPL Port</td> | |
| <td class="input-cell"><input id="rplPort"><button>Delete RPL Port</button></td> | |
| </tr> | |
| <tr> | |
| <td class="label">RL Port</td> | |
| <td class="input-cell"><input id="rlPort"><button>Delete RL Port</button></td> | |
| </tr> | |
| <tr> | |
| <td class="label">Revertive Behaviour</td> | |
| <td class="input-cell"> | |
| <select id="revertiveBehaviour"> | |
| <option value="revertive">revertive</option> | |
| <option value="non-revertive">non-revertive</option> | |
| </select> | |
| </td> | |
| </tr> | |
| <tr><td class="label">Hold-off Time</td><td class="input-cell"><input id="holdOffTime" value="0"> ms</td></tr> | |
| <tr><td class="label">Guard Time</td><td class="input-cell"><input id="guardTime" value="0"> ms</td></tr> | |
| <tr><td class="label">WTR Time</td><td class="input-cell"><input id="wtrTime" value="0"> min</td></tr> | |
| <tr><td class="label">WTB Time</td><td class="input-cell"><input id="wtbTime" value="0"> sec</td></tr> | |
| <tr><td class="label">Raps-send Time</td><td class="input-cell"><input id="rapsSendTime" value="0"> sec</td></tr> | |
| <tr> | |
| <td class="label">ERPS Ring Enable</td> | |
| <td class="input-cell"> | |
| <select id="erpsRingEnable"> | |
| <option value="disable">disable</option> | |
| <option value="enable">enable</option> | |
| </select> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td class="label">Forced Switch RPL/RL</td> | |
| <td class="input-cell"> | |
| <input id="forcedSwitchRpl"> | |
| <input id="forcedSwitchRl"> | |
| <button>Clear Forced</button> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td class="label">Manual Switch Port</td> | |
| <td class="input-cell"> | |
| <input id="manualSwitchPort"> | |
| <button>Clear Manual</button> | |
| </td> | |
| </tr> | |
| </table> | |
| <div class="erps-center-buttons"> | |
| <button>Manual Recover</button> | |
| <button onclick="refreshErpsConfig()" id="refreshBtn">Refresh</button> | |
| <button onclick="applyErpsConfig()" id="applyBtn">Apply</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| </div> | |
| `); | |
| refreshErpsConfig(); | |
| } | |
| // ERPS Information Page | |
| function showErpsInfo() { | |
| showContent(` | |
| <h2 style="color: red;">ERPS Information</h2> | |
| <div style="margin: 20px 0;"> | |
| <pre id="erpsInfoText" style="font-family: monospace; white-space: pre-line;">Loading...</pre> | |
| <div style="text-align: center; margin-top: 20px;"> | |
| <button onclick="refreshErpsInfo()">Refresh</button> | |
| <button onclick="showHelp()">Help</button> | |
| </div> | |
| </div> | |
| `); | |
| refreshErpsInfo(); | |
| } | |
| // ERPS Helper Functions | |
| async function refreshErpsConfig() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/erps-config`); | |
| const data = await response.json(); | |
| if (!data) return; | |
| document.getElementById('erpsDomain').value = data.domain || '1'; | |
| document.getElementById('erpsDomainStatus').value = data.domainStatus || 'Not Created'; | |
| document.getElementById('erpsDomainNodeRole').value = data.domainNodeRole || 'none-interconnection'; | |
| document.getElementById('erpsRing').value = data.ring || '1'; | |
| document.getElementById('erpsRingStatus').value = data.ringStatus || 'Not Created'; | |
| document.getElementById('ringMode').value = data.ringMode || ''; | |
| document.getElementById('nodeMode').value = data.nodeMode || ''; | |
| document.getElementById('rapsVlan').value = data.rapsVlan || '0'; | |
| document.getElementById('trafficVlan').value = data.trafficVlan || ''; | |
| document.getElementById('rplPort').value = data.rplPort || ''; | |
| document.getElementById('rlPort').value = data.rlPort || ''; | |
| document.getElementById('revertiveBehaviour').value = data.revertiveBehaviour || 'revertive'; | |
| document.getElementById('holdOffTime').value = data.holdOffTime || '0'; | |
| document.getElementById('guardTime').value = data.guardTime || '0'; | |
| document.getElementById('wtrTime').value = data.wtrTime || '0'; | |
| document.getElementById('wtbTime').value = data.wtbTime || '0'; | |
| document.getElementById('rapsSendTime').value = data.rapsSendTime || '0'; | |
| document.getElementById('erpsRingEnable').value = data.ringEnable || 'disable'; | |
| document.getElementById('forcedSwitchRpl').value = data.forcedSwitchRpl || ''; | |
| document.getElementById('forcedSwitchRl').value = data.forcedSwitchRl || ''; | |
| document.getElementById('manualSwitchPort').value = data.manualSwitchPort || ''; | |
| } catch (err) { | |
| console.log('Error refreshing ERPS config:', err); | |
| } | |
| } | |
| async function applyErpsConfig() { | |
| const config = { | |
| domain: document.getElementById('erpsDomain').value, | |
| domainStatus: document.getElementById('erpsDomainStatus').value, | |
| domainNodeRole: document.getElementById('erpsDomainNodeRole').value, | |
| ring: document.getElementById('erpsRing').value, | |
| ringStatus: document.getElementById('erpsRingStatus').value, | |
| ringMode: document.getElementById('ringMode').value, | |
| nodeMode: document.getElementById('nodeMode').value, | |
| rapsVlan: document.getElementById('rapsVlan').value, | |
| trafficVlan: document.getElementById('trafficVlan').value, | |
| rplPort: document.getElementById('rplPort').value, | |
| rlPort: document.getElementById('rlPort').value, | |
| revertiveBehaviour: document.getElementById('revertiveBehaviour').value, | |
| holdOffTime: document.getElementById('holdOffTime').value, | |
| guardTime: document.getElementById('guardTime').value, | |
| wtrTime: document.getElementById('wtrTime').value, | |
| wtbTime: document.getElementById('wtbTime').value, | |
| rapsSendTime: document.getElementById('rapsSendTime').value, | |
| ringEnable: document.getElementById('erpsRingEnable').value, | |
| forcedSwitchRpl: document.getElementById('forcedSwitchRpl').value, | |
| forcedSwitchRl: document.getElementById('forcedSwitchRl').value, | |
| manualSwitchPort: document.getElementById('manualSwitchPort').value | |
| }; | |
| try { | |
| const response = await fetch(`${BASE_URL}/erps-config`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(config) | |
| }); | |
| const result = await response.json(); | |
| alert(result.message || 'ERPS configuration applied successfully.'); | |
| refreshErpsConfig(); | |
| } catch (err) { | |
| alert('Failed to apply ERPS configuration.'); | |
| console.error(err); | |
| } | |
| } | |
| async function createErpsDomain() { | |
| document.getElementById('erpsDomainStatus').value = 'Created'; | |
| await applyErpsConfig(); | |
| } | |
| async function deleteErpsDomain() { | |
| document.getElementById('erpsDomainStatus').value = 'Not Created'; | |
| await applyErpsConfig(); | |
| } | |
| async function applyErpsDomainNodeRole() { | |
| await applyErpsConfig(); | |
| } | |
| async function createErpsRing() { | |
| document.getElementById('erpsRingStatus').value = 'Created'; | |
| await applyErpsConfig(); | |
| } | |
| async function deleteErpsRing() { | |
| document.getElementById('erpsRingStatus').value = 'Not Created'; | |
| await applyErpsConfig(); | |
| } | |
| async function refreshErpsInfo() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/erps-info`); | |
| const data = await response.json(); | |
| let text = ''; | |
| if (data.domains && Array.isArray(data.domains)) { | |
| data.domains.forEach(domain => { | |
| text += `domain ${domain.domain} information:\n`; | |
| text += `instance: ${domain.instance}\n`; | |
| text += `node role: ${domain.nodeRole}\n`; | |
| (domain.rings || []).forEach(ring => { | |
| text += `% ring ${ring.ring} information:\n`; | |
| text += `% ring mode: ${ring.ringMode}\n`; | |
| text += `% rpl port: ${ring.rplPort}\n`; | |
| text += `% rl port: ${ring.rlPort}\n`; | |
| text += `% revertive behaviour: ${ring.revertiveBehaviour}\n`; | |
| text += `% hold off time: ${ring.holdOffTime}\n`; | |
| text += `% guard time: ${ring.guardTime}\n`; | |
| text += `% wtr time: ${ring.wtrTime}\n`; | |
| text += `% wtb time: ${ring.wtbTime}\n`; | |
| text += `% raps send time: ${ring.rapsSendTime}\n`; | |
| text += `% raps vlan: ${ring.rapsVlan}\n`; | |
| text += `% traffic vlan list: ${ring.trafficVlanList}\n`; | |
| text += `% enable state: ${ring.enableState}\n`; | |
| text += `% node state: ${ring.nodeState}\n`; | |
| text += `% port list: ${ring.portList}\n`; | |
| }); | |
| text += '\n'; | |
| }); | |
| } else { | |
| text = 'No ERPS domain information available.'; | |
| } | |
| document.getElementById('erpsInfoText').textContent = text; | |
| } catch (err) { | |
| document.getElementById('erpsInfoText').textContent = 'Failed to load ERPS information.'; | |
| } | |
| } | |
| // LLDP Configuration | |
| async function lldpGlobal() { | |
| showContent(` | |
| <h2>LLDP Global Configuration</h2> | |
| <hr/> | |
| <div style="width: 400px; margin: 30px auto; font-family: Arial, sans-serif;"> | |
| <div style="display: flex; flex-direction: column;"> | |
| <div style="display: flex; align-items: center;"> | |
| <span style="display: inline-block;font-size:12px; border: 1px solid black; width: 230px; color: #333; background-color: #cce0ff; padding: 2px 4px; margin-right: 10px;"> | |
| LLDP Global | |
| </span> | |
| <select id="lldpGlobal" style="width: 87px;margin-left:0px; padding: 2px; border: 1px solid #7f9db9; font-size: 12px;"> | |
| <option value="0">Enable</option> | |
| <option value="1">Disable</option> | |
| </select> | |
| </div> | |
| <div style="display: flex; align-items: center;"> | |
| <span style="display: inline-block; font-size:12px; border: 1px solid black; width: 230px; color: #333; background-color: #cce0ff; padding: 2px 4px; margin-right: 10px;"> | |
| Hold-multiplier <1-10> | |
| </span> | |
| <input id="holdMultiplier" name="holdMultiplier" type="text" value="0" style="width: 130px; padding: 2px; border: 1px solid #7f9db9; font-size: 12px;"> | |
| </div> | |
| <div style="display: flex; align-items: center;"> | |
| <span style="display: inline-block; font-size:12px;border: 1px solid black; width: 230px; color: #333; background-color: #cce0ff; padding: 2px 4px; margin-right: 10px;"> | |
| Reinit-delay <1-10s> | |
| </span> | |
| <input id="reinitDelay" name="reinitDelay" type="text" value="0" style="width: 130px; padding: 2px; border: 1px solid #7f9db9; font-size: 12px;"> | |
| </div> | |
| <div style="display: flex; align-items: center;"> | |
| <span style="display: inline-block; font-size:12px; border: 1px solid black; width: 230px; color: #333; background-color: #cce0ff; padding: 2px 4px; margin-right: 10px;"> | |
| Tx-delay <1-10s> | |
| </span> | |
| <input id="txDelay" name="txDelay" type="text" value="0" style="width: 130px; padding: 2px; border: 1px solid #7f9db9; font-size: 12px;"> | |
| </div> | |
| <div style="display: flex; align-items: center;"> | |
| <span style="display: inline-block; font-size:12px; border: 1px solid black; width: 230px; color: #333; background-color: #cce0ff; padding: 2px 4px; margin-right: 10px;"> | |
| Tx-interval <5-300s> | |
| </span> | |
| <input id="txInterval" name="txInterval" type="text" value="0" style="width: 130px; padding: 2px; border: 1px solid #7f9db9; font-size: 12px;"> | |
| </div> | |
| </div> | |
| </div> | |
| <div> | |
| <button onclick="lldpGlobal()" id="refreshBtn" >Refresh</button> | |
| <button onclick="addLLDPGlobal()" id = "applyBtn">Apply</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| getLLDPGlobal() | |
| } | |
| async function addLLDPGlobal() { | |
| const lldpGlobal = document.getElementById('lldpGlobal').value; | |
| const holdMultiplier = document.getElementById('holdMultiplier').value; | |
| const reinitDelay = document.getElementById('reinitDelay').value; | |
| const txDelay = document.getElementById('txDelay').value; | |
| const txInterval = document.getElementById('txInterval').value; | |
| const data = { | |
| lldpGlobal, | |
| holdMultiplier, | |
| reinitDelay, | |
| txDelay, | |
| txInterval | |
| }; | |
| try { | |
| const res = await fetch(`${BASE_URL}/add-lldp-global`, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify(data) | |
| }); | |
| const result = await res.json(); | |
| if (res.ok) { | |
| alert('LLDP Global configuration updated successfully.'); | |
| // ✅ Call the function to refresh form values | |
| await getLLDPGlobal(); | |
| } else { | |
| alert('Error: ' + result.error); | |
| } | |
| } catch (error) { | |
| console.error('Fetch error:', error); | |
| alert('Request failed'); | |
| } | |
| } | |
| async function getLLDPGlobal() { | |
| try { | |
| const res = await fetch(`${BASE_URL}/get-lldp-global`); | |
| const data = await res.json(); | |
| if (res.ok) { | |
| // ✅ Populate input fields with received values | |
| document.getElementById('lldpGlobal').value = data.lldpGlobal || ''; | |
| document.getElementById('holdMultiplier').value = data.holdMultiplier || ''; | |
| document.getElementById('reinitDelay').value = data.reinitDelay || ''; | |
| document.getElementById('txDelay').value = data.txDelay || ''; | |
| document.getElementById('txInterval').value = data.txInterval || ''; | |
| } else { | |
| alert('Error: ' + (data.error || 'Failed to fetch LLDP settings')); | |
| } | |
| } catch (error) { | |
| console.error('GET LLDP Global Error:', error); | |
| alert('Network or server error'); | |
| } | |
| } | |
| // LLDP PORT Configuration | |
| async function lldpPort() { | |
| showContent(` | |
| <div> | |
| <h2>LLDP Global Configuration</h2> | |
| <hr/> | |
| <div style="width: 580px; margin:0px auto; font-family: Arial, sans-serif;"> | |
| <div style="display: flex; flex-direction: column;"> | |
| <div style="display: flex; align-items: center;"> | |
| <span class="lldp-span"> | |
| Port | |
| </span> | |
| <select id="portId" onchange="loadPortConfig()" class="lldp-select"> | |
| <option value="ge1/1" selected>ge1/1</option> | |
| <option value="ge1/2">ge1/2</option> | |
| <option value="ge1/3">ge1/3</option> | |
| <option value="ge1/4">ge1/4</option> | |
| <option value="ge1/5">ge1/5</option> | |
| <option value="ge1/6">ge1/6</option> | |
| <option value="ge1/7">ge1/7</option> | |
| <option value="ge1/8">ge1/8</option> | |
| <option value="ge1/9">ge1/9</option> | |
| <option value="ge1/10">ge1/10</option> | |
| </select> | |
| </div> | |
| <div style="display: flex; align-items: center;"> | |
| <span class="lldp-span"> | |
| LLDP Status | |
| </span> | |
| <select id="lldpStatus" class="lldp-select"> | |
| <option value="0">Enable</option> | |
| <option value="1">Disable</option> | |
| </select> | |
| </div> | |
| <div style="display: flex; align-items: center;"> | |
| <span class="lldp-span"> | |
| Admin Status | |
| </span> | |
| <select id="adminStatus" class="lldp-select" > | |
| <option value="0">Disable</option> | |
| <option value="1">Tx</option> | |
| <option value="2">Rx</option> | |
| <option value="3">TxRx</option> | |
| </select> | |
| </div> | |
| <div style="display: flex; align-items: center;"> | |
| <span class="lldp-span"> | |
| Manage IP | |
| </span> | |
| <input id="manageIp" name="txDelay" type="text" value="0.0.0.0" style="width: 130px; height:28px; padding: 2px; border: 1px solid #7f9db9; font-size: 15px;"> | |
| </div> | |
| <div style="display: flex; align-items: center;"> | |
| <span class="lldp-span"> | |
| Check-Change Interval <0-30s> | |
| </span> | |
| <input id="checkChangeInterval" name="txInterval" type="text" value="0" style="width: 100px; height:28px; padding: 2px; border: 1px solid #7f9db9; font-size: 15px;"> | |
| </div> | |
| <div style="display: flex; align-items: center;"> | |
| <span class="lldp-span"> | |
| DOT1-TLV | |
| </span> | |
| <select id="dot1tlv" class="lldp-select"> | |
| <option value="0">Enable</option> | |
| <option value="1">Disable</option> | |
| </select> | |
| </div> | |
| <div style="display: flex; align-items: center;"> | |
| <span class="lldp-span"> | |
| DOT3-TLV | |
| </span> | |
| <select id="dot3tlv" class="lldp-select"> | |
| <option value="0">Enable</option> | |
| <option value="1">Disable</option> | |
| </select> | |
| </div> | |
| <div style="display: flex; align-items: center;"> | |
| <span class="lldp-span"> | |
| MED-TLV | |
| </span> | |
| <select id="medtlv" class="lldp-select"> | |
| <option value="0">Enable</option> | |
| <option value="1">Disable</option> | |
| </select> | |
| </div> | |
| </div> | |
| </div> | |
| <div style="text-align: center; margin-top: 10px;"> | |
| <button onclick="lldpPort()" id="refreshBtn" >Refresh</button> | |
| <button onclick="applylldpPortConfig()" id="applyBtn">Apply</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| <table id="lldpPortTable" style="width: 100%; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 12px; margin-top:25px"> | |
| <thead> | |
| <tr class="lldp-global-table-header"> | |
| <th>Port</th> | |
| <th>LLDP Status</th> | |
| <th>Admin Status</th> | |
| <th>Manage IP</th> | |
| <th>Check Change Interval</th> | |
| <th>DOT1-TLV</th> | |
| <th>DOT3-TLV</th> | |
| <th>MED-TLV</th> | |
| </tr> | |
| </thead> | |
| <tbody id="lldpPortTableBody" > | |
| </tbody> | |
| </table> | |
| </div> | |
| `); | |
| fetchLldpPortTableByPort() | |
| } | |
| function formatAdminStatus(value) { | |
| value = parseInt(value); | |
| return value === 3 ? "TxRx" : value === 2 ? "Rx" : value === 1 ? "Tx" : "Disable"; | |
| } | |
| async function applylldpPortConfig() { | |
| const port = document.getElementById("portId").value; | |
| const status = document.getElementById("lldpStatus").value; | |
| const adminStatus = document.getElementById("adminStatus").value; | |
| const interval = document.getElementById("checkChangeInterval").value; | |
| const manageIp = document.getElementById("manageIp").value; | |
| const dot1tlv = document.getElementById("dot1tlv").value; | |
| const dot3tlv = document.getElementById("dot3tlv").value; | |
| const medtlv = document.getElementById("medtlv").value; | |
| const payload = { | |
| port, // ✅ include port here! | |
| status: status === "0" ? "Enable" : "Disable", | |
| adminStatus: formatAdminStatus(adminStatus), | |
| interval, | |
| manageIp, | |
| dot1tlv: dot1tlv === "0" ? "Enable" : "Disable", | |
| dot3tlv: dot3tlv === "0" ? "Enable" : "Disable", | |
| medtlv: medtlv === "0" ? "Enable" : "Disable", | |
| }; | |
| try { | |
| const response = await fetch(`${BASE_URL}/apply-lldp-port-config`, { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| }, | |
| body: JSON.stringify(payload), | |
| }); | |
| const contentType = response.headers.get("Content-Type") || ""; | |
| if (!contentType.includes("application/json")) { | |
| const text = await response.text(); | |
| console.error("Non-JSON error:", text); | |
| alert("Server returned unexpected HTML (check console for details)."); | |
| return; | |
| } | |
| const result = await response.json(); | |
| if (response.ok) { | |
| alert(`LLDP config updated for port ${port}`); | |
| fetchLldpPortTableByPort(); // refresh table | |
| } else { | |
| alert(result.message || "Update failed"); | |
| } | |
| } catch (error) { | |
| console.error("Error applying LLDP port config:", error); | |
| alert("Something went wrong"); | |
| } | |
| } | |
| async function fetchLldpPortTableByPort() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/get-lldp-port-table-data`); | |
| const data = await response.json(); | |
| const tbody = document.getElementById("lldpPortTableBody"); | |
| tbody.innerHTML = ""; | |
| data.forEach(row => { | |
| const tr = document.createElement("tr"); | |
| tr.classList.add("tableRowData"); | |
| tr.innerHTML = ` | |
| <td>${row.port}</td> | |
| <td>${row.status}</td> | |
| <td>${row.adminStatus}</td> | |
| <td>${row.manageIp}</td> | |
| <td>${row.interval}</td> | |
| <td>${row.dot1tlv}</td> | |
| <td>${row.dot3tlv}</td> | |
| <td>${row.medtlv}</td> | |
| `; | |
| tbody.appendChild(tr); | |
| }); | |
| } catch (error) { | |
| alert("Failed to load LLDP port data."); | |
| } | |
| } | |
| async function loadPortConfig() { | |
| const port = document.getElementById("portId").value; | |
| try { | |
| const response = await fetch(`${BASE_URL}/get-lldp-port-config?port=${encodeURIComponent(port)}`); | |
| if (!response.ok) { | |
| throw new Error("Failed to fetch port config"); | |
| } | |
| const data = await response.json(); | |
| document.getElementById("lldpStatus").value = data.status === "Enable" ? "0" : "1"; | |
| document.getElementById("adminStatus").value = | |
| data.adminStatus === "TxRx" ? "3" : | |
| data.adminStatus === "Rx" ? "2" : | |
| data.adminStatus === "Tx" ? "1" : "0"; | |
| document.getElementById("manageIp").value = data.manageIp || ""; | |
| document.getElementById("checkChangeInterval").value = data.interval || ""; | |
| document.getElementById("dot1tlv").value = data.dot1tlv === "Enable" ? "0" : "1"; | |
| document.getElementById("dot3tlv").value = data.dot3tlv === "Enable" ? "0" : "1"; | |
| document.getElementById("medtlv").value = data.medtlv === "Enable" ? "0" : "1"; | |
| } catch (error) { | |
| console.error("Error loading port config:", error); | |
| alert("Failed to load port configuration."); | |
| } | |
| } | |
| // LLDP Neighbor // | |
| async function lldpNeighbor(params) { | |
| showContent(` | |
| <h2>LLDP Neighbor </h2> | |
| <div style="font-family: Arial, sans-serif; font-size: 13px; padding: 20px;"> | |
| <table style="width: 100%; border-collapse: collapse;"> | |
| <thead> | |
| <tr> | |
| <th style="background-color: #dbe5f1; border: 1px solid #a0a0a0; padding: 6px 10px; text-align: center; white-space: nowrap;">Index</th> | |
| <th style="background-color: #dbe5f1; border: 1px solid #a0a0a0; padding: 6px 10px; text-align: center; white-space: nowrap;">Local Port</th> | |
| <th style="background-color: #dbe5f1; border: 1px solid #a0a0a0; padding: 6px 10px; text-align: center; white-space: nowrap;">Device ID</th> | |
| <th style="background-color: #dbe5f1; border: 1px solid #a0a0a0; padding: 6px 10px; text-align: center; white-space: nowrap;">Chassis ID</th> | |
| <th style="background-color: #dbe5f1; border: 1px solid #a0a0a0; padding: 6px 10px; text-align: center; white-space: nowrap;">Port ID</th> | |
| <th style="background-color: #dbe5f1; border: 1px solid #a0a0a0; padding: 6px 10px; text-align: center; white-space: nowrap;">Manage IP</th> | |
| <th style="background-color: #dbe5f1; border: 1px solid #a0a0a0; padding: 6px 10px; text-align: center; white-space: nowrap;">VLAN</th> | |
| <th style="background-color: #dbe5f1; border: 1px solid #a0a0a0; padding: 6px 10px; text-align: center; white-space: nowrap;">TTL (s)</th> | |
| <th style="background-color: #dbe5f1; border: 1px solid #a0a0a0; padding: 6px 10px; text-align: center; white-space: nowrap;">Capability</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| </tbody> | |
| </table> | |
| <div style="margin-top: 20px; text-align: center;"> | |
| <button onclick="lldpNeighbor()" id="refreshBtn" >Refresh</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| </div> | |
| `) | |
| } | |
| ///////////////////////MAC ADDRESS TABLE | |
| async function showMacAddressTable() { | |
| showContent('<p>Loading MAC address table...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/mac-address-table`); | |
| const data = await response.json(); | |
| // Extract unique ports from data as strings directly | |
| const uniquePorts = [...new Set(data.map(entry => (entry.port ?? '').trim()))]; | |
| const portOptions = ['<option value="All">All</option>', ...uniquePorts.map(port => `<option value="${port}">${port}</option>`)].join(''); | |
| showContent(` | |
| <h2>MAC Address Table</h2> | |
| <div class="info"> | |
| <label for="macPort">Port:</label> | |
| <select style="width:20%;" id="macPort"> | |
| ${portOptions} | |
| </select> | |
| <br> | |
| <label for="vlan">VLAN ID:</label> | |
| <input type="number" id="vlan" placeholder="0 means All VLAN" value="0" style="width:20%;"> 0 means All VLAN | |
| <br> | |
| <label for="macNumber">MAC Number:</label> | |
| <input type="number" id="macNumber" placeholder="Number of MACs to show" value="10" style="width:20%;" readonly> | |
| <br> | |
| <button onclick="displayTable()">Display</button> | |
| <button onclick="showHelp()">Help</button> | |
| </div> | |
| <table id="macTable"> | |
| <thead> | |
| <tr> | |
| <th>MAC Address</th> | |
| <th>VLAN ID</th> | |
| <th>Port</th> | |
| <th>Static</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <!-- Dynamic rows will be added here --> | |
| </tbody> | |
| </table> | |
| `); | |
| // Initial display after a short delay | |
| setTimeout(displayTable, 100); | |
| } catch (err) { | |
| console.error("Failed to load MAC address table:", err); | |
| showContent('<p style="color:red;">Failed to load MAC address table.</p>'); | |
| } | |
| } | |
| async function displayTable() { | |
| const port = document.getElementById('macPort')?.value ?? "All"; | |
| const vlan = parseInt(document.getElementById('vlan')?.value ?? "0"); | |
| const macNumberInput = document.getElementById('macNumber'); | |
| console.log('Selected port:', port); | |
| console.log('Selected vlan:', vlan); | |
| try { | |
| const response = await fetch(`${BASE_URL}/mac-address-table`); | |
| const data = await response.json(); | |
| console.log('Fetched data length:', data.length); | |
| // Filter data based on selected port and VLAN | |
| const filtered = data.filter(entry => { | |
| const portMatch = (port === "All") || ((entry.port ?? '').trim() === port); | |
| const vlanMatch = (vlan === 0) || (entry.vlanId === vlan); | |
| return portMatch && vlanMatch; | |
| }); | |
| console.log('Filtered entries count:', filtered.length); | |
| // Always update the MAC number input to reflect filtered count | |
| const macNumber = filtered.length; | |
| if (macNumberInput) macNumberInput.value = macNumber; | |
| // Limit the displayed entries to the filtered count | |
| const entriesToShow = filtered.slice(0, macNumber); | |
| const tbody = document.querySelector('#macTable tbody'); | |
| tbody.innerHTML = ''; | |
| entriesToShow.forEach(entry => { | |
| const row = document.createElement('tr'); | |
| row.innerHTML = ` | |
| <td>${entry.macAddress}</td> | |
| <td>${entry.vlanId ?? '-'}</td> | |
| <td>${entry.port}</td> | |
| <td>${(entry.static === true || entry.static === 1) ? '0' : '1'}</td> | |
| `; | |
| tbody.appendChild(row); | |
| }); | |
| if (entriesToShow.length === 0) { | |
| tbody.innerHTML = '<tr><td colspan="4" style="text-align:center;">No matching entries found.</td></tr>'; | |
| } | |
| } catch (err) { | |
| console.error("Error fetching or displaying MAC table:", err); | |
| alert("Failed to fetch MAC address table."); | |
| } | |
| } | |
| function refreshMacAddressTable() { | |
| showMacAddressTable(); | |
| } | |
| ////////////MAC BIND TABLE | |
| async function showMacBindTable() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/mac-bind-table`); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| const data = await response.json(); | |
| const portOptions = Array.from({ length: 10 }, (_, i) => `<option value="${i + 1}">${i + 1}</option>`).join(''); | |
| // Render form and table | |
| showContent(` | |
| <h2>MAC Address Binding</h2> | |
| <div class="info"> | |
| <label for="macPort">Port:</label> | |
| <select id="macPort" style="width:20%;">${portOptions}</select><br> | |
| <label for="macAddress">MAC Address:</label> | |
| <input type="text" id="macAddress" placeholder="Format: AA:BB:CC:DD:EE:FF" style="width:40%;"><br> | |
| <label for="vlanId">VLAN ID:</label> | |
| <input type="number" id="vlanId" value="0" placeholder="0 means All VLAN" style="width:20%;"><br> | |
| <button onclick="showMacBindTable()">Refresh</button> | |
| <button onclick="selectAll()">Select All</button> | |
| <button onclick="applybinding()">Apply</button> | |
| <button onclick="deleteEntryMac()">Delete</button> | |
| </div> | |
| <table id="macTable" border="1"> | |
| <thead> | |
| <tr> | |
| <th>Select</th> | |
| <th>MAC Address</th> | |
| <th>VLAN ID</th> | |
| <th>Port</th> | |
| </tr> | |
| </thead> | |
| <tbody id="macTableBody"></tbody> | |
| </table> | |
| `); | |
| populateTable12(data); | |
| } catch (err) { | |
| console.error("Failed to load MAC bind table:", err); | |
| showContent('<p style="color:red;">Failed to load MAC bind table.</p>'); | |
| } | |
| } | |
| function populateTable12(data) { | |
| const tbody = document.getElementById('macTableBody'); | |
| tbody.innerHTML = ''; | |
| if (!Array.isArray(data) || data.length === 0) { | |
| tbody.innerHTML = `<tr><td colspan="5" style="text-align:center;">No MAC bindings available.</td></tr>`; | |
| return; | |
| } | |
| data.forEach(({ macAddress, vlanId, port }) => { | |
| const row = document.createElement('tr'); | |
| row.innerHTML = ` | |
| <td><input type="checkbox" data-mac="${macAddress}" data-vlan="${vlanId}" data-port="${port}"></td> | |
| <td>${macAddress}</td> | |
| <td>${vlanId}</td> | |
| <td>${port}</td> | |
| `; | |
| tbody.appendChild(row); | |
| }); | |
| } | |
| function selectAll() { | |
| document.querySelectorAll('#macTableBody input[type="checkbox"]').forEach(cb => cb.checked = true); | |
| } | |
| // Basic MAC validation | |
| function validateMAC(mac) { | |
| const macPattern = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/; | |
| return macPattern.test(mac); | |
| } | |
| async function applybinding() { | |
| const port = document.getElementById('macPort').value; | |
| const macAddress = document.getElementById('macAddress').value.trim(); | |
| const vlanId = document.getElementById('vlanId').value; | |
| if (!port) { | |
| alert('Please select a port.'); | |
| return; | |
| } | |
| if (!macAddress || !validateMAC(macAddress)) { | |
| alert('Invalid MAC Address format. Use XX:XX:XX:XX:XX:XX.'); | |
| return; | |
| } | |
| if (!vlanId || isNaN(vlanId) || vlanId < 0) { | |
| alert('Please enter a valid VLAN ID.'); | |
| return; | |
| } | |
| const payload = { port, macAddress, vlanId: parseInt(vlanId) }; | |
| try { | |
| const response = await fetch(`${BASE_URL}/mac-bind-table`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(payload) | |
| }); | |
| if (!response.ok) { | |
| const errorMsg = await response.text(); | |
| throw new Error(errorMsg || 'Failed to apply MAC binding'); | |
| } | |
| alert('MAC Address binding applied successfully!'); | |
| await showMacBindTable(); // refresh the table | |
| } catch (error) { | |
| console.error('Apply binding failed:', error); | |
| alert('Failed to apply MAC binding'); | |
| } | |
| } | |
| async function deleteEntryMac() { | |
| const checkedBoxes = document.querySelectorAll('#macTableBody input[type="checkbox"]:checked'); | |
| if (checkedBoxes.length === 0) { | |
| alert('Please select at least one entry to delete.'); | |
| return; | |
| } | |
| const deletePayloads = Array.from(checkedBoxes).map(cb => ({ | |
| macAddress: cb.getAttribute('data-mac'), | |
| vlanId: parseInt(cb.getAttribute('data-vlan')), | |
| port: cb.getAttribute('data-port') | |
| })); | |
| try { | |
| // Assuming backend supports batch delete or you can call delete multiple times | |
| for (const entry of deletePayloads) { | |
| const response = await fetch(`${BASE_URL}/mac-bind-table`, { | |
| method: 'DELETE', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(entry) | |
| }); | |
| if (!response.ok) { | |
| const errorText = await response.text(); | |
| throw new Error(errorText || 'Failed to delete entry'); | |
| } | |
| } | |
| alert('Selected entries deleted successfully!'); | |
| await showMacBindTable(); | |
| } catch (error) { | |
| console.error('Delete failed:', error); | |
| alert('Failed to delete selected entries.'); | |
| } | |
| } | |
| /////////////AUTO BIND | |
| let macData = []; // store backend data globally once fetched | |
| let allSelected = false; | |
| async function macAutoBind() { | |
| showContent('<p>Loading MAC Auto Bind data...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/mac-auto-bind`); | |
| macData = await response.json(); // store globally | |
| const portOptions = Array.from({ length: 10 }, (_, i) => `<option value="${i + 1}">${i + 1}</option>`).join(''); | |
| showContent(` | |
| <h2>MAC AutoBind Table</h2> | |
| <p>(Select MAC addresses dynamically learned on a port and bind them.)</p> | |
| <div class="info"> | |
| <label for="macPort">Port:</label> | |
| <select id="macPort" onchange="displayMacTableByPort()" style="width:20%;"> | |
| <option value="0">All</option> | |
| ${portOptions} | |
| </select><br> | |
| <button onclick="macAutoBind()">Refresh</button> | |
| <button onclick="selectAll()">Select All</button> | |
| <button onclick="applyBind()">Apply</button> | |
| <button onclick="showHelp()">Help</button> | |
| </div> | |
| <table id="macTable" border="1"> | |
| <thead> | |
| <tr> | |
| <th>Select</th> | |
| <th>MAC Address</th> | |
| <th>VLAN ID</th> | |
| </tr> | |
| </thead> | |
| <tbody id="macTableBody"> | |
| ${generateMacTableRows(macData, "0")} | |
| </tbody> | |
| </table> | |
| `); | |
| allSelected = false; // reset selectAll toggle on reload | |
| } catch (err) { | |
| console.error("Failed to load MAC auto bind table:", err); | |
| showContent('<p style="color:red;">Failed to load MAC auto bind table.</p>'); | |
| } | |
| } | |
| // Only show entries with valid MAC and VLAN data | |
| function generateMacTableRows(data, selectedPort) { | |
| const filtered = selectedPort === "0" ? data : data.filter(d => String(d.port) === selectedPort); | |
| const validEntries = filtered.filter(entry => { | |
| // Basic validation: macAddress non-empty string, vlanId can be parsed to number | |
| return entry.macAddress && entry.macAddress.trim() !== "" && | |
| entry.vlanId != null && !isNaN(parseInt(entry.vlanId, 10)); | |
| }); | |
| if (validEntries.length === 0) { | |
| return `<tr><td colspan="3" style="text-align:center;">No entries found.</td></tr>`; | |
| } | |
| return validEntries.map(entry => ` | |
| <tr> | |
| <td><input type="checkbox" name="macSelect" data-mac="${entry.macAddress}" data-vlan="${entry.vlanId}"></td> | |
| <td>${entry.macAddress}</td> | |
| <td>${entry.vlanId}</td> | |
| </tr> | |
| `).join(''); | |
| } | |
| function displayMacTableByPort() { | |
| const selectedPort = document.getElementById("macPort").value; | |
| const tbody = document.getElementById("macTableBody"); | |
| tbody.innerHTML = generateMacTableRows(macData, selectedPort); | |
| allSelected = false; // reset selectAll toggle on filter change | |
| } | |
| function selectAll() { | |
| // Only toggle checkboxes currently visible in the table body | |
| const checkboxes = document.querySelectorAll('#macTableBody input[type="checkbox"]'); | |
| allSelected = !allSelected; | |
| checkboxes.forEach(cb => cb.checked = allSelected); | |
| } | |
| async function applyBind() { | |
| const port = document.getElementById("macPort").value; | |
| if (!port || port === "0") { | |
| alert('Please select a valid port.'); | |
| return; | |
| } | |
| // Get only checked checkboxes from the currently filtered table rows | |
| const checkedBoxes = Array.from(document.querySelectorAll('#macTableBody input[type="checkbox"]:checked')); | |
| if (checkedBoxes.length === 0) { | |
| alert('Please select at least one MAC entry.'); | |
| return; | |
| } | |
| // Extract macAddress and vlanId from each checked row | |
| const bindings = checkedBoxes.map(cb => ({ | |
| macAddress: cb.dataset.mac, | |
| vlanId: parseInt(cb.dataset.vlan, 10), | |
| port: port // port from dropdown, not from data attribute | |
| })); | |
| // Defensive validation | |
| if (bindings.some(b => !b.macAddress || isNaN(b.vlanId))) { | |
| alert('One or more selected entries have invalid MAC or VLAN data.'); | |
| console.error('Invalid data in bindings:', bindings); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/mac-bind-table`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ bindings }) | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`Server responded with status ${response.status}`); | |
| } | |
| alert(`MAC binding applied for ${bindings.length} entries on port ${port}.`); | |
| await macAutoBind(); // Refresh table and reset selections | |
| } catch (error) { | |
| console.error('Failed to apply MAC binding:', error); | |
| alert('Failed to apply MAC binding. Please try again.'); | |
| } | |
| } | |
| ///////// mac FILTER | |
| let macAutoBindTable = []; | |
| async function showMacFilter() { | |
| showContent('<p>Loading MAC Filter Configuration...</p>'); | |
| const portOptions = Array.from({ length: 10 }, (_, i) => `<option value="${i + 1}">${i + 1}</option>`).join(''); | |
| showContent(` | |
| <h2>MAC Filter Configuration</h2> | |
| <p>(The list will display the MAC addresses and VLAN ID that the port has dynamically learned. You can select one or more items and then press apply to filter those MAC addresses to that port.)</p> | |
| <div class="info"> | |
| <label for="macPort">Port:</label> | |
| <select style="width:20%;" id="macPort" onchange="refreshMacFilter()"> | |
| <option value="0">Select port</option> | |
| ${portOptions} | |
| </select><br> | |
| <label for="macAddress">MAC Address:</label> | |
| <input type="text" id="macAddress" placeholder="Format: AA:BB:CC:DD:EE:FF" style="width:40%;"><br> | |
| <label for="vlanId">VLAN ID:</label> | |
| <input type="number" id="vlanId" value="0" placeholder="0 means All VLAN" style="width:20%;"><br> | |
| <button onclick="refreshMacFilter()">Refresh</button> | |
| <button onclick="selectAll()">Select All</button> | |
| <button onclick="apply12()">Apply</button> | |
| <button onclick="deleteEntry()">Delete</button> | |
| <button onclick="showHelp()">Help</button> | |
| </div> | |
| <table id="macTable"> | |
| <thead> | |
| <tr> | |
| <th>Select</th> | |
| <th>MAC Address</th> | |
| <th>VLAN ID</th> | |
| </tr> | |
| </thead> | |
| <tbody id="macTableBody"></tbody> | |
| </table> | |
| `); | |
| await refreshMacFilter(); // Load initial table | |
| } | |
| async function refreshMacFilter() { | |
| const selectedPort = document.getElementById("macPort").value; | |
| if (!selectedPort || selectedPort === "0") { | |
| document.getElementById("macTableBody").innerHTML = `<tr><td colspan="3" style="text-align:center;">Select a port to view data.</td></tr>`; | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/mac-filter?port=${selectedPort}`); | |
| if (!response.ok) throw new Error(`Status ${response.status}`); | |
| const data = await response.json(); | |
| macAutoBindTable = data; | |
| populateTable(); | |
| } catch (err) { | |
| console.error("Failed to fetch MAC filter data:", err); | |
| showContent('<p style="color:red;">Failed to load MAC filter table.</p>'); | |
| } | |
| } | |
| function populateTable() { | |
| const tbody = document.getElementById("macTableBody"); | |
| tbody.innerHTML = ''; | |
| if (!macAutoBindTable.length) { | |
| tbody.innerHTML = `<tr><td colspan="3" style="text-align:center;">No entries found.</td></tr>`; | |
| return; | |
| } | |
| macAutoBindTable.forEach(({ macAddress, vlanId }) => { | |
| const row = document.createElement('tr'); | |
| row.innerHTML = ` | |
| <td><input type="checkbox" data-mac="${macAddress}" data-vlan="${vlanId}"></td> | |
| <td>${macAddress}</td> | |
| <td>${vlanId}</td> | |
| `; | |
| tbody.appendChild(row); | |
| }); | |
| } | |
| function selectAll() { | |
| const checkboxes = document.querySelectorAll('#macTableBody input[type="checkbox"]'); | |
| checkboxes.forEach(cb => cb.checked = true); | |
| } | |
| async function apply12() { | |
| const port = document.getElementById('macPort').value; | |
| if (!port || port === "0") { | |
| alert('Please select a port.'); | |
| return; | |
| } | |
| const selectedCheckboxes = [...document.querySelectorAll('#macTableBody input[type="checkbox"]:checked')]; | |
| const selectedEntries = selectedCheckboxes.map(cb => ({ | |
| macAddress: cb.getAttribute('data-mac'), | |
| vlanId: parseInt(cb.getAttribute('data-vlan'), 10) | |
| })); | |
| const manualMac = document.getElementById("macAddress").value.trim(); | |
| const manualVlan = parseInt(document.getElementById("vlanId").value.trim(), 10); | |
| if (manualMac && !isNaN(manualVlan)) { | |
| selectedEntries.push({ macAddress: manualMac, vlanId: manualVlan }); | |
| } | |
| if (selectedEntries.length === 0) { | |
| alert('Please select at least one MAC address or enter one manually.'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/mac-filter`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ port, macEntries: selectedEntries }) | |
| }); | |
| if (!response.ok) throw new Error(`Server responded with status ${response.status}`); | |
| // Response now returns data filtered by selected port | |
| const updatedData = await response.json(); | |
| macAutoBindTable = updatedData; | |
| populateTable(); | |
| alert(`MAC Filter applied successfully to port ${port}.`); | |
| refreshMacFilter() | |
| } catch (err) { | |
| console.error('Failed to apply MAC filter:', err); | |
| alert('Failed to apply MAC filter. Please try again.'); | |
| } | |
| } | |
| async function deleteEntry() { | |
| const port = document.getElementById('macPort').value; | |
| if (!port || port === "0") { | |
| alert('Please select a port.'); | |
| return; | |
| } | |
| const selectedCheckboxes = [...document.querySelectorAll('#macTableBody input[type="checkbox"]:checked')]; | |
| if (selectedCheckboxes.length === 0) { | |
| alert('Please select at least one entry to delete.'); | |
| return; | |
| } | |
| const entriesToDelete = selectedCheckboxes.map(cb => ({ | |
| macAddress: cb.getAttribute('data-mac'), | |
| vlanId: parseInt(cb.getAttribute('data-vlan'), 10) | |
| })); | |
| try { | |
| const response = await fetch(`${BASE_URL}/mac-filter`, { | |
| method: 'DELETE', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ port, entries: entriesToDelete }) // <-- include port here | |
| }); | |
| if (!response.ok) throw new Error(`Server responded with status ${response.status}`); | |
| // Backend returns data filtered by port | |
| const updatedData = await response.json(); | |
| macAutoBindTable = updatedData; | |
| populateTable(); | |
| alert('Selected entries deleted successfully.'); | |
| } catch (err) { | |
| console.error('Failed to delete MAC filter entries:', err); | |
| alert('Failed to delete selected entries. Please try again.'); | |
| } | |
| } | |
| function showHelp() { | |
| alert('Help info: Select MAC addresses and VLAN IDs, choose a port, then click Apply to filter those MAC addresses on the port. Use Delete to remove selected entries.'); | |
| } | |
| /////////AUTO FILTER | |
| async function macAutoFind() { | |
| showContent('<p>Loading MAC Auto Filter...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/mac-auto-filter`); | |
| macData = await response.json(); | |
| const portOptions = Array.from({ length: 10 }, (_, i) => `<option value="${i + 1}">${i + 1}</option>`).join(''); | |
| showContent(` | |
| <h2>MAC Auto Filter</h2> | |
| <p>(The list will display the MAC addresses and VLAN ID that the port has dynamically learned. You can select one or more items and then press apply to filter those MAC addresses from that port.)</p> | |
| <div class="info"> | |
| <label for="macPort">Port:</label> | |
| <select id="macPort" style="width:20%;" onchange="displayMacTableByPort()"> | |
| <option value="0">All</option> | |
| ${portOptions} | |
| </select><br> | |
| <button onclick="macAutoFind()">Refresh</button> | |
| <button onclick="selectAll()">Select All</button> | |
| <button onclick="applyFilter()">Apply</button> | |
| <button onclick="showHelp()">Help</button> | |
| </div> | |
| <table id="macTable" border="1" style="width:100%; margin-top:10px;"> | |
| <thead> | |
| <tr> | |
| <th>Select</th> | |
| <th>MAC Address</th> | |
| <th>VLAN ID</th> | |
| </tr> | |
| </thead> | |
| <tbody id="macTableBody"> | |
| <!-- Filled dynamically --> | |
| </tbody> | |
| </table> | |
| `); | |
| displayMacTableByPort(); // show all on initial load | |
| } catch (err) { | |
| console.error("Failed to load MAC auto filter table:", err); | |
| showContent('<p style="color:red;">Failed to load MAC auto filter table.</p>'); | |
| } | |
| } | |
| // Render filtered MAC/VLAN entries by selected port | |
| function displayMacTableByPort() { | |
| const portSelection = document.getElementById('macPort').value; | |
| const tbody = document.getElementById('macTableBody'); | |
| tbody.innerHTML = ''; | |
| if (!Array.isArray(macData) || macData.length === 0) { | |
| tbody.innerHTML = `<tr><td colspan="3" style="text-align:center;">No data available.</td></tr>`; | |
| return; | |
| } | |
| // Filter by port (or show all if "0") | |
| const filteredData = macData.filter(entry => { | |
| if (!entry.port) return false; | |
| const portNumber = entry.port.toString().split('/').pop().trim(); | |
| return portSelection === "0" || portNumber === portSelection; | |
| }); | |
| if (filteredData.length === 0) { | |
| tbody.innerHTML = `<tr><td colspan="3" style="text-align:center;">No data found for this selection.</td></tr>`; | |
| return; | |
| } | |
| filteredData.forEach((entry, index) => { | |
| const row = document.createElement('tr'); | |
| row.innerHTML = ` | |
| <td><input type="checkbox" class="macCheckbox" data-mac="${entry.macAddress}" data-vlan="${entry.vlanId}"></td> | |
| <td>${entry.macAddress ?? '-'}</td> | |
| <td>${entry.vlanId ?? '-'}</td> | |
| `; | |
| tbody.appendChild(row); | |
| }); | |
| allSelected = false; // reset select all toggle on filter change | |
| } | |
| // Toggle select/deselect all visible checkboxes | |
| function selectAll() { | |
| const checkboxes = document.querySelectorAll('#macTableBody input[type="checkbox"]'); | |
| allSelected = !allSelected; | |
| checkboxes.forEach(cb => cb.checked = allSelected); | |
| } | |
| // Send selected MAC+VLAN with selected port to backend for filtering (blocking) | |
| async function applyFilter() { | |
| const port = document.getElementById('macPort').value; | |
| if (!port || port === "0") { | |
| alert('Please select a valid port.'); | |
| return; | |
| } | |
| const checkedBoxes = Array.from(document.querySelectorAll('#macTableBody input[type="checkbox"]:checked')); | |
| if (checkedBoxes.length === 0) { | |
| alert('Please select at least one MAC entry.'); | |
| return; | |
| } | |
| // Build payload for backend | |
| const bindings = checkedBoxes.map(cb => ({ | |
| macAddress: cb.dataset.mac, | |
| vlanId: parseInt(cb.dataset.vlan, 10), | |
| port | |
| })); | |
| // Validate entries | |
| if (bindings.some(b => !b.macAddress || isNaN(b.vlanId))) { | |
| alert('One or more selected entries have invalid MAC or VLAN data.'); | |
| console.error('Invalid data in bindings:', bindings); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/mac-filter-apply`, { // change endpoint as needed | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ bindings }) | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`Server responded with status ${response.status}`); | |
| } | |
| alert(`MAC filter applied for ${bindings.length} entries on port ${port}.`); | |
| await macAutoFind(); // Refresh the UI | |
| } catch (error) { | |
| console.error('Failed to apply MAC filter:', error); | |
| alert('Failed to apply MAC filter. Please try again.'); | |
| } | |
| } | |
| // Example help popup | |
| function showHelp() { | |
| alert(`Select a port to filter MAC addresses dynamically learned on that port.\n | |
| Select one or more MAC addresses and click Apply to filter them from the selected port.`); | |
| } | |
| ////VLAN INFORMATION | |
| async function showVlans() { | |
| showContent('<p>Loading VLAN Information...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/get-vlan-info`); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| const data = await response.json(); | |
| const vlanList = Array.isArray(data?.vlans) ? data.vlans : []; | |
| if (vlanList.length === 0) { | |
| showContent('<p>No VLAN data available.</p>'); | |
| return; | |
| } | |
| // Step 1: Render HTML with placeholder <select> | |
| showContent(` | |
| <h2>VLAN Information</h2> | |
| <p><small> | |
| (Note: The drop-down box displays all current VLANs. The list displays up to 1000 VLANs. | |
| If you select a VLAN in the drop-down box, the list will show all VLANs with VID >= selected VLAN, | |
| but no more than 1000 VLANs.)<br> | |
| (t = tagged member, u = untagged member) | |
| </small></p> | |
| <select style="width: 20%;" id="vlanSelect"></select> | |
| <table id="vlanTable" border="1" cellspacing="0" cellpadding="5" style="border-collapse: collapse; width: 100%;"> | |
| <thead style="background-color: #d9e1f2;"> | |
| <tr> | |
| <th>VID</th> | |
| <th>VLAN Name</th> | |
| <th>State</th> | |
| <th>Port Member</th> | |
| </tr> | |
| </thead> | |
| <tbody></tbody> | |
| </table> | |
| <br> | |
| <button id= "refreshBtn" onclick="showVlans()">Refresh</button> | |
| <button id= "helpBtn" onclick="showVlanHelp()">Help</button> | |
| `); | |
| // Step 2: Populate select dropdown after rendering | |
| const vlanSelect = document.getElementById("vlanSelect"); | |
| vlanSelect.innerHTML = ''; // clear any previous content | |
| vlanList.forEach(vlan => { | |
| const option = document.createElement("option"); | |
| option.value = String(vlan.vid); // Ensure string type | |
| option.textContent = `vlan${vlan.vid}`; | |
| vlanSelect.appendChild(option); | |
| }); | |
| // Step 3: Set default selection | |
| const minVid = Math.min(...vlanList.map(v => v.vid)); | |
| vlanSelect.value = String(minVid); // MUST match option.value | |
| // Step 4: Display table and bind change handler | |
| displayVlanInfo(vlanList); | |
| vlanSelect.addEventListener("change", () => displayVlanInfo(vlanList)); | |
| } catch (error) { | |
| console.error("Error fetching VLAN data:", error); | |
| showContent(`<p style="color:red">Failed to fetch VLAN data: ${error.message}</p>`); | |
| } | |
| } | |
| function populateVlanList(vlans) { | |
| const vlanSelect = document.getElementById("vlanSelect"); | |
| vlanSelect.innerHTML = ''; | |
| vlans.forEach(vlan => { | |
| const option = document.createElement("option"); | |
| option.value = vlan.vid; | |
| option.textContent = `vlan${vlan.vid}`; | |
| vlanSelect.appendChild(option); | |
| }); | |
| } | |
| function displayVlanInfo(vlans) { | |
| const vlanSelect = document.getElementById("vlanSelect"); | |
| if (!vlanSelect) return; | |
| const selectedVid = Number(vlanSelect.value); | |
| const filteredVlans = vlans | |
| .filter(vlan => vlan.vid >= selectedVid) | |
| .slice(0, 1000); | |
| const tbody = document.querySelector("#vlanTable tbody"); | |
| tbody.innerHTML = ''; | |
| filteredVlans.forEach(vlan => { | |
| const row = document.createElement('tr'); | |
| row.innerHTML = ` | |
| <td>${vlan.vid}</td> | |
| <td>${vlan.vlan_name || ''}</td> | |
| <td>${vlan.State || ''}</td> | |
| <td>${vlan.Port_member || ''}</td> | |
| `; | |
| tbody.appendChild(row); | |
| }); | |
| } | |
| function showVlanHelp() { | |
| alert("This panel displays all VLANs currently configured. Use the dropdown to filter VLANs by VID."); | |
| } | |
| /////VLAN CONFIGURATION | |
| async function fetchVlanData() { | |
| showContent('<p>Loading Static VLAN Configuration...</p>'); | |
| showContent(` | |
| <h2>Static VLAN Configuration</h2> | |
| <table> | |
| <thead style="background-color: #d9e1f2;"> | |
| <tr> | |
| <th>VID</th> | |
| <th>VLAN Name</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td><input type="number" id="vidInput" /></td> | |
| <td><input type="text" id="vlanNameInput" /></td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <select id="vlanList" size="10" style="width: 20%; margin-top: 10px;"> | |
| <!-- Populated dynamically --> | |
| </select> | |
| <br><br> | |
| <button id ="refreshBtn" onclick="fetchVlanData();">Refresh</button> | |
| <button id ="applyBtn" onclick="applyVlan()">Apply</button> | |
| <button id ="deleteBtn" onclick="deleteVlanID()">Delete</button> | |
| <button id ="helpBtn" onclick="showHelp()">Help</button> | |
| `); | |
| populateVlanList(); | |
| } | |
| async function populateVlanList() { | |
| const vlanList = document.getElementById('vlanList'); | |
| if (!vlanList) { | |
| console.error('VLAN list select element not found'); | |
| return; | |
| } | |
| vlanList.innerHTML = '<option>Loading...</option>'; | |
| try { | |
| const response = await fetch(`${BASE_URL}/vlans`); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| const data = await response.json(); | |
| const vlanData = Array.isArray(data) ? data : data.vlans; | |
| if (!Array.isArray(vlanData)) { | |
| throw new Error('Invalid VLAN data format, expected array'); | |
| } | |
| vlanList.innerHTML = ''; | |
| vlanData.forEach(vlan => { | |
| const option = document.createElement('option'); | |
| option.value = vlan.vid; | |
| option.textContent = `vlan${vlan.vid}`; // Show as "vlan<ID>" | |
| vlanList.appendChild(option); | |
| }); | |
| } catch (error) { | |
| console.error("Error fetching VLAN data:", error); | |
| vlanList.innerHTML = '<option>Error loading VLANs</option>'; | |
| } | |
| } | |
| async function applyVlan() { | |
| const vid = parseInt(document.getElementById("vidInput").value.trim(), 10); | |
| const vlan_name = document.getElementById("vlanNameInput").value.trim(); | |
| if (isNaN(vid) || vid <= 0 || vid > 4094) { | |
| alert("Please enter a valid VLAN ID (1-4094)."); | |
| return; | |
| } | |
| if (!vlan_name) { | |
| alert("Please enter a VLAN name."); | |
| return; | |
| } | |
| try { | |
| // Step 1: Fetch existing VLANs | |
| const checkResponse = await fetch(`${BASE_URL}/vlans`); | |
| if (!checkResponse.ok) throw new Error("Failed to fetch existing VLANs."); | |
| const vlanList = await checkResponse.json(); // [{ vid, vlan_name }, ...] | |
| const existingVids = vlanList.map(v => v.vid); | |
| if (existingVids.includes(vid)) { | |
| alert(`VLAN ${vid} already exists.`); | |
| return; | |
| } | |
| // Step 2: Send create VLAN request | |
| const response = await fetch(`${BASE_URL}/create_vlan`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ vid, vlan_name }) | |
| }); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| const data = await response.json(); | |
| alert(data.message || "VLAN applied successfully."); | |
| fetchVlanData(); // Refresh the table | |
| } catch (error) { | |
| console.error("Error applying VLAN:", error); | |
| alert("Failed to apply VLAN configuration."); | |
| } | |
| } | |
| async function deleteVlanID() { | |
| const vlanList = document.getElementById("vlanList"); | |
| if (!vlanList || vlanList.selectedIndex === -1) { | |
| alert("Please select a VLAN to delete."); | |
| return; | |
| } | |
| const selectedOption = vlanList.options[vlanList.selectedIndex]; | |
| const vid = parseInt(selectedOption.value, 10); | |
| if (isNaN(vid)) { | |
| alert("Invalid VLAN ID selected."); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/destroy_vlan`, { | |
| method: "DELETE", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ vid }) | |
| }); | |
| if (!response.ok) { | |
| const errorData = await response.json(); | |
| throw new Error(errorData.message || `HTTP error! Status: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| alert(data.message || "VLAN deleted successfully."); | |
| fetchVlanData(); // Refresh VLAN list after deletion | |
| } catch (error) { | |
| console.error("Error deleting VLAN:", error); | |
| alert("Failed to delete VLAN configuration: " + error.message); | |
| } | |
| } | |
| ///VLAN PORT CONFIGURATION | |
| async function showVlanPortConfig() { | |
| showContent('<p>Loading VLAN Port Configuration...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/get-vlan-information`); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| await response.json(); // Optional: response not used | |
| const portOptions = Array.from({ length: 10 }, (_, i) => `<option value="${i + 1}">${i + 1}</option>`).join(''); | |
| showContent(` | |
| <h2>VLAN Port Configuration</h2> | |
| <p>(p=default VLAN member, t=tagged member, u=untagged member)</p> | |
| <div id="vlan-config" style="display: flex; gap: 30px; align-items: flex-start;"> | |
| <div class="column"> | |
| <strong>Port</strong><br/> | |
| <select style="width:100px;" id="portSelect" size="10">${portOptions}</select> | |
| </div> | |
| <div class="column"> | |
| <strong>Mode</strong><br/> | |
| <select style="width:100px;" id="modeSelect"> | |
| <option value="access">Access</option> | |
| <option value="Hybrid">Hybrid</option> | |
| <option value="trunk">Trunk</option> | |
| </select> | |
| </div> | |
| <div class="column"> | |
| <label>Current VLAN</label><br/> | |
| <select style="width:100px;" size="10" id="vlanSelect" multiple></select> | |
| </div> | |
| <div> | |
| <button id="btnDefault">Default VLAN =></button><br/> | |
| <button id="btnTagged">Tagged =></button><br/> | |
| <button id="btnUntagged">Untagged =></button><br/> | |
| <button id="btnUnmember">UnMember <=</button> | |
| </div> | |
| <div class="column"> | |
| <label>Port Members</label><br/> | |
| <select style="width:100px;" size="10" id="portMembers" multiple></select> | |
| </div> | |
| </div> | |
| <button id="btnRefresh">Refresh</button> | |
| <button onclick="alert('Help: Choose port, mode and VLAN. Then use buttons to apply.')">Help</button> | |
| `); | |
| // Local state encapsulated | |
| let currentVlanPort = ''; | |
| let membershipVlanMap = {}; | |
| const portSelect = document.getElementById('portSelect'); | |
| const modeSelect = document.getElementById('modeSelect'); | |
| const vlanSelect = document.getElementById('vlanSelect'); | |
| const portMembers = document.getElementById('portMembers'); | |
| const btnMap = { | |
| default: document.getElementById('btnDefault'), | |
| tag: document.getElementById('btnTagged'), | |
| untag: document.getElementById('btnUntagged'), | |
| unmember: document.getElementById('btnUnmember'), | |
| }; | |
| // Event listeners | |
| portSelect.addEventListener('change', async () => { | |
| currentVlanPort = portSelect.value; | |
| if (currentVlanPort) await updatePortMembers(); | |
| }); | |
| modeSelect.addEventListener('change', updateButtonStates); | |
| document.getElementById('btnRefresh').addEventListener('click', loadVlans); | |
| Object.entries(btnMap).forEach(([action, button]) => { | |
| button.addEventListener('click', async () => { | |
| const selectedVLANs = Array.from(vlanSelect.selectedOptions).map(opt => opt.value); | |
| const mode = modeSelect.value; | |
| const currentVLANs = Object.keys(membershipVlanMap); | |
| if (!selectedVLANs.length) { | |
| alert("Select at least one VLAN."); | |
| return; | |
| } | |
| if (mode === 'access') { | |
| selectedVLANs.splice(1); | |
| } else if (mode === 'Hybrid') { | |
| const newVLANs = selectedVLANs.filter(v => !currentVLANs.includes(v)); | |
| const total = currentVLANs.length + newVLANs.length; | |
| if (total > 2) { | |
| const vlansToRemove = currentVLANs.slice(0, total - 2); | |
| for (const vlan of vlansToRemove) { | |
| await sendVLANUpdate(vlan, 'unmember'); | |
| delete membershipVlanMap[vlan]; | |
| } | |
| } | |
| } | |
| for (const vlan of selectedVLANs) { | |
| await sendVLANUpdate(vlan, action); | |
| } | |
| await updatePortMembers(); | |
| }); | |
| }); | |
| // Internal helpers | |
| async function loadVlans() { | |
| const res = await fetch(`${BASE_URL}/vlans`); | |
| const vlans = await res.json(); | |
| vlanSelect.innerHTML = ''; | |
| vlans.forEach(v => { | |
| const opt = document.createElement('option'); | |
| opt.value = v.vid; | |
| opt.text = v.vid; | |
| vlanSelect.appendChild(opt); | |
| }); | |
| } | |
| async function updatePortMembers() { | |
| const res = await fetch(`${BASE_URL}/get_vlan_ports/${currentVlanPort}`); | |
| membershipVlanMap = await res.json(); | |
| portMembers.innerHTML = ''; | |
| Object.entries(membershipVlanMap).forEach(([vlan, type]) => { | |
| const opt = document.createElement('option'); | |
| opt.value = vlan; | |
| opt.textContent = `[${type}] ${vlan}`; | |
| portMembers.appendChild(opt); | |
| }); | |
| updateButtonStates(); | |
| } | |
| function updateButtonStates() { | |
| const isAccess = modeSelect.value === 'access'; | |
| btnMap.tag.disabled = isAccess; | |
| btnMap.untag.disabled = isAccess; | |
| btnMap.unmember.disabled = isAccess; | |
| } | |
| async function sendVLANUpdate(vlan, action) { | |
| const res = await fetch(`${BASE_URL}/set_vlan_port`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| port: currentVlanPort, | |
| vlan, | |
| mode: modeSelect.value, | |
| action | |
| }) | |
| }); | |
| if (!res.ok) { | |
| console.error(`Failed to update VLAN ${vlan}: ${res.status}`); | |
| return; | |
| } | |
| const result = await res.json(); | |
| membershipVlanMap = result.members || membershipVlanMap; | |
| } | |
| // Initial load | |
| await loadVlans(); | |
| portSelect.value = ''; | |
| } catch (err) { | |
| console.error("Error loading VLAN config:", err); | |
| alert("Failed to load VLAN Port Configuration."); | |
| } | |
| } | |
| /////IP ADRESS CONFIGURATION | |
| async function ipAddressConfig() { | |
| showContent('<p>Loading IP Address Configuration...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/get_vlan_interfaces`); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| let vlanInterfaces = await response.json(); // array of { vlanId, ipAddress, dhcpEnabled, macAddress } | |
| // Build Line Item dropdown: New + indexes 1, 2, 3... | |
| let optionsHtml = `<option value="new">New</option>`; | |
| for (let i = 0; i < vlanInterfaces.length; i++) { | |
| optionsHtml += `<option value="${i}">${i + 1}</option>`; | |
| } | |
| showContent(` | |
| <h2>IP Address Configuration</h2> | |
| <table id="configTable"> | |
| <thead> | |
| <tr> | |
| <th>Line Item</th> | |
| <th>VLAN ID</th> | |
| <th>IP Address / Subnet Prefix</th> | |
| <th>DHCP Client</th> | |
| <th>MAC Address</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td> | |
| <select id="lineItemSelect" style="width: 60%;">${optionsHtml}</select> | |
| </td> | |
| <td><input type="number" id="vlanIdInput" value="0" min="1"></td> | |
| <td><input type="text" id="ipAddressInput" placeholder="e.g. 192.168.0.1/24" style="width: 50%;"></td> | |
| <td> | |
| <select id="dhcpSelect" style="width: 55%;"> | |
| <option value="Disable">Disable</option> | |
| <option value="Enable">Enable</option> | |
| </select> | |
| </td> | |
| <td><input type="text" id="macAddressInput" disabled></td> | |
| </tr> | |
| </tbody> | |
| <tbody id="interfacesTableBody"> | |
| <!-- Filled dynamically --> | |
| </tbody> | |
| </table> | |
| <br /> | |
| <button onclick="createVlan()" id="applyBtn">Create VLAN Interface</button> | |
| <button onclick="deleteVlan()" id="deleteBtn">Delete VLAN Interface</button> | |
| <button onclick="setIpDhcp()" id="applyBtn">Set IP Address/DHCP Client</button> | |
| <button onclick="deleteIpAddress()" id="deleteBtn">Delete IP Address</button> | |
| <button onclick="ipAddressConfig()" id="refreshBtn">Refresh</button> | |
| `); | |
| const lineItemSelect = document.getElementById('lineItemSelect'); | |
| const vlanIdInput = document.getElementById('vlanIdInput'); | |
| const ipAddressInput = document.getElementById('ipAddressInput'); | |
| const dhcpSelect = document.getElementById('dhcpSelect'); | |
| const macAddressInput = document.getElementById('macAddressInput'); | |
| function renderInterfaceTable(interfaces) { | |
| const tbody = document.getElementById('interfacesTableBody'); | |
| tbody.innerHTML = ''; | |
| interfaces.forEach(({ vlanId, ipAddress, dhcpEnabled, macAddress }, index) => { | |
| const row = document.createElement('tr'); | |
| row.innerHTML = ` | |
| <td>${index + 1}</td> | |
| <td>${vlanId}</td> | |
| <td>${ipAddress || '-'}</td> | |
| <td>${dhcpEnabled ? 'Yes' : 'No'}</td> | |
| <td>${macAddress || '-'}</td> | |
| `; | |
| tbody.appendChild(row); | |
| }); | |
| } | |
| renderInterfaceTable(vlanInterfaces); | |
| dhcpSelect.addEventListener('change', () => { | |
| if (dhcpSelect.value === 'Enable') { | |
| ipAddressInput.disabled = true; | |
| ipAddressInput.value = ''; | |
| ipAddressInput.placeholder = 'DHCP enabled'; | |
| } else { | |
| ipAddressInput.disabled = false; | |
| ipAddressInput.placeholder = 'e.g. 192.168.0.1/24'; | |
| } | |
| }); | |
| lineItemSelect.addEventListener('change', () => { | |
| const selectedIndex = lineItemSelect.value; | |
| if (selectedIndex === 'new') { | |
| vlanIdInput.value = 0; | |
| ipAddressInput.value = ''; | |
| dhcpSelect.value = 'Disable'; | |
| ipAddressInput.disabled = false; | |
| macAddressInput.value = '-'; | |
| } else { | |
| // Use selected index to get VLAN data | |
| const vlan = vlanInterfaces[selectedIndex]; | |
| if (vlan) { | |
| vlanIdInput.value = vlan.vlanId; | |
| dhcpSelect.value = vlan.dhcpEnabled ? 'Enable' : 'Disable'; | |
| ipAddressInput.disabled = vlan.dhcpEnabled; | |
| ipAddressInput.value = vlan.dhcpEnabled ? '' : vlan.ipAddress; | |
| macAddressInput.value = vlan.macAddress || '-'; | |
| } | |
| } | |
| }); | |
| vlanIdInput.addEventListener('blur', async () => { | |
| const vlanId = parseInt(vlanIdInput.value, 10); | |
| if (isNaN(vlanId) || vlanId <= 0) return; | |
| try { | |
| const response = await fetch(`${BASE_URL}/vlans`); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| const vlanList = await response.json(); // [{ vid, vlan_name }, ...] | |
| const vlanIds = vlanList.map(v => v.vid); // extract only vid | |
| if (!vlanIds.includes(vlanId)) { | |
| alert(`VLAN ${vlanId} does not exist.`); | |
| // vlanIdInput.focus(); | |
| } | |
| } catch (error) { | |
| console.error('Error checking VLAN existence:', error); | |
| alert('Failed to verify VLAN existence. Please try again.'); | |
| } | |
| }); | |
| window.refreshTable = async () => { | |
| try { | |
| const response = await fetch(`${BASE_URL}/get_vlan_interfaces`); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| vlanInterfaces = await response.json(); | |
| renderInterfaceTable(vlanInterfaces); | |
| // Update line item dropdown options | |
| let optionsHtml = `<option value="new">New</option>`; | |
| for (let i = 0; i < vlanInterfaces.length; i++) { | |
| optionsHtml += `<option value="${i}">${i + 1}</option>`; | |
| } | |
| lineItemSelect.innerHTML = optionsHtml; | |
| lineItemSelect.value = 'new'; | |
| } catch (error) { | |
| console.error("Error refreshing VLAN interfaces:", error); | |
| alert('Failed to refresh VLAN interfaces.'); | |
| } | |
| }; | |
| lineItemSelect.dispatchEvent(new Event('change')); | |
| } catch (error) { | |
| console.error("Error fetching IP ADDRESS configuration:", error); | |
| showContent('<p style="color:red;">Failed to load IP ADDRESS configuration.</p>'); | |
| } | |
| } | |
| function isValidIpWithCidr(ip) { | |
| const regex = /^((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\/(\d|[1-2]\d|3[0-2])$/; | |
| return regex.test(ip); | |
| } | |
| function isValidIp(ip) { | |
| const ipRegex = /^(\d{1,3}\.){3}\d{1,3}$/; | |
| if (!ipRegex.test(ip)) return false; | |
| return ip.split('.').every(octet => parseInt(octet) >= 0 && parseInt(octet) <= 255); | |
| } | |
| function isValidMac(mac) { | |
| const macRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/; | |
| return macRegex.test(mac); | |
| } | |
| function isValidCIDR(cidr) { | |
| const regex = /^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/; | |
| if (!regex.test(cidr)) return false; | |
| const [ip, prefix] = cidr.split('/'); | |
| const octets = ip.split('.').map(Number); | |
| const prefixNum = Number(prefix); | |
| return octets.every(octet => octet >= 0 && octet <= 255) && prefixNum >= 0 && prefixNum <= 32; | |
| } | |
| function isValidIP(ip) { | |
| const regex = /^(\d{1,3}\.){3}\d{1,3}$/; | |
| if (!regex.test(ip)) return false; | |
| return ip.split('.').every(octet => { | |
| const val = Number(octet); | |
| return val >= 0 && val <= 255; | |
| }); | |
| } | |
| async function createVlan() { | |
| const vlanId = parseInt(document.getElementById('vlanIdInput').value, 10); | |
| const ipAddress = document.getElementById('ipAddressInput').value.trim(); | |
| const dhcpEnabled = document.getElementById('dhcpSelect').value === 'Enable'; | |
| if (isNaN(vlanId) || vlanId <= 0) { | |
| alert('Please enter a valid VLAN ID (> 0).'); | |
| return; | |
| } | |
| if (!dhcpEnabled) { | |
| if (ipAddress === '') { | |
| alert('Please enter an IP address or enable DHCP.'); | |
| return; | |
| } | |
| if (!isValidIpWithCidr(ipAddress)) { | |
| alert('Invalid IP address format. Use format: x.x.x.x/xx'); | |
| return; | |
| } | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/create_vlan_interface`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ vlanId, ipAddress, dhcpEnabled }) | |
| }); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| alert(`VLAN Interface ${vlanId} created successfully.`); | |
| refreshTable(); | |
| } catch (error) { | |
| console.error("Error creating VLAN:", error); | |
| alert("Interface overlap or conflict. Please check the VLAN ID and IP address."); | |
| } | |
| } | |
| async function setIpDhcp() { | |
| const vlanId = parseInt(document.getElementById('vlanIdInput').value, 10); | |
| const ipAddress = document.getElementById('ipAddressInput').value.trim(); | |
| const dhcpEnabled = document.getElementById('dhcpSelect').value === 'Enable'; | |
| if (isNaN(vlanId) || vlanId <= 0) { | |
| alert('Please select a valid VLAN ID.'); | |
| return; | |
| } | |
| if (!dhcpEnabled) { | |
| if (ipAddress === '') { | |
| alert('Please enter an IP address or enable DHCP.'); | |
| return; | |
| } | |
| if (!isValidIpWithCidr(ipAddress)) { | |
| alert('Invalid IP address format. Use format: x.x.x.x/xx'); | |
| return; | |
| } | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/update_vlan_interface`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ vlanId, ipAddress, dhcpEnabled }) | |
| }); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| alert(`IP Address/DHCP settings updated for VLAN ${vlanId}.`); | |
| refreshTable(); | |
| } catch (error) { | |
| console.error("Error setting IP/DHCP:", error); | |
| alert("Failed to update IP/DHCP settings. IP conflict or interface error."); | |
| } | |
| } | |
| async function deleteVlan() { | |
| const vlanId = parseInt(document.getElementById('vlanIdInput').value, 10); | |
| if (isNaN(vlanId) || vlanId <= 0) { | |
| alert('Please select a valid VLAN ID to delete.'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/destroy_vlan_interface`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ vlanId }) | |
| }); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| alert(`VLAN Interface ${vlanId} deleted successfully.`); | |
| refreshTable(); | |
| } catch (error) { | |
| console.error("Error deleting VLAN:", error); | |
| alert("Failed to delete VLAN."); | |
| } | |
| } | |
| async function deleteIpAddress() { | |
| const vlanId = parseInt(document.getElementById('vlanIdInput').value, 10); | |
| const ipAddress = document.getElementById('ipAddressInput').value.trim(); | |
| if (isNaN(vlanId) || vlanId <= 0) { | |
| alert('Please select a valid VLAN ID.'); | |
| return; | |
| } | |
| if (ipAddress === '') { | |
| alert('Please enter the IP address to delete.'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/delete_vlanIp`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ vlanId, ipAddress }) | |
| }); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| alert(`IP Address ${ipAddress} deleted from VLAN ${vlanId}.`); | |
| refreshTable(); | |
| } catch (error) { | |
| console.error("Error deleting IP address:", error); | |
| alert("Failed to delete IP address."); | |
| } | |
| } | |
| ////////ARP CONFIG | |
| async function arpConfigandDisplay() { | |
| showContent('<p>Loading ARP Configure And Display...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/arp-config-display`); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| const data = await response.json(); | |
| showContent(` | |
| <h2 style="color:red; text-align:center;">ARP Configure And Display</h2> | |
| <!-- Static ARP Item Configuration --> | |
| <div class="section"> | |
| <div class="form-row" style="display: flex; align-items: center; justify-content: center; gap: 15px;"> | |
| <label for="addIp">IP Address:</label> | |
| <input type="text" id="addIp" placeholder="192.168.0.113" style="width: 200px;" /> | |
| <label for="addMac">MAC Address:</label> | |
| <input type="text" id="addMac" placeholder="30:24:a9:a5:18:83" style="width: 200px;" /> | |
| </div> | |
| <div style="text-align: center; margin: 10px 0;"> | |
| <button onclick="addArp()" id ="applyBtn">Add</button> | |
| </div> | |
| </div> | |
| <!-- Delete ARP Item --> | |
| <div class="section"> | |
| <div class="form-row" style="display: flex; align-items: center; justify-content: center; gap: 15px;"> | |
| <label for="deleteIp">ARP Item:</label> | |
| <select id="deleteIp" style="width: 200px;"><option value=""></option></select> | |
| <label for="deleteIpSegment">IP Address (Segment):</label> | |
| <input type="text" id="deleteIpSegment" placeholder="192.168.0.113" style="width: 200px;" /> | |
| </div> | |
| <div style="text-align: center; margin: 10px 0;"> | |
| <button onclick="deleteArp()"id ="deleteBtn">Delete</button> | |
| </div> | |
| </div> | |
| <!-- Convert Dynamic ARP to Static --> | |
| <div class="section"> | |
| <div class="form-row" style="display: flex; align-items: center; justify-content: center; gap: 15px;"> | |
| <label for="convertIp">ARP List Item:</label> | |
| <select id="convertIp" style="width: 200px;"><option value=""></option></select> | |
| <label for="convertIpSegment">IP Address (Segment):</label> | |
| <input type="text" id="convertIpSegment" placeholder="192.168.0.113" style="width: 200px;" /> | |
| </div> | |
| <div style="text-align: center; margin: 10px 0;"> | |
| <button onclick="convertToStatic()" id ="applyBtn">Apply</button> | |
| </div> | |
| </div> | |
| <!-- ARP Table --> | |
| <div class="section"> | |
| <table id="arpTable" style="width: 50%; border-collapse: collapse; margin: 0 auto;"> | |
| <thead style="background-color: #d0e4f5;"> | |
| <tr> | |
| <th style="padding: 8px; border: 1px solid #ccc;">IP Address</th> | |
| <th style="padding: 8px; border: 1px solid #ccc;">MAC Address</th> | |
| <th style="padding: 8px; border: 1px solid #ccc;">Type</th> | |
| </tr> | |
| </thead> | |
| <tbody id="arpTableBody"></tbody> | |
| </table> | |
| <div style="margin-top: 15px; text-align: center;"> | |
| <button id="refreshBtn" onclick="refreshArpTable()">Refresh</button> | |
| <button id="helpBtn" onclick="alert('Contact admin for help.')">Help</button> | |
| </div> | |
| </div> | |
| `); | |
| populateDropdowns(); | |
| arp = data; | |
| updateArpTable(); | |
| } catch (error) { | |
| console.error("Error fetching ARP table:", error); | |
| showContent('<p style="color:red;">Failed to load ARP table.</p>'); | |
| } | |
| } | |
| let arp = []; | |
| function updateArpTable() { | |
| const tableBody = document.getElementById('arpTableBody'); | |
| tableBody.innerHTML = ''; | |
| arp.forEach((arp, index) => { | |
| const row = document.createElement('tr'); | |
| row.innerHTML = ` | |
| <td>${arp.ipAddress}</td> | |
| <td>${arp.macAddress}</td> | |
| <td>${arp.type}</td> | |
| `; | |
| tableBody.appendChild(row); | |
| }); | |
| } | |
| function addArp() { | |
| const ip = document.getElementById('addIp').value; | |
| const mac = document.getElementById('addMac').value; | |
| if (!isValidIp(ip) || !isValidMac(mac)) { | |
| alert('Invalid IP or MAC address format.'); | |
| return; | |
| } | |
| fetch(`${BASE_URL}/arp-config-display`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ ipAddress: ip, macAddress: mac, type: 'static' }) | |
| }) | |
| .then(res => res.json()) | |
| .then(() => refreshArpTable()); | |
| } | |
| function deleteArp() { | |
| const category = document.getElementById('deleteIp').value; | |
| const ipSegment = document.getElementById('deleteIpSegment').value; | |
| if (!category || !isValidIp(ipSegment)) { | |
| alert('Please select a valid ARP category or enter a valid IP segment.'); | |
| return; | |
| } | |
| fetch(`${BASE_URL}/arp-config-display?category=${category}&segment=${ipSegment}`, { | |
| method: 'DELETE' | |
| }) | |
| .then(res => res.json()) | |
| .then(() => refreshArpTable()); | |
| } | |
| function convertToStatic() { | |
| const category = document.getElementById('convertIp').value; | |
| const ipSegment = document.getElementById('convertIpSegment').value; | |
| if (!category || !isValidIp(ipSegment)) { | |
| alert('Please select a valid ARP category or enter a valid IP segment.'); | |
| return; | |
| } | |
| fetch(`${BASE_URL}/arp-config-display/convert?category=${category}&segment=${ipSegment}`, { | |
| method: 'PUT' | |
| }) | |
| .then(res => res.json()) | |
| .then(() => refreshArpTable()); | |
| } | |
| function refreshArpTable() { | |
| arpConfigandDisplay(); | |
| } | |
| function populateDropdowns() { | |
| const deleteDropdown = document.getElementById('deleteIp'); | |
| const convertDropdown = document.getElementById('convertIp'); | |
| const categories = ['A.B.C.D/M', 'all', 'dynamic', 'static']; | |
| categories.forEach(category => { | |
| deleteDropdown.innerHTML += `<option value="${category}">${category}</option>`; | |
| if (category !== 'static' && category !== 'all') { | |
| convertDropdown.innerHTML += `<option value="${category}">${category}</option>`; | |
| } | |
| }); | |
| } | |
| ////////////////////////////////////HOSTSTATIC ROUTE | |
| async function hostStaticRoute() { | |
| showContent('<p>Loading Host Static Route...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/host-static-route`); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| const routeData = await response.json(); | |
| showContent(` | |
| <h2>Host Static Route Configuration</h2> | |
| <div style="margin-bottom: 10px;"> | |
| <label for="targetAddress">Target Address/Subnet prefix:</label> | |
| <input type="text" id="targetAddress" placeholder="e.g., 192.168.1.0/24"> | |
| <label for="nextHop">Next Hop:</label> | |
| <input type="text" id="nextHop" placeholder="e.g., 192.168.1.1"> | |
| <div style="margin-top: 10px;"> | |
| <button onclick="addRoute()" id ="applyBtn">Apply</button> | |
| <button onclick="refreshRoutes()" id ="refreshBtn">Refresh</button> | |
| <button onclick="deleteSelected()" id ="deleteBtn">Delete</button> | |
| <button onclick="help()" id ="helpBtn">Help</button> | |
| </div> | |
| </div> | |
| <table> | |
| <thead> | |
| <tr> | |
| <th><input type="checkbox" id="selectAll" onclick="toggleSelectAll()"></th> | |
| <th>Item</th> | |
| <th>Target Address/Subnet prefix</th> | |
| <th>Next Hop</th> | |
| <th>Distance</th> | |
| <th>State</th> | |
| </tr> | |
| </thead> | |
| <tbody id="routeTableBody"></tbody> | |
| </table> | |
| `); | |
| routes = routeData; | |
| updateRouteTable(); | |
| } catch (error) { | |
| console.error("Error fetching host static route data:", error); | |
| showContent('<p style="color:red;">Failed to load host static route data.</p>'); | |
| } | |
| } | |
| let routes = []; | |
| function updateRouteTable() { | |
| const tableBody = document.getElementById('routeTableBody'); | |
| tableBody.innerHTML = ''; | |
| routes.forEach((route, index) => { | |
| const row = document.createElement('tr'); | |
| row.innerHTML = ` | |
| <td><input type="checkbox" class="routeCheckbox" data-index="${index}"></td> | |
| <td>${index + 1}</td> | |
| <td>${route.destination || route.targetAddress || ''}</td> | |
| <td>${route.nextHop}</td> | |
| <td>${route.distance}</td> | |
| <td>${route.state}</td> | |
| `; | |
| tableBody.appendChild(row); | |
| }); | |
| } | |
| function toggleSelectAll() { | |
| const selectAll = document.getElementById('selectAll').checked; | |
| document.querySelectorAll('.routeCheckbox').forEach(cb => cb.checked = selectAll); | |
| } | |
| function refreshRoutes() { | |
| hostStaticRoute(); | |
| } | |
| async function addRoute() { | |
| const targetAddress = document.getElementById('targetAddress').value.trim(); | |
| const nextHop = document.getElementById('nextHop').value.trim(); | |
| if (!targetAddress || !nextHop) { | |
| alert('Please fill in both fields.'); | |
| return; | |
| } | |
| if (!isValidCIDR(targetAddress)) { | |
| alert('Invalid CIDR format. Example: 192.168.1.0/24'); | |
| return; | |
| } | |
| if (!isValidIP(nextHop)) { | |
| alert('Invalid IP address format for Next Hop. Example: 192.168.1.1'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/host-static-route`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ targetAddress, nextHop }) | |
| }); | |
| const result = await response.json(); | |
| if (!response.ok) throw new Error(result.message); | |
| alert(result.message); | |
| refreshRoutes(); | |
| } catch (error) { | |
| alert(`Error adding route: ${error.message}`); | |
| } | |
| } | |
| async function deleteSelected() { | |
| const selectedRoutes = Array.from(document.querySelectorAll('.routeCheckbox:checked')) | |
| .map(cb => { | |
| const index = parseInt(cb.dataset.index, 10); | |
| const route = routes[index]; | |
| return route?.targetAddress || route?.destination || null; | |
| }) | |
| .filter(addr => addr !== null && typeof addr === 'string' && addr.trim() !== ''); | |
| if (selectedRoutes.length === 0) { | |
| alert("Please select valid route(s) to delete."); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/host-static-route`, { | |
| method: 'DELETE', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ targetAddresses: selectedRoutes }) | |
| }); | |
| const result = await response.json(); | |
| alert(result.message); | |
| refreshRoutes(); | |
| } catch (error) { | |
| alert(`Error deleting routes: ${error.message}`); | |
| } | |
| } | |
| function help() { | |
| alert('Static routes are used to manually define paths to specific network destinations.'); | |
| } | |
| ///MSTP | |
| // async function mstpGlobalConfig() { | |
| // showContent('<p>Loading MSTP Global Configuration...</p>'); | |
| // try { | |
| // const response = await fetch(`${BASE_URL}/mstp-global-config`); | |
| // if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| // const mstpData = await response.json(); // expects full config object | |
| // showContent(` | |
| // <h2>MSTP Global Configuration</h2> | |
| // <div class="info"> | |
| // <label for="mstp">MSTP</label> | |
| // <select id="mstp" name="mstp" style="width: 100px;"> | |
| // <option value="enabled" ${mstpData.mstp === 'enabled' ? 'selected' : ''}>Enabled</option> | |
| // <option value="disabled" ${mstpData.mstp === 'disabled' ? 'selected' : ''}>Disabled</option> | |
| // </select> | |
| // <br> | |
| // <label for="priority">Priority</label> | |
| // <input type="number" id="priority" value="${mstpData.priority}" style="width: 200px;"> | |
| // <br> | |
| // <label for="portfast_bpdu_filter">Portfast Bpdu-Filter</label> | |
| // <select id="portfast_bpdu_filter" name="portfast_bpdu_filter" style="width: 100px;"> | |
| // <option value="enabled" ${mstpData.portfastBpduFilter === 'enabled' ? 'selected' : ''}>Enabled</option> | |
| // <option value="disabled" ${mstpData.portfastBpduFilter === 'disabled' ? 'selected' : ''}>Disabled</option> | |
| // </select> | |
| // <br> | |
| // <label for="portfast_bpdu_guard">Portfast Bpdu-Guard</label> | |
| // <select id="portfast_bpdu_guard" name="portfast_bpdu_guard" style="width: 100px;"> | |
| // <option value="enabled" ${mstpData.portfastBpduGuard === 'enabled' ? 'selected' : ''}>Enabled</option> | |
| // <option value="disabled" ${mstpData.portfastBpduGuard === 'disabled' ? 'selected' : ''}>Disabled</option> | |
| // </select> | |
| // <br> | |
| // <label for="forwardTime">Forward Time (seconds):</label> | |
| // <input type="number" id="forwardTime" value="${mstpData.forwardTime}" min="1" > | |
| // <br> | |
| // <label for="helloTime">Hello Time (seconds):</label> | |
| // <input type="number" id="helloTime" value="${mstpData.helloTime}" min="1" max="10"> | |
| // <br> | |
| // <label for="errdisabledTimeoutSelect">Errdisable-Timeout</label> | |
| // <select id="errdisabledTimeoutSelect" name="errdisabledTimeoutSelect" style="width: 100px;"> | |
| // <option value="enabled" ${mstpData.errdisabledTimeout === 'enabled' ? 'selected' : ''}>Enabled</option> | |
| // <option value="disabled" ${mstpData.errdisabledTimeout === 'disabled' ? 'selected' : ''}>Disabled</option> | |
| // </select> | |
| // <br> | |
| // <label for="errdisabledTimeoutInterval">Errdisable-Timeout Interval</label> | |
| // <input type="number" id="errdisabledTimeoutInterval" value="${mstpData.errdisabledTimeoutInterval}" min="6" max="40"> | |
| // <br> | |
| // <label for="maxAge">Max Age</label> | |
| // <input type="number" id="maxAge" value="${mstpData.maxAge}" min="4" max="30"> | |
| // <br> | |
| // <label for="maxhops">Max Hops</label> | |
| // <input type="number" id="maxhops" value="${mstpData.maxHops}" min="4" max="30"> | |
| // <br> | |
| // <label for="ciscoInteroperability">Cisco-Interoperability</label> | |
| // <select id="ciscoInteroperability" name="ciscoInteroperability" style="width: 100px;"> | |
| // <option value="enabled" ${mstpData.ciscoInteroperability === 'enabled' ? 'selected' : ''}>Enabled</option> | |
| // <option value="disabled" ${mstpData.ciscoInteroperability === 'disabled' ? 'selected' : ''}>Disabled</option> | |
| // </select> | |
| // <br> | |
| // <button onclick="applyMstpConfig()" id="applyBtn">Apply</button> | |
| // <button onclick="refreshMstpConfig()" id="refreshBtn">Refresh</button> | |
| // <button onclick="showHelp()" id="helpBtn">Help</button> | |
| // </div> | |
| // `); | |
| // } catch (error) { | |
| // console.error("Error fetching MSTP global configuration:", error); | |
| // showContent('<p style="color:red;">Failed to load MSTP global configuration.</p>'); | |
| // } | |
| // } | |
| // function applyMstpConfig() { | |
| // const config = { | |
| // mstp: document.getElementById('mstp').value, | |
| // priority: parseInt(document.getElementById('priority').value), | |
| // portfastBpduFilter: document.getElementById('portfast_bpdu_filter').value, | |
| // portfastBpduGuard: document.getElementById('portfast_bpdu_guard').value, | |
| // forwardTime: parseInt(document.getElementById('forwardTime').value), | |
| // helloTime: parseInt(document.getElementById('helloTime').value), | |
| // errdisabledTimeout: document.getElementById('errdisabledTimeoutSelect').value, | |
| // errdisabledTimeoutInterval: parseInt(document.getElementById('errdisabledTimeoutInterval').value), | |
| // maxAge: parseInt(document.getElementById('maxAge').value), | |
| // maxHops: parseInt(document.getElementById('maxhops').value), | |
| // ciscoInteroperability: document.getElementById('ciscoInteroperability').value | |
| // }; | |
| // fetch(`${BASE_URL}/mstp-global-config`, { | |
| // method: 'POST', | |
| // headers: { 'Content-Type': 'application/json' }, | |
| // body: JSON.stringify(config) | |
| // }) | |
| // .then(response => { | |
| // if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| // return response.json(); | |
| // }) | |
| // .then(data => { | |
| // alert("MSTP configuration applied successfully!"); | |
| // }) | |
| // .catch(error => { | |
| // console.error("Error applying MSTP configuration:", error); | |
| // alert("Failed to apply MSTP configuration."); | |
| // }); | |
| // } | |
| // function refreshMstpConfig() { | |
| // mstpGlobalConfig(); | |
| // alert("MSTP global configuration refreshed."); | |
| // } | |
| // async function mstpPortConfig() { | |
| // showContent('<p>Loading MSTP Port Configuration...</p>'); | |
| // try { | |
| // const response = await fetch(`${BASE_URL}/mstp-port-config`); | |
| // if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| // const mstpPortData = await response.json(); | |
| // // Store globally for reference | |
| // window.mstpPortData = mstpPortData; | |
| // // Build form HTML (no table) | |
| // showContent(` | |
| // <h2>MSTP Port Configuration</h2> | |
| // <div class="info"> | |
| // <label for="portSelect">Port:</label> | |
| // <select style="width:150px;" id="portSelect" onchange="populatePortFields()"> | |
| // <option value="">Select a port</option> | |
| // ${mstpPortData.map(p => `<option value="${p.port}">Port ${p.port}</option>`).join('')} | |
| // </select><br> | |
| // <label for="portFast">Portfast:</label> | |
| // <select style="width:150px;" id="portFast"><option>Enabled</option><option>Disabled</option></select><br> | |
| // <label for="portfast_bpdu_filter">Portfast Bpdu-Filter:</label> | |
| // <select style="width:150px;" id="portfast_bpdu_filter"><option>Enabled</option><option>Disabled</option></select><br> | |
| // <label for="portfast_bpdu_guard">Portfast Bpdu-Guard:</label> | |
| // <select style="width:150px;" id="portfast_bpdu_guard"><option>Enabled</option><option>Disabled</option></select><br> | |
| // <label for="rootGuard">Root Guard:</label> | |
| // <select style="width:150px;" id="rootGuard"><option>Enabled</option><option>Disabled</option></select><br> | |
| // <label for="linkType">Link Type:</label> | |
| // <select style="width:150px;" id="linkType"><option>Shared</option><option>point-to-point</option></select><br> | |
| // <label for="priority">Priority:</label> | |
| // <input style="width:150px;" type="number" id="priority"><br> | |
| // <label for="pathCost">Path-Cost:</label> | |
| // <input style="width:150px;" type="number" id="pathCost"><br> | |
| // <label for="forceVersion">Force Version:</label> | |
| // <select style="width:150px;" id="forceVersion"> | |
| // <option value="STP">STP</option> | |
| // <option value="nonSupported">Non supported</option> | |
| // <option value="MSTP">MSTP</option> | |
| // <option value="RSTP">RSTP</option> | |
| // </select><br> | |
| // <button onclick="applyMstpPortConfig()" id="applyBtn">Apply</button> | |
| // <button onclick="refreshMstpPortConfig()" id="refreshBtn">Refresh</button> | |
| // <button onclick="showHelp()" id="helpBtn">Help</button> | |
| // </div> | |
| // `); | |
| // } catch (err) { | |
| // console.error("Error loading MSTP port config:", err); | |
| // showContent('<p style="color:red;">Failed to load MSTP port configuration.</p>'); | |
| // } | |
| // } | |
| // function populatePortFields() { | |
| // const selectedPort = document.getElementById('portSelect').value; | |
| // const portData = window.mstpPortData.find(p => p.port === selectedPort); | |
| // if (!portData) return; | |
| // document.getElementById('portFast').value = portData.portFast || "Disabled"; | |
| // document.getElementById('portfast_bpdu_filter').value = portData.portfastBpduFilter || "Disabled"; | |
| // document.getElementById('portfast_bpdu_guard').value = portData.portfastBpduGuard || "Disabled"; | |
| // document.getElementById('rootGuard').value = portData.rootGuard || "Disabled"; | |
| // document.getElementById('linkType').value = portData.linkType || "Shared"; | |
| // document.getElementById('priority').value = portData.priority || 0; | |
| // document.getElementById('pathCost').value = portData.pathCost || 0; | |
| // document.getElementById('forceVersion').value = portData.forceVersion || "MSTP"; | |
| // } | |
| // async function applyMstpPortConfig() { | |
| // const selectedPort = document.getElementById('portSelect').value; | |
| // if (!selectedPort) { | |
| // alert('Please select a port before applying configuration.'); | |
| // return; | |
| // } | |
| // // Gather form data | |
| // const data = { | |
| // port: selectedPort, | |
| // portFast: document.getElementById('portFast').value, | |
| // portfastBpduFilter: document.getElementById('portfast_bpdu_filter').value, | |
| // portfastBpduGuard: document.getElementById('portfast_bpdu_guard').value, | |
| // rootGuard: document.getElementById('rootGuard').value, | |
| // linkType: document.getElementById('linkType').value, | |
| // priority: Number(document.getElementById('priority').value), | |
| // pathCost: Number(document.getElementById('pathCost').value), | |
| // forceVersion: document.getElementById('forceVersion').value | |
| // }; | |
| // try { | |
| // const response = await fetch(`${BASE_URL}/mstp-port-config`, { | |
| // method: 'POST', | |
| // headers: { | |
| // 'Content-Type': 'application/json' | |
| // }, | |
| // body: JSON.stringify(data) | |
| // }); | |
| // if (!response.ok) { | |
| // const errText = await response.text(); | |
| // throw new Error(`Server responded with status ${response.status}: ${errText}`); | |
| // } | |
| // alert('MSTP Port configuration applied successfully.'); | |
| // // Optionally refresh the config | |
| // refreshMstpPortConfig(); | |
| // } catch (err) { | |
| // console.error('Error applying MSTP port config:', err); | |
| // alert('Failed to apply MSTP port configuration. See console for details.'); | |
| // } | |
| // } | |
| // function refreshMstpPortConfig() { | |
| // mstpPortConfig(); | |
| // alert("MSTP port configuration refreshed."); | |
| // } | |
| // function populateMstpPortDropdown(mstpPortData) { | |
| // const portSelect = document.getElementById('portSelect'); | |
| // portSelect.innerHTML = ""; // Clear existing options | |
| // mstpPortData.forEach(port => { | |
| // const option = document.createElement('option'); | |
| // option.value = port.port; | |
| // option.textContent = port.port; | |
| // portSelect.appendChild(option); | |
| // }); | |
| // } | |
| // async function mstpPortInformation() { | |
| // showContent('<p>Loading MSTP Port Information...</p>'); | |
| // try { | |
| // const response = await fetch(`${BASE_URL}/mstp-port-information`); | |
| // if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| // const mstpPortInfo = await response.json(); // expects [{ port: ' 1', state: 'forwarding', role: 'root' }, ...] | |
| // showContent(` | |
| // <h2>MSTP Port Information</h2> | |
| // <table id="mstpPortInfoTable"> | |
| // <thead> | |
| // <tr> | |
| // <th>Port</th> | |
| // <th>Portfast</th> | |
| // <th>Bpdu-filter</th> | |
| // <th>Bpdu-Gaurd</th> | |
| // <th>Root Gaurd</th> | |
| // <th>Link-Type</th> | |
| // <th>Priority</th> | |
| // <th>Path-Cost</th> | |
| // <th>Force-version</th> | |
| // </tr> | |
| // </thead> | |
| // <tbody id="mstpPortInfoTableBody"> | |
| // <!-- Dynamic rows will be added here --> | |
| // </tbody> | |
| // </table> | |
| // <button onclick="mstpPortInformation()">Refresh</button> | |
| // <button onclick="showHelp()">Help</button> | |
| // `); | |
| // populateMstpPortInfoTable(mstpPortInfo); | |
| // } catch (error) { | |
| // console.error("Error fetching MSTP port information:", error); | |
| // showContent('<p style="color:red;">Failed to load MSTP port information.</p>'); | |
| // } | |
| // } | |
| // function populateMstpPortInfoTable(mstpPortInfo) { | |
| // const tableBody = document.getElementById('mstpPortInfoTableBody'); | |
| // fetch(`${BASE_URL}/mstp-port-information`, { | |
| // method: 'GET', | |
| // headers: { 'Content-Type': 'application/json' } | |
| // }) | |
| // .then(response => response.json()) | |
| // .then(data => { | |
| // mstpPortInfo.forEach(port => { | |
| // const row = `<tr> | |
| // <td>${port.port}</td> | |
| // <td>${port.portFast}</td> | |
| // <td>${port.portfastBpduFilter}</td> | |
| // <td>${port.portfastBpduGuard}</td> | |
| // <td>${port.rootGuard}</td> | |
| // <td>${port.linkType}</td> | |
| // <td>${port.priority}</td> | |
| // <td>${port.pathCost}</td> | |
| // <td>${port.forceVersion}</td> | |
| // </tr>`; | |
| // tableBody.insertAdjacentHTML('beforeend', row); | |
| // }); | |
| // }) | |
| // .catch(error => { | |
| // console.error("Error populating MSTP port information table:", error); | |
| // alert("Failed to populate MSTP port information table."); | |
| // }); | |
| // } | |
| //MSTP | |
| async function mstpGlobalConfig() { | |
| showContent('<p>Loading MSTP Global Configuration...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/mstp-global-config`); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| const mstpData = await response.json(); | |
| showContent(` | |
| <style> | |
| .info { display: flex; flex-direction: column; align-items: center; gap: 10px; } | |
| .form-row { display: flex; align-items: center; gap: 10px; width: 100%; max-width: 600px; } | |
| .form-row label { width: 200px; text-align: right; } | |
| .form-row input, .form-row select { flex: 1; max-width: 200px; } | |
| .button-group { display: flex; gap: 10px; justify-content: center; } | |
| </style> | |
| <h2 style="text-align: center;">MSTP Global Configuration</h2> | |
| <div class="info"> | |
| <div class="form-row"> | |
| <label for="mstp">MSTP</label> | |
| <select id="mstp" name="mstp"> | |
| <option value="enabled" ${mstpData.mstp === 'enabled' ? 'selected' : ''}>Enabled</option> | |
| <option value="disabled" ${mstpData.mstp === 'disabled' ? 'selected' : ''}>Disabled</option> | |
| </select> | |
| </div> | |
| <div class="form-row"> | |
| <label for="priority">Priority</label> | |
| <input type="number" id="priority" value="${mstpData.priority}" min="0" max="61440" step="4096"> | |
| </div> | |
| <div class="form-row"> | |
| <label for="portfast_bpdu_filter">Portfast Bpdu-Filter</label> | |
| <select id="portfast_bpdu_filter" name="portfast_bpdu_filter"> | |
| <option value="enabled" ${mstpData.portfastBpduFilter === 'enabled' ? 'selected' : ''}>Enabled</option> | |
| <option value="disabled" ${mstpData.portfastBpduFilter === 'disabled' ? 'selected' : ''}>Disabled</option> | |
| </select> | |
| </div> | |
| <div class="form-row"> | |
| <label for="portfast_bpdu_guard">Portfast Bpdu-Guard</label> | |
| <select id="portfast_bpdu_guard" name="portfast_bpdu_guard"> | |
| <option value="enabled" ${mstpData.portfastBpduGuard === 'enabled' ? 'selected' : ''}>Enabled</option> | |
| <option value="disabled" ${mstpData.portfastBpduGuard === 'disabled' ? 'selected' : ''}>Disabled</option> | |
| </select> | |
| </div> | |
| <div class="form-row"> | |
| <label for="forwardTime">Forward Time (seconds)</label> | |
| <input type="number" id="forwardTime" value="${mstpData.forwardTime}" min="1"> | |
| </div> | |
| <div class="form-row"> | |
| <label for="helloTime">Hello Time (seconds)</label> | |
| <input type="number" id="helloTime" value="${mstpData.helloTime}" min="1" max="10"> | |
| </div> | |
| <div class="form-row"> | |
| <label for="errdisabledTimeout">Errdisable-Timeout</label> | |
| <input type="number" id="errdisabledTimeout" value="${mstpData.errdisabledTimeout}" min="6" max="40"> | |
| </div> | |
| <div class="form-row"> | |
| <label for="maxAge">Max Age</label> | |
| <input type="number" id="maxAge" value="${mstpData.maxAge}" min="4" max="30"> | |
| </div> | |
| <div class="form-row"> | |
| <label for="maxHops">Max Hops</label> | |
| <input type="number" id="maxHops" value="${mstpData.maxHops}" min="4" max="30"> | |
| </div> | |
| <div class="form-row"> | |
| <label for="ciscoInteroperability">Cisco-Interoperability</label> | |
| <select id="ciscoInteroperability" name="ciscoInteroperability"> | |
| <option value="enabled" ${mstpData.ciscoInteroperability === 'enabled' ? 'selected' : ''}>Enabled</option> | |
| <option value="disabled" ${mstpData.ciscoInteroperability === 'disabled' ? 'selected' : ''}>Disabled</option> | |
| </select> | |
| </div> | |
| <div class="button-group"> | |
| <button onclick="applyMstpConfig()" id="applyBtn">Apply</button> | |
| <button onclick="refreshMstpConfig()" id="refreshBtn">Refresh</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| </div> | |
| `); | |
| } catch (error) { | |
| console.error("Error fetching MSTP global configuration:", error); | |
| showContent('<p style="color:red;">Failed to load MSTP global configuration.</p>'); | |
| } | |
| } | |
| function applyMstpConfig() { | |
| const config = { | |
| mstp: document.getElementById('mstp').value, | |
| priority: parseInt(document.getElementById('priority').value), | |
| portfastBpduFilter: document.getElementById('portfast_bpdu_filter').value, | |
| portfastBpduGuard: document.getElementById('portfast_bpdu_guard').value, | |
| forwardTime: parseInt(document.getElementById('forwardTime').value), | |
| helloTime: parseInt(document.getElementById('helloTime').value), | |
| errdisabledTimeout: parseInt(document.getElementById('errdisabledTimeout').value), | |
| maxAge: parseInt(document.getElementById('maxAge').value), | |
| maxHops: parseInt(document.getElementById('maxHops').value), | |
| ciscoInteroperability: document.getElementById('ciscoInteroperability').value | |
| }; | |
| const validBooleans = ["enabled", "disabled"]; | |
| if ( | |
| !validBooleans.includes(config.mstp) || | |
| !Number.isInteger(config.priority) || config.priority < 0 || config.priority > 61440 || | |
| !validBooleans.includes(config.portfastBpduFilter) || | |
| !validBooleans.includes(config.portfastBpduGuard) || | |
| !Number.isInteger(config.forwardTime) || config.forwardTime < 1 || | |
| !Number.isInteger(config.helloTime) || config.helloTime < 1 || config.helloTime > 10 || | |
| !Number.isInteger(config.errdisabledTimeout) || config.errdisabledTimeout < 6 || config.errdisabledTimeout > 40 || | |
| !Number.isInteger(config.maxAge) || config.maxAge < 4 || config.maxAge > 30 || | |
| !Number.isInteger(config.maxHops) || config.maxHops < 4 || config.maxHops > 30 || | |
| !validBooleans.includes(config.ciscoInteroperability) | |
| ) { | |
| alert("Invalid configuration parameters. Check input values."); | |
| return; | |
| } | |
| fetch(`${BASE_URL}/mstp-global-config`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(config) | |
| }) | |
| .then(response => { | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| return response.json(); | |
| }) | |
| .then(data => { | |
| alert("MSTP configuration applied successfully!"); | |
| }) | |
| .catch(error => { | |
| console.error("Error applying MSTP configuration:", error); | |
| alert("Failed to apply MSTP configuration."); | |
| }); | |
| } | |
| function refreshMstpConfig() { | |
| mstpGlobalConfig(); | |
| alert("MSTP global configuration refreshed."); | |
| } | |
| async function mstpPortConfig() { | |
| showContent('<p>Loading MSTP Port Configuration...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/mstp-port-config`); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| const mstpPortData = await response.json(); | |
| window.mstpPortData = mstpPortData; | |
| const ports = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // Always show 10 ports | |
| showContent(` | |
| <style> | |
| .info { display: flex; flex-direction: column; align-items: center; gap: 10px; } | |
| .form-row { display: flex; align-items: center; gap: 10px; width: 100%; max-width: 600px; } | |
| .form-row label { width: 200px; text-align: right; } | |
| .form-row input, .form-row select { flex: 1; max-width: 200px; } | |
| .button-group { display: flex; gap: 10px; justify-content: center; } | |
| </style> | |
| <h2 style="text-align: center;">MSTP Port Configuration</h2> | |
| <div class="info"> | |
| <div class="form-row"> | |
| <label for="portSelect">Port:</label> | |
| <select id="portSelect" onchange="populatePortFields()"> | |
| <option value="">Select a port</option> | |
| ${ports.map(p => `<option value="${p}">Port ${p}</option>`).join('')} | |
| </select> | |
| </div> | |
| <div class="form-row"> | |
| <label for="portFast">Portfast:</label> | |
| <select id="portFast"> | |
| <option value="Enabled">Enabled</option> | |
| <option value="Disabled">Disabled</option> | |
| </select> | |
| </div> | |
| <div class="form-row"> | |
| <label for="portfast_bpdu_filter">Portfast Bpdu-Filter:</label> | |
| <select id="portfast_bpdu_filter"> | |
| <option value="Enabled">Enabled</option> | |
| <option value="Disabled">Disabled</option> | |
| </select> | |
| </div> | |
| <div class="form-row"> | |
| <label for="portfast_bpdu_guard">Portfast Bpdu-Guard:</label> | |
| <select id="portfast_bpdu_guard"> | |
| <option value="Enabled">Enabled</option> | |
| <option value="Disabled">Disabled</option> | |
| </select> | |
| </div> | |
| <div class="form-row"> | |
| <label for="rootGuard">Root Guard:</label> | |
| <select id="rootGuard"> | |
| <option value="Enabled">Enabled</option> | |
| <option value="Disabled">Disabled</option> | |
| </select> | |
| </div> | |
| <div class="form-row"> | |
| <label for="linkType">Link Type:</label> | |
| <select id="linkType"> | |
| <option value="Shared">Shared</option> | |
| <option value="point-to-point">point-to-point</option> | |
| </select> | |
| </div> | |
| <div class="form-row"> | |
| <label for="priority">Priority:</label> | |
| <input type="number" id="priority" min="0" max="240" step="16"> | |
| </div> | |
| <div class="form-row"> | |
| <label for="pathCost">Path-Cost:</label> | |
| <input type="number" id="pathCost" min="0"> | |
| </div> | |
| <div class="form-row"> | |
| <label for="forceVersion">Force Version:</label> | |
| <select id="forceVersion"> | |
| <option value="STP">STP</option> | |
| <option value="nonSupported">Non supported</option> | |
| <option value="MSTP">MSTP</option> | |
| <option value="RSTP">RSTP</option> | |
| </select> | |
| </div> | |
| <div class="button-group"> | |
| <button onclick="applyMstpPortConfig()"id="applyBtn">Apply</button> | |
| <button onclick="deleteMstpPortConfig()"id="deleteBtn">Delete</button> | |
| <button onclick="refreshMstpPortConfig()"id="refreshBtn">Refresh</button> | |
| <button onclick="showHelp()"id="helpBtn">Help</button> | |
| </div> | |
| </div> | |
| `); | |
| } catch (err) { | |
| console.error("Error loading MSTP port config:", err); | |
| showContent('<p style="color:red;">Failed to load MSTP port configuration.</p>'); | |
| } | |
| } | |
| function populatePortFields() { | |
| const selectedPort = document.getElementById('portSelect').value; | |
| const portData = window.mstpPortData.find(p => p.port === selectedPort); | |
| if (portData) { | |
| document.getElementById('portFast').value = portData.portFast || "Disabled"; | |
| document.getElementById('portfast_bpdu_filter').value = portData.portfastBpduFilter || "Disabled"; | |
| document.getElementById('portfast_bpdu_guard').value = portData.portfastBpduGuard || "Disabled"; | |
| document.getElementById('rootGuard').value = portData.rootGuard || "Disabled"; | |
| document.getElementById('linkType').value = portData.linkType || "Shared"; | |
| document.getElementById('priority').value = portData.priority || 0; | |
| document.getElementById('pathCost').value = portData.pathCost || 0; | |
| document.getElementById('forceVersion').value = portData.forceVersion || "MSTP"; | |
| } else { | |
| document.getElementById('portFast').value = "Disabled"; | |
| document.getElementById('portfast_bpdu_filter').value = "Disabled"; | |
| document.getElementById('portfast_bpdu_guard').value = "Disabled"; | |
| document.getElementById('rootGuard').value = "Disabled"; | |
| document.getElementById('linkType').value = "Shared"; | |
| document.getElementById('priority').value = 0; | |
| document.getElementById('pathCost').value = 0; | |
| document.getElementById('forceVersion').value = "MSTP"; | |
| } | |
| } | |
| async function applyMstpPortConfig() { | |
| const selectedPort = document.getElementById('portSelect').value; | |
| if (!selectedPort) { | |
| alert('Please select a port before applying configuration.'); | |
| return; | |
| } | |
| const data = { | |
| port: selectedPort, | |
| portFast: document.getElementById('portFast').value, | |
| portfastBpduFilter: document.getElementById('portfast_bpdu_filter').value, | |
| portfastBpduGuard: document.getElementById('portfast_bpdu_guard').value, | |
| rootGuard: document.getElementById('rootGuard').value, | |
| linkType: document.getElementById('linkType').value, | |
| priority: parseInt(document.getElementById('priority').value), | |
| pathCost: parseInt(document.getElementById('pathCost').value), | |
| forceVersion: document.getElementById('forceVersion').value | |
| }; | |
| const validBooleans = ["Enabled", "Disabled"]; | |
| const validLinkTypes = ["Shared", "point-to-point"]; | |
| const validForceVersions = ["STP", "nonSupported", "MSTP", "RSTP"]; | |
| if ( | |
| !selectedPort || | |
| !validBooleans.includes(data.portFast) || | |
| !validBooleans.includes(data.portfastBpduFilter) || | |
| !validBooleans.includes(data.portfastBpduGuard) || | |
| !validBooleans.includes(data.rootGuard) || | |
| !validLinkTypes.includes(data.linkType) || | |
| !Number.isInteger(data.priority) || data.priority < 0 || data.priority > 240 || | |
| !Number.isInteger(data.pathCost) || data.pathCost < 0 || | |
| !validForceVersions.includes(data.forceVersion) | |
| ) { | |
| alert("Invalid port configuration parameters. Check input values."); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/mstp-port-config`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(data) | |
| }); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| alert('MSTP Port configuration applied successfully.'); | |
| refreshMstpPortConfig(); | |
| } catch (err) { | |
| console.error('Error applying MSTP port config:', err); | |
| alert('Failed to apply MSTP port configuration.'); | |
| } | |
| } | |
| async function deleteMstpPortConfig() { | |
| const selectedPort = document.getElementById('portSelect').value; | |
| if (!selectedPort) { | |
| alert('Please select a port to delete.'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/mstp-port-config?port=${selectedPort}`, { | |
| method: 'DELETE', | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| alert('MSTP Port configuration deleted successfully.'); | |
| refreshMstpPortConfig(); | |
| } catch (err) { | |
| console.error('Error deleting MSTP port config:', err); | |
| alert('Failed to delete MSTP port configuration.'); | |
| } | |
| } | |
| function refreshMstpPortConfig() { | |
| mstpPortConfig(); | |
| alert("MSTP port configuration refreshed."); | |
| } | |
| async function mstpPortInformation() { | |
| showContent('<p>Loading MSTP Port Information...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/mstp-port-information`); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| const mstpPortInfo = await response.json(); | |
| showContent(` | |
| <style> | |
| table { margin: 0 auto; border-collapse: collapse; } | |
| th, td { border: 1px solid #ddd; padding: 8px; text-align: center; } | |
| .button-group { display: flex; gap: 10px; justify-content: center; margin-top: 10px; } | |
| </style> | |
| <h2 style="text-align: center;">MSTP Port Information</h2> | |
| <table id="mstpPortInfoTable"> | |
| <thead> | |
| <tr> | |
| <th>Port</th> | |
| <th>Portfast</th> | |
| <th>Bpdu-Filter</th> | |
| <th>Bpdu-Guard</th> | |
| <th>Root Guard</th> | |
| <th>Link-Type</th> | |
| <th>Priority</th> | |
| <th>Path-Cost</th> | |
| <th>Force-Version</th> | |
| </tr> | |
| </thead> | |
| <tbody id="mstpPortInfoTableBody"> | |
| </tbody> | |
| </table> | |
| <div class="button-group"> | |
| <button onclick="mstpPortInformation()" id="refreshBtn">Refresh</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| populateMstpPortInfoTable(mstpPortInfo); | |
| } catch (error) { | |
| console.error("Error fetching MSTP port information:", error); | |
| showContent('<p style="color:red;">Failed to load MSTP port information.</p>'); | |
| } | |
| } | |
| function populateMstpPortInfoTable(mstpPortInfo) { | |
| const tableBody = document.getElementById('mstpPortInfoTableBody'); | |
| tableBody.innerHTML = ''; | |
| mstpPortInfo.forEach(port => { | |
| if ( | |
| typeof port.port === "string" && | |
| ["Enabled", "Disabled"].includes(port.portFast) && | |
| ["Enabled", "Disabled"].includes(port.portfastBpduFilter) && | |
| ["Enabled", "Disabled"].includes(port.portfastBpduGuard) && | |
| ["Enabled", "Disabled"].includes(port.rootGuard) && | |
| ["Shared", "point-to-point"].includes(port.linkType) && | |
| Number.isInteger(port.priority) && | |
| Number.isInteger(port.pathCost) && | |
| ["STP", "nonSupported", "MSTP", "RSTP"].includes(port.forceVersion) | |
| ) { | |
| const row = document.createElement('tr'); | |
| row.innerHTML = ` | |
| <td>${port.port}</td> | |
| <td>${port.portFast}</td> | |
| <td>${port.portfastBpduFilter}</td> | |
| <td>${port.portfastBpduGuard}</td> | |
| <td>${port.rootGuard}</td> | |
| <td>${port.linkType}</td> | |
| <td>${port.priority}</td> | |
| <td>${port.pathCost}</td> | |
| <td>${port.forceVersion}</td> | |
| `; | |
| tableBody.appendChild(row); | |
| } | |
| }); | |
| } | |
| //////NTDP | |
| async function ntdpConfig() { | |
| showContent('<p>Loading NTDPS Configuration...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/ntdp-config`); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| const ntdpData = await response.json(); // expects { status: 'enabled', interval: 30, maxEntries: 100 } | |
| showContent(` | |
| <h2>NTDPS Configuration</h2> | |
| <div class="info"> | |
| <label for="port">Port</label> | |
| <select style="width:150px;" id="port"> | |
| ${ntdpData.port.map(p => ` | |
| <option value="${p}" ${p === ntdpData.selectedPort ? 'selected' : ''}>${p}</option> | |
| `).join('')} | |
| </select> | |
| <br> | |
| <label for="portEnable">Port enable</label> | |
| <select style="width:150px;" id="portEnable"> | |
| <option value="Enabled" ${ntdpData.portEnable === 'Enabled' ? 'selected' : ''}>Enabled</option> | |
| <option value="Disabled" ${ntdpData.portEnable === 'Disabled' ? 'selected' : ''}>Disabled</option> | |
| </select> | |
| <br> | |
| <label for="globalEnable">Global Enable</label> | |
| <select style="width:150px;" id="globalEnable"> | |
| <option value="Enabled" ${ntdpData.globalEnable === 'Enabled' ? 'selected' : ''}>Enabled</option> | |
| <option value="Disabled" ${ntdpData.globalEnable === 'Disabled' ? 'selected' : ''}>Disabled</option> | |
| </select> | |
| <br> | |
| <label for="ntdpInterval">Interval time(seconds)</label> | |
| <input type="number" id="ntdpInterval" value="${ntdpData.interval}" min="1"> | |
| (0-65535 min) | |
| <br> | |
| <label for="hopsDelay">Hops Delay</label> | |
| <input type="number" id="hopsDelay" value="${ntdpData.hopsDelay}" min="1"> | |
| <br> | |
| <label for="portDelay">Port Delay</label> | |
| <input type="number" id="portDelay" value="${ntdpData.portDelay}" min="1"> | |
| <br> | |
| <button onclick="applyNtdpConfig()" id="applyBtn">Apply</button> | |
| <button onclick="refreshNtdpConfig()" id="refreshBtn">Refresh</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| } catch (error) { | |
| console.error("Error fetching NTDPS configuration:", error); | |
| showContent('<p style="color:red;">Failed to load NTDPS configuration.</p>'); | |
| } | |
| } | |
| function applyNtdpConfig() { | |
| const interval = parseInt(document.getElementById('ntdpInterval').value); | |
| if (isNaN(interval) || interval < 0 || interval > 65535) { | |
| alert("Interval time must be between 0 and 65535 seconds."); | |
| return; | |
| } | |
| fetch(`${BASE_URL}/ntdp-config`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| port: document.getElementById('port').value, | |
| portEnable: document.getElementById('portEnable').value, | |
| globalEnable: document.getElementById('globalEnable').value, | |
| interval: interval, | |
| hopsDelay: document.getElementById('hopsDelay').value, | |
| portDelay: document.getElementById('portDelay').value | |
| }) | |
| }) | |
| .then(response => { | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! Status: ${response.status}`); | |
| } | |
| return response.json(); | |
| }) | |
| .then(data => { | |
| console.log("NTDPS configuration applied successfully:", data); | |
| alert("NTDPS configuration applied successfully!"); | |
| }) | |
| .catch(error => { | |
| console.error("Error applying NTDPS configuration:", error); | |
| alert("Failed to apply NTDPS configuration."); | |
| }); | |
| } | |
| function refreshNtdpConfig() { | |
| ntdpConfig(); | |
| alert("NTDPS configuration refreshed."); | |
| } | |
| //NPD CONFIGURATION | |
| async function ndpConfig() { | |
| showContent('<p>Loading NDP Configuration...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/ndp-config`); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| const ndpData = await response.json(); // expects { status: 'enabled', interval: 30, maxEntries: 100 } | |
| showContent(` | |
| <h2>NDP Configuration</h2> | |
| <div class="info"> | |
| <label for="port">Port</label> | |
| <select style="width:150px;" id="port"> | |
| ${ndpData.port.map(p => ` | |
| <option value="${p}" ${p === ndpData.selectedPort ? 'selected' : ''}>${p}</option> | |
| `).join('')} | |
| </select> | |
| <br> | |
| <label for="portEnable">Port enable</label> | |
| <select style="width:150px;" id="ndpStatus"> | |
| <option value="Enabled" ${ndpData.portEnable === 'Enabled' ? 'selected' : ''}>Enabled</option> | |
| <option value="Disabled" ${ndpData.portEnable === 'Disabled' ? 'selected' : ''}>Disabled</option> | |
| </select> | |
| <br> | |
| <label for="globalEnable">Global Enable</label> | |
| <select style="width:150px;" id="globalEnable"> | |
| <option value="Enabled" ${ndpData.globalEnable === 'Enabled' ? 'selected' : ''}>Enabled</option> | |
| <option value="Disabled" ${ndpData.globalEnable === 'Disabled' ? 'selected' : ''}>Disabled</option> | |
| </select> | |
| <br> | |
| <label for="helloTime">Hello-time</label> | |
| <input type="number" id="helloTime" value="${ndpData.helloTime}" min="1" max="65535"> | |
| (1-65535 min) | |
| <br> | |
| <label for="agingTime">Aging-Time</label> | |
| <input type="number" id="hopsDelay" value="${ndpData.agingTime}" min="1"> | |
| <br> | |
| <button onclick="applyNdpConfig()" id = "applyBtn">Apply</button> | |
| <button onclick="refreshNdpConfig()" id = "refresh Btn">Refresh</button> | |
| <button onclick="showHelp()" id = "helpBtn">Help</button> | |
| </div> | |
| `); | |
| } catch (error) { | |
| console.error("Error fetching NDP configuration:", error); | |
| showContent('<p style="color:red;">Failed to load NDP configuration.</p>'); | |
| } | |
| } | |
| function applyNdpConfig() { | |
| const hello = parseInt(document.getElementById('helloTime').value); | |
| if (isNaN(hello) || hello < 0 || hello > 65535) { | |
| alert("Hello-time must be between 0 and 65535."); | |
| return; | |
| } | |
| fetch(`${BASE_URL}/ndp-config`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| port: document.getElementById('port').value, | |
| portEnable: document.getElementById('ndpStatus').value, | |
| globalEnable: document.getElementById('globalEnable').value, | |
| helloTime: hello, | |
| agingTime: document.getElementById('hopsDelay').value | |
| }) | |
| }) | |
| .then(response => { | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! Status: ${response.status}`); | |
| } | |
| return response.json(); | |
| }) | |
| .then(data => { | |
| console.log("NDP configuration applied successfully:", data); | |
| alert("NDP configuration applied successfully!"); | |
| }) | |
| .catch(error => { | |
| console.error("Error applying NDP configuration:", error); | |
| alert("Failed to apply NDP configuration."); | |
| }); | |
| } | |
| function refreshNdpConfig() { | |
| ndpConfig(); | |
| alert("NDP configuration refreshed."); | |
| } | |
| async function showClusterConfigPage() { | |
| showContent("Loading Cluster Configuration"); | |
| try { | |
| const configRes = await fetch(`${BASE_URL}/cluster-config`); | |
| const config = await configRes.json(); | |
| showContent(` | |
| <h2 style="color: red;">Cluster Configuration</h2> | |
| <div class="config-section"> | |
| <label>Cluster Enable</label> | |
| <select style="width:150px;" id="clusterEnable"> | |
| <option value="enable" ${config.enable === "enable" ? "selected" : ""}>Enable</option> | |
| <option value="disable" ${config.enable === "disable" ? "selected" : ""}>Disable</option> | |
| </select> | |
| <br> | |
| <label>Management-vlan</label> | |
| <input type="number" id="mgmtVlan" min="1" max="4094" value="${config.mgmtVlan}"> | |
| <span>(1-4094)</span><br> | |
| <label>IP-pool</label> | |
| <input type="text" id="ipPool" value="${config.ipPool}"> | |
| <span>(A.B.C.D/M)</span><br> | |
| <label>Handshake time</label> | |
| <input type="number" id="handshakeTime" min="1" max="255" value="${config.handshakeTime}"> | |
| <span>(1-255 sec)</span><br> | |
| <label>Handshake hold-time</label> | |
| <input type="number" id="holdTime" min="1" max="255" value="${config.holdTime}"> | |
| <span>(1-255 sec)</span><br> | |
| <button onclick="applyClusterConfig()" id = "applyBtn">Apply</button> | |
| </div> | |
| <div class="name-section"> | |
| <label>Cluster Name</label> | |
| <input type="text" id="clusterName"> | |
| <br> | |
| <label>Type</label> | |
| <select style="width:150px;" id="clusterType"> | |
| <option value="static">Static</option> | |
| <option value="dynamic">Dynamic</option> | |
| </select> | |
| <button onclick="applyClusterName()" id = "applyBtn">Apply</button> | |
| <button onclick="deleteClusterName()" id = "deleteBtn">Delete</button> | |
| </div> | |
| <h3 style="color: red;">Cluster Member List</h3> | |
| <table border="1" cellpadding="4"> | |
| <thead> | |
| <tr> | |
| <th>Serial</th> | |
| <th>MAC</th> | |
| <th>IP</th> | |
| <th>Status</th> | |
| <th>Name</th> | |
| <th>Role</th> | |
| </tr> | |
| </thead> | |
| <tbody id="clusterMembersTable"> | |
| <tr><td colspan="6" align="center">(Press the Button "Refresh" to view the latest information)</td></tr> | |
| </tbody> | |
| </table> | |
| <br> | |
| <button onclick="refreshClusterMembers()">Refresh</button> | |
| <button onclick="showHelp()">Help</button> | |
| `); | |
| } catch (err) { | |
| console.error("Error loading cluster config:", err); | |
| showContent("<p style='color:red;'>Failed to load cluster configuration.</p>"); | |
| } | |
| } | |
| function applyClusterConfig() { | |
| const data = { | |
| enable: document.getElementById("clusterEnable").value, | |
| mgmtVlan: parseInt(document.getElementById("mgmtVlan").value), | |
| ipPool: document.getElementById("ipPool").value, | |
| handshakeTime: parseInt(document.getElementById("handshakeTime").value), | |
| holdTime: parseInt(document.getElementById("holdTime").value), | |
| }; | |
| fetch(`${BASE_URL}/cluster-config`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(data), | |
| }) | |
| .then(res => res.json()) | |
| .then(result => { | |
| alert("Cluster configuration applied successfully."); | |
| console.log(result); | |
| }) | |
| .catch(err => { | |
| console.error("Error applying cluster config:", err); | |
| alert("Failed to apply cluster configuration."); | |
| }); | |
| } | |
| function applyClusterName() { | |
| const data = { | |
| name: document.getElementById("clusterName").value, | |
| type: document.getElementById("clusterType").value | |
| }; | |
| fetch(`${BASE_URL}/cluster-name`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(data), | |
| }) | |
| .then(res => res.json()) | |
| .then(result => { | |
| alert("Cluster name applied."); | |
| console.log(result); | |
| }) | |
| .catch(err => { | |
| console.error("Error applying cluster name:", err); | |
| alert("Failed to apply cluster name."); | |
| }); | |
| } | |
| function deleteClusterName() { | |
| const data = { | |
| name: document.getElementById("clusterName").value | |
| }; | |
| fetch(`${BASE_URL}/cluster-name`, { | |
| method: 'DELETE', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(data), | |
| }) | |
| .then(res => res.json()) | |
| .then(result => { | |
| alert("Cluster name deleted."); | |
| console.log(result); | |
| }) | |
| .catch(err => { | |
| console.error("Error deleting cluster name:", err); | |
| alert("Failed to delete cluster name."); | |
| }); | |
| } | |
| function refreshClusterMembers() { | |
| fetch(`${BASE_URL}/cluster-members`) | |
| .then(res => res.json()) | |
| .then(members => { | |
| const tbody = document.getElementById("clusterMembersTable"); | |
| tbody.innerHTML = members.map(m => ` | |
| <tr> | |
| <td>${m.serial}</td> | |
| <td>${m.mac}</td> | |
| <td>${m.ip}</td> | |
| <td>${m.status}</td> | |
| <td>${m.name}</td> | |
| <td>${m.role}</td> | |
| </tr> | |
| `).join(''); | |
| }) | |
| .catch(err => { | |
| console.error("Error fetching cluster members:", err); | |
| alert("Failed to fetch cluster members."); | |
| }); | |
| } | |
| function showHelp() { | |
| alert("This section manages clustering settings. Refer to documentation for detailed usage."); | |
| } | |
| ///POE POLICY CONFIGURATION | |
| async function poePolicyConfig() { | |
| showContent("Loading PoE policy configuration..."); | |
| try { | |
| const response = await fetch(`${BASE_URL}/poe-policy-config`); | |
| const data = await response.json(); | |
| const portOptions = data.map(item => `<option value="${item.port}">${item.port}</option>`).join(''); | |
| showContent(` | |
| <h2 style="color: crimson;">PoE Policy Configuration</h2> | |
| <label>PoE Port: </label> | |
| <select id="poePort">${portOptions}</select><br/> | |
| <label>Policy Status: </label> | |
| <select id="policyStatus"> | |
| <option value="disable">disable</option> | |
| <option value="enable">enable</option> | |
| </select><br/><br/> | |
| <button id="refreshBtn">Refresh</button> | |
| <button id="applyBtn">Apply</button> | |
| <table id="scheduleTable" border="1" style="margin-top: 10px;"></table> | |
| `); | |
| // Attach event listeners only once | |
| document.getElementById('poePort').addEventListener('change', fetchPolicy); | |
| document.getElementById('refreshBtn').addEventListener('click', fetchPolicy); | |
| document.getElementById('applyBtn').addEventListener('click', applyPolicy); | |
| // Load initial schedule | |
| await fetchPolicy(); | |
| } catch (err) { | |
| console.error("Error loading PoE policy config:", err); | |
| showContent("<p style='color:red;'>Failed to load PoE policy configuration.</p>"); | |
| } | |
| } | |
| async function fetchPolicy() { | |
| const port = document.getElementById('poePort').value; | |
| try { | |
| const res = await fetch(`${BASE_URL}/poe-policy-config/${port}`); | |
| const { status, schedule } = await res.json(); | |
| document.getElementById('policyStatus').value = status || 'disable'; | |
| renderScheduleTable(schedule || {}); | |
| } catch (err) { | |
| console.error("Error fetching port policy:", err); | |
| showContent("<p style='color:red;'>Failed to load policy for selected port.</p>"); | |
| } | |
| } | |
| function renderScheduleTable(schedule = {}) { | |
| const days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]; | |
| const table = document.getElementById("scheduleTable"); | |
| let html = ` | |
| <tr> | |
| <th> | |
| Clock<br/> | |
| (<input type="checkbox" id="selectAll" onchange="toggleAll(this)"> All) | |
| </th>`; | |
| days.forEach(day => html += `<th>${day}</th>`); | |
| html += '</tr>'; | |
| for (let h = 0; h < 24; h++) { | |
| const hour = h.toString().padStart(2, '0'); | |
| html += `<tr> | |
| <td> | |
| ${hour} <br/> | |
| <input type="checkbox" class="hour-checkbox" data-hour="${hour}" onchange="toggleHour(this)"> | |
| </td>`; | |
| days.forEach(day => { | |
| const key = `${hour}-${day}`; | |
| const checked = schedule[key] ? 'checked' : ''; | |
| html += `<td><input type="checkbox" class="poe-checkbox" data-key="${key}" data-hour="${hour}" data-day="${day}" ${checked}></td>`; | |
| }); | |
| html += '</tr>'; | |
| } | |
| table.innerHTML = html; | |
| } | |
| function toggleHour(checkbox) { | |
| const hour = checkbox.dataset.hour; | |
| document.querySelectorAll(`.poe-checkbox[data-hour="${hour}"]`) | |
| .forEach(cb => cb.checked = checkbox.checked); | |
| } | |
| function toggleAll(master) { | |
| document.querySelectorAll('.poe-checkbox').forEach(cb => cb.checked = master.checked); | |
| document.querySelectorAll('.hour-checkbox').forEach(cb => cb.checked = master.checked); | |
| } | |
| async function applyPolicy() { | |
| const port = document.getElementById('poePort').value; | |
| const status = document.getElementById('policyStatus').value; | |
| const checkboxes = document.querySelectorAll('.poe-checkbox'); | |
| const schedule = {}; | |
| checkboxes.forEach(cb => { | |
| if (cb.checked) schedule[cb.dataset.key] = true; | |
| }); | |
| try { | |
| const res = await fetch(`${BASE_URL}/poe-policy-config/${port}`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ status, schedule }) | |
| }); | |
| const msg = await res.json(); | |
| alert("Policy applied: " + msg.message); | |
| } catch (err) { | |
| console.error("Failed to apply PoE policy:", err); | |
| alert("Failed to apply configuration."); | |
| } | |
| } | |
| //poe port configuration | |
| async function poePortConfig() { | |
| showContent("Loading PoE port configuration..."); | |
| try { | |
| const config = await fetchPoePortConfig(); | |
| const ports = config.ports; | |
| showContent(` | |
| <h2>PoE Port Configuration</h2> | |
| <div class="controls" style="width: 600px;"> | |
| <table style="width: 100%;"> | |
| <tr> | |
| <td style="padding: 6px;">Selected Ports</td> | |
| <td><input type="text" id="selectedPorts" readonly style=" width: 70%;" /></td> | |
| </tr> | |
| <tr> | |
| <td style=" padding: 6px;">PoE Mode</td> | |
| <td> | |
| <select id="poeMode" style="width: 70%;"> | |
| <option value="automatic">Automatic</option> | |
| <option value="active24V">Active 24V</option> | |
| <option value="active48V">Active 48V</option> | |
| <option value="forced24V">24V Forced ON</option> | |
| <option value="forced48V">48V Forced ON</option> | |
| </select> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style=" padding: 6px;">PoE Admin Status</td> | |
| <td> | |
| <select id="adminStatus" style="width: 70%;"> | |
| <option value="enabled">Enabled</option> | |
| <option value="disabled">Disabled</option> | |
| </select> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="padding: 6px;">Active PSE Type</td> | |
| <td> | |
| <select id="activePSEType" style="width: 70%;"> | |
| <option value="af">AF</option> | |
| <option value="at">AT</option> | |
| </select> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="padding: 6px;">Passive PSE Type</td> | |
| <td> | |
| <select id="passivePSEType" style="width: 70%;"> | |
| <option value="af">AF</option> | |
| <option value="at">AT</option> | |
| </select> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="padding: 6px;">Active Voltage (V)</td> | |
| <td><input type="number" id="activeVoltage" min="0" style="width: 70%;" /></td> | |
| </tr> | |
| <tr> | |
| <td style="padding: 6px;">Passive Voltage (V)</td> | |
| <td><input type="number" id="passiveVoltage" min="0" style="width: 70%;" /></td> | |
| </tr> | |
| <tr> | |
| <td style="padding: 6px;">Total Power (W)</td> | |
| <td><input type="number" id="totalPower" min="0" style="width: 70%;" /></td> | |
| </tr> | |
| <tr> | |
| <td style=" padding: 6px;">Power Consumption (W)</td> | |
| <td><input type="number" id="powerConsumption" readonly style="width: 70%; " /></td> | |
| </tr> | |
| <tr> | |
| <td style="padding: 6px;">Power Usage (%)</td> | |
| <td><input type="number" id="powerUsage" readonly style="width: 70%;" /></td> | |
| </tr> | |
| <tr> | |
| <td style="padding: 6px;">PSE Hardware Version</td> | |
| <td><input type="text" id="pseHardwareVersion" readonly style="width: 70%;" /></td> | |
| </tr> | |
| </table> | |
| <br/> | |
| <br/> | |
| </div> | |
| <div> | |
| <button id="refreshBtn">Refresh</button> | |
| <button id="applyBtn" disabled>Apply</button> | |
| <button id="restoreBtn" disabled>Restore Default</button> | |
| <button id="restartBtn" disabled>Port Restarting</button> | |
| </div> | |
| <table id="portTable"> | |
| <thead> | |
| <tr> | |
| <th><input type="checkbox" id="selectAll" />Select All</th> | |
| <th>Port</th> | |
| <th>Description</th> | |
| <th>Type</th> | |
| <th>Mode</th> | |
| <th>Admin Status</th> | |
| <th>Operation</th> | |
| <th>Active PSE Type</th> | |
| <th>Passive PSE Type</th> | |
| <th>Current (mA)</th> | |
| <th>Voltage (V)</th> | |
| <th>Power (W)</th> | |
| </tr> | |
| </thead> | |
| <tbody></tbody> | |
| </table> | |
| `); | |
| renderPortTable(ports); | |
| setupEventHandlers(ports); | |
| } catch (err) { | |
| console.error("Error loading PoE port config:", err); | |
| showContent("<p style='color:red;'>Failed to load PoE port configuration.</p>"); | |
| } | |
| } | |
| async function fetchPoePortConfig() { | |
| const response = await fetch(`${BASE_URL}/poe-port-config`); | |
| if (!response.ok) throw new Error("Failed to fetch port config"); | |
| return response.json(); | |
| } | |
| function renderPortTable(ports) { | |
| const tbody = document.querySelector("#portTable tbody"); | |
| tbody.innerHTML = ports.map((p, i) => ` | |
| <tr data-index="${i}"> | |
| <td><input type="checkbox" class="portCheckbox" value="${p.id}"/></td> | |
| <td>${p.id}</td> | |
| <td>${p.description}</td> | |
| <td>${p.type}</td> | |
| <td>${p.mode}</td> | |
| <td>${p.adminStatus}</td> | |
| <td>${p.operation}</td> | |
| <td>${p.activePSEType}</td> | |
| <td>${p.passivePSEType}</td> | |
| <td>${p.current}</td> | |
| <td>${p.voltage}</td> | |
| <td>${p.power}</td> | |
| </tr> | |
| `).join(""); | |
| } | |
| function setupEventHandlers(ports) { | |
| const tbody = document.querySelector("#portTable tbody"); | |
| const selectedPortsInput = document.getElementById("selectedPorts"); | |
| const selectAllCheckbox = document.getElementById("selectAll"); | |
| const applyBtn = document.getElementById("applyBtn"); | |
| const refreshBtn = document.getElementById("refreshBtn"); | |
| const restoreBtn = document.getElementById("restoreBtn"); | |
| const restartBtn = document.getElementById("restartBtn"); | |
| let selectedPorts = []; | |
| // Helper to update Selected Ports input display | |
| function updateSelectedPortsDisplay() { | |
| if (selectedPorts.length === ports.length) { | |
| // Show range e.g. "1-8" based on min and max port ids | |
| const portIds = ports.map(p => p.id).sort((a, b) => a - b); | |
| selectedPortsInput.value = `${portIds[0]}-${portIds[portIds.length - 1]}`; | |
| } else if (selectedPorts.length > 0) { | |
| selectedPortsInput.value = selectedPorts.join(", "); | |
| } else { | |
| selectedPortsInput.value = ""; | |
| } | |
| applyBtn.disabled = selectedPorts.length === 0; | |
| restoreBtn.disabled = selectedPorts.length === 0; | |
| restartBtn.disabled = selectedPorts.length === 0; | |
| } | |
| // Individual port checkbox click | |
| tbody.addEventListener("change", (e) => { | |
| if (e.target.classList.contains("portCheckbox")) { | |
| const checkbox = e.target; | |
| const portId = checkbox.value; | |
| if (checkbox.checked) { | |
| if (!selectedPorts.includes(portId)) selectedPorts.push(portId); | |
| } else { | |
| selectedPorts = selectedPorts.filter(p => p !== portId); | |
| } | |
| // Update row highlight | |
| const tr = checkbox.closest("tr"); | |
| if (checkbox.checked) { | |
| tr.classList.add("selected"); | |
| } else { | |
| tr.classList.remove("selected"); | |
| } | |
| // Update Select All checkbox state | |
| if (selectedPorts.length === ports.length) { | |
| selectAllCheckbox.checked = true; | |
| selectAllCheckbox.indeterminate = false; | |
| } else if (selectedPorts.length > 0) { | |
| selectAllCheckbox.checked = false; | |
| selectAllCheckbox.indeterminate = true; | |
| } else { | |
| selectAllCheckbox.checked = false; | |
| selectAllCheckbox.indeterminate = false; | |
| } | |
| updateSelectedPortsDisplay(); | |
| if (selectedPorts.length === 1) { | |
| const singlePort = ports.find(p => p.id === selectedPorts[0]); | |
| populateForm(singlePort); | |
| } else { | |
| // Clear form or disable it if multiple ports selected | |
| clearForm(); | |
| } | |
| } | |
| }); | |
| // Row click to toggle selection for convenience (optional) | |
| tbody.addEventListener("click", (e) => { | |
| if (e.target.tagName === "TD" && !e.target.querySelector("input")) { | |
| const tr = e.target.closest("tr"); | |
| if (!tr) return; | |
| const checkbox = tr.querySelector(".portCheckbox"); | |
| checkbox.checked = !checkbox.checked; | |
| checkbox.dispatchEvent(new Event('change')); | |
| } | |
| }); | |
| // Select All checkbox click | |
| selectAllCheckbox.addEventListener("change", (e) => { | |
| const checked = e.target.checked; | |
| const checkboxes = tbody.querySelectorAll(".portCheckbox"); | |
| selectedPorts = []; | |
| checkboxes.forEach(cb => { | |
| cb.checked = checked; | |
| const tr = cb.closest("tr"); | |
| if (checked) { | |
| tr.classList.add("selected"); | |
| selectedPorts.push(cb.value); | |
| } else { | |
| tr.classList.remove("selected"); | |
| } | |
| }); | |
| updateSelectedPortsDisplay(); | |
| if (checked && ports.length === 1) { | |
| populateForm(ports[0]); | |
| } else { | |
| clearForm(); | |
| } | |
| }); | |
| // Apply button click | |
| applyBtn.addEventListener("click", () => applyChanges(selectedPorts)); | |
| // Refresh button click | |
| refreshBtn.addEventListener("click", () => poePortConfig()); | |
| // Restore button click | |
| restoreBtn.addEventListener("click", async () => { | |
| selectedPortsInput.value = "Restoring default now, please wait ..."; | |
| disableButtons(true); | |
| await restoreDefaults(selectedPorts); | |
| disableButtons(false); | |
| updateSelectedPortsDisplay(); | |
| }); | |
| // Restart button click | |
| restartBtn.addEventListener("click", async () => { | |
| selectedPortsInput.value = "Port restarting now, please wait ..."; | |
| disableButtons(true); | |
| await restartPorts(selectedPorts); | |
| disableButtons(false); | |
| updateSelectedPortsDisplay(); | |
| }); | |
| // Disable or enable buttons except Refresh (which should always be enabled) | |
| function disableButtons(disable) { | |
| applyBtn.disabled = disable; | |
| restoreBtn.disabled = disable; | |
| restartBtn.disabled = disable; | |
| // Refresh button stays enabled always | |
| } | |
| function clearForm() { | |
| // Clear or reset form fields | |
| const fields = ["poeMode", "adminStatus", "activePSEType", "passivePSEType", | |
| "activeVoltage", "passiveVoltage", "totalPower", "powerConsumption", | |
| "powerUsage", "pseHardwareVersion"]; | |
| fields.forEach(id => { | |
| const el = document.getElementById(id); | |
| if (!el) return; | |
| if (el.tagName === "SELECT" || el.tagName === "INPUT") { | |
| if (el.hasAttribute("readonly")) { | |
| el.value = ""; | |
| } else if (el.tagName === "SELECT") { | |
| el.selectedIndex = 0; | |
| } else { | |
| el.value = ""; | |
| } | |
| } | |
| }); | |
| } | |
| } | |
| function populateForm(port) { | |
| document.getElementById("poeMode").value = port.mode || ""; | |
| document.getElementById("adminStatus").value = port.adminStatus || ""; | |
| document.getElementById("activePSEType").value = port.activePSEType || ""; | |
| document.getElementById("passivePSEType").value = port.passivePSEType || ""; | |
| document.getElementById("activeVoltage").value = port.activeVoltage ?? ""; | |
| document.getElementById("passiveVoltage").value = port.passiveVoltage ?? ""; | |
| document.getElementById("totalPower").value = port.totalPower ?? ""; | |
| document.getElementById("powerConsumption").value = port.powerConsumption ?? ""; | |
| document.getElementById("powerUsage").value = port.powerUsage ?? ""; | |
| document.getElementById("pseHardwareVersion").value = port.pseHardwareVersion || ""; | |
| } | |
| async function applyChanges(selectedPorts) { | |
| if (selectedPorts.length === 0) { | |
| alert("No ports selected."); | |
| return; | |
| } | |
| const config = { | |
| mode: document.getElementById("poeMode").value, | |
| adminStatus: document.getElementById("adminStatus").value, | |
| activePSEType: document.getElementById("activePSEType").value, | |
| passivePSEType: document.getElementById("passivePSEType").value, | |
| activeVoltage: parseFloat(document.getElementById("activeVoltage").value), | |
| passiveVoltage: parseFloat(document.getElementById("passiveVoltage").value), | |
| totalPower: parseInt(document.getElementById("totalPower").value), | |
| }; | |
| try { | |
| const response = await fetch(`${BASE_URL}/poe-port-config/apply-settings`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ ports: selectedPorts, config }) | |
| }); | |
| if (!response.ok) throw new Error("Failed to apply configuration"); | |
| // ✅ Fetch and update UI immediately after applying changes | |
| const updated = await response.json(); | |
| renderPortTable(updated.ports); // updated.ports comes from backend response | |
| setupEventHandlers(updated.ports); // reattach checkbox/select handlers | |
| alert("Configuration applied successfully."); | |
| } catch (err) { | |
| console.error("Apply failed:", err); | |
| alert("Failed to apply changes."); | |
| } | |
| } | |
| async function restoreDefaults(selectedPorts) { | |
| try { | |
| const response = await fetch(`${BASE_URL}/poe-port-config/restore-defaults`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ ports: selectedPorts }) | |
| }); | |
| if (!response.ok) throw new Error("Restore failed"); | |
| alert("Defaults restored."); | |
| await poePortConfig(); | |
| } catch (err) { | |
| console.error("Restore error:", err); | |
| alert("Restore default failed."); | |
| } | |
| } | |
| async function restartPorts(selectedPorts) { | |
| try { | |
| const response = await fetch(`${BASE_URL}/poe-port-restart`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ ports: selectedPorts }) | |
| }); | |
| if (!response.ok) throw new Error("Restart failed"); | |
| alert("Ports restarted."); | |
| } catch (err) { | |
| console.error("Restart error:", err); | |
| alert("Restart failed."); | |
| } | |
| } | |
| ///log managemnet | |
| let lastLogData = null; | |
| async function logInfo() { | |
| showContent('<p>Loading Log Information...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/log-info`); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| lastLogData = await response.json(); | |
| renderLogInfo(lastLogData); | |
| wireLogInfoHandlers(); | |
| } catch (error) { | |
| console.error("Error fetching log information:", error); | |
| showContent('<p style="color:red;">Failed to load log information.</p>'); | |
| } | |
| } | |
| function renderLogInfo(logData) { | |
| showContent(` | |
| <h2>Log Information</h2> | |
| <div class="info"> | |
| <label for="logPriority">Log Priority</label> | |
| <select style="width:150px;" id="logPriority"> | |
| <option value="all">All</option> | |
| <option value="critical">Critical</option> | |
| <option value="warning">Warning</option> | |
| <option value="info">Informational</option> | |
| <option value="debug">Debugging</option> | |
| </select> | |
| <br><br> | |
| <p><strong>Critical</strong> — Total: ${logData.criticalCount}, Used: ${logData.criticalUsed}</p> | |
| <p><strong>Warning</strong> — Total: ${logData.warningCount}, Used: ${logData.warningUsed}</p> | |
| <p><strong>Informational</strong> — Total: ${logData.infoCount}, Used: ${logData.infoUsed}</p> | |
| <p><strong>Debugging</strong> — Total: ${logData.debugCount}, Used: ${logData.debugUsed}</p> | |
| <button id="btnRefreshLogInfo">Refresh</button> | |
| <button id="btnClearInfo">Clear</button> | |
| </div> | |
| <hr> | |
| <table style="width:100%; border-collapse: collapse;"> | |
| <thead style="background:#e8f0ff;"> | |
| <tr> | |
| <th style="border:1px solid #ccc; padding:4px;">Time</th> | |
| <th style="border:1px solid #ccc; padding:4px;">Priority</th> | |
| <th style="border:1px solid #ccc; padding:4px;">Message</th> | |
| </tr> | |
| </thead> | |
| <tbody id="logsTableBody"></tbody> | |
| </table> | |
| `); | |
| renderLogsTable(logData, 'all'); | |
| } | |
| function wireLogInfoHandlers() { | |
| const prioritySelect = document.getElementById('logPriority'); | |
| const refreshBtn = document.getElementById('btnRefreshLogInfo'); | |
| const clearBtn = document.getElementById('btnClearInfo'); | |
| if (prioritySelect) { | |
| prioritySelect.addEventListener('change', () => { | |
| const selected = prioritySelect.value; | |
| renderLogsTable(lastLogData, selected); | |
| }); | |
| } | |
| if (refreshBtn) refreshBtn.addEventListener('click', logInfo); | |
| if (clearBtn) clearBtn.addEventListener('click', clearInfo); | |
| } | |
| function renderLogsTable(logData, filter = 'all') { | |
| const tbody = document.getElementById('logsTableBody'); | |
| if (!tbody) return; | |
| const rows = (logData.logs || []) | |
| .filter(log => filter === 'all' || log.priority?.toLowerCase() === filter.toLowerCase()) | |
| .map(log => ` | |
| <tr> | |
| <td style="border:1px solid #ccc; padding:4px;">${log.timestamp}</td> | |
| <td style="border:1px solid #ccc; padding:4px;">${log.priority}</td> | |
| <td style="border:1px solid #ccc; padding:4px;">${log.message}</td> | |
| </tr> | |
| `) | |
| .join(''); | |
| tbody.innerHTML = rows || `<tr><td colspan="3" style="text-align:center;">No logs available.</td></tr>`; | |
| } | |
| async function clearInfo() { | |
| try { | |
| const res = await fetch(`${BASE_URL}/log-info/clear`, { method: 'POST' }); | |
| if (!res.ok) throw new Error('Failed to clear logs.'); | |
| await logInfo(); | |
| } catch (err) { | |
| console.error("Clear failed:", err); | |
| alert("Could not clear logs."); | |
| } | |
| } | |
| async function logConfig() { | |
| showContent('<p>Loading Log Configuration...</p>'); | |
| try { | |
| const response = await fetch(`${BASE_URL}/log-config`); | |
| if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
| const logConfigData = await response.json(); // expects { level: 'info', destination: 'file' } | |
| showContent(` | |
| <h2>Log Configuration</h2> | |
| <div class="info"> | |
| <label for="syslog">Syslog</label> | |
| <select style="width:150px;" id="syslog"> | |
| <option value="enable">Enable</option> | |
| <option value="disable">Disable</option> | |
| </select> | |
| <br> | |
| <label for="firstServerIp">First Server IP</label> | |
| <input type="text" id="firstServerIp" style="width:150px;" value="${logConfigData.firstServerIp || ''}"> | |
| <br> | |
| <label for="secondServerIp">Second Server IP</label> | |
| <input type="text" id="secondServerIp" style="width:150px;" value="${logConfigData.secondServerIp || ''}"> | |
| <br> | |
| <label for="udp">UDP</label> | |
| <input type="text" id="udp" style="width:150px;" value="${logConfigData.udp || ''}"> (1-65535) | |
| <br> | |
| <label for="logLevel">Log Level</label> | |
| <select style="width:150px;" id="logLevel"> | |
| <option value="debug">Debug</option> | |
| <option value="info">Info</option> | |
| <option value="warning">Warning</option> | |
| <option value="error">Error</option> | |
| </select> | |
| <button onclick="applyLogConfig()" id = "applyBtn">Apply</button> | |
| <button onclick="refreshLogConfig()" id = "refreshBtn">Refresh</button> | |
| <button onclick="showHelp()" id = "helpBtn">Help</button> | |
| </div> | |
| `); | |
| } catch (error) { | |
| console.error("Error fetching log configuration:", error); | |
| showContent('<p style="color:red;">Failed to load log configuration.</p>'); | |
| } | |
| } | |
| async function applyLogConfig() { | |
| const syslog = document.getElementById('syslog').value; | |
| const firstServerIp = document.getElementById('firstServerIp').value; | |
| const secondServerIp = document.getElementById('secondServerIp').value; | |
| const udp = parseInt(document.getElementById('udp').value, 10); | |
| const logLevel = document.getElementById('logLevel').value; | |
| if (isNaN(udp) || udp < 1 || udp > 65535) { | |
| alert("Please enter a valid UDP port (1–65535)."); | |
| return; | |
| } | |
| const payload = { | |
| syslog, | |
| firstServerIp, | |
| secondServerIp, | |
| udp, | |
| logLevel | |
| }; | |
| try { | |
| const response = await fetch(`${BASE_URL}/log-config`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(payload) | |
| }); | |
| if (!response.ok) throw new Error(`Failed to apply config: ${response.statusText}`); | |
| alert('Log configuration applied successfully.'); | |
| await logConfig(); // Reload updated values | |
| } catch (err) { | |
| console.error("Apply failed:", err); | |
| alert("Failed to apply log configuration."); | |
| } | |
| } | |
| function refreshLogConfig() { | |
| logConfig(); | |
| alert("Log configuration refreshed."); | |
| } | |
| // STANDARD IP | |
| async function standardIP() { | |
| showContent(` | |
| <h2>ACL Standard IP Configuration</h2> | |
| <hr/> | |
| <div style="font-family: Arial, sans-serif; font-size: 13px;"> | |
| <!-- Configuration Fields --> | |
| <div style="margin-bottom: 10px;"> | |
| <label style="font-weight: bold;">ACL Standard IP Group Num:</label> | |
| <select id="aclStandardIPGroupSelect" style="padding: 3px 5px; border: 1px solid #a0a0a0; border-radius: 2px; font-size: 13px; width: 75px;"></select> | |
| </div> | |
| <div style="display: flex; flex-direction: column; gap: 0px; margin-bottom: 10px; width: 600px"> | |
| <div style="display: flex; gap: 20px;"> | |
| <div style="display: flex; align-items: center;"> | |
| <label class="label-style">Source IP Address</label> | |
| <input type="text" id="sourceIp" style="width:200px" /> | |
| </div> | |
| <div style="display: flex; align-items: center;"> | |
| <label class="label-style">Source Wildcard</label> | |
| <input type="text" id="sourceWildcard" style="width:200px" /> | |
| </div> | |
| </div> | |
| </div> | |
| <div style="font-size: 11px; color: #333; margin-bottom: 10px;"> | |
| <b>(e.g.: If input IP Address 192.168.1.2, ACL wants to control 192.168.1.0, then Wildcard should be 0.0.0.255...)</b> | |
| </div> | |
| <div style="margin-bottom: 10px;"> | |
| <label> | |
| <input type="radio" id="deny" name="action" value="deny" checked /> | |
| Deny | |
| </label> | |
| <label style="margin-left: 15px;"> | |
| <input type="radio" id="permit" name="action" value="permit" /> | |
| Permit | |
| </label> | |
| </div> | |
| <table style="border-collapse: collapse; margin-bottom: 10px;"> | |
| <thead> | |
| <tr> | |
| <th ><input type="checkbox" id="selectAllCheckbox" onclick="selectStandardIPAllAcl(this)"></th> | |
| <th >Group Num</th> | |
| <th >Deny/Permit</th> | |
| <th >Source IP Address</th> | |
| <th >Source Wildcard</th> | |
| </tr> | |
| </thead> | |
| <tbody id="aclStandardIpTableBody"></tbody> | |
| </table> | |
| <div style="text-align: center; margin-top: 20px;"> | |
| <button onclick="standardIP()" id="refreshBtn">Refresh</button> | |
| <button onclick="handleSelectAllIPButtonClick()">Select-all</button> | |
| <button onclick="addAclStandardIP()" id="applyBtn">Add</button> | |
| <button onclick="deleteStandardAclGroups()" id="deleteBtn">Delete</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| </div> | |
| `); | |
| await aclStandardIPGroupSelect(); | |
| // Add change listener to the dropdown | |
| document.getElementById('aclStandardIPGroupSelect').addEventListener('change', function () { | |
| const selectedGroup = this.value; | |
| if (selectedGroup) { | |
| fetchAclStandardIPData(selectedGroup); | |
| } | |
| }) | |
| } | |
| async function aclStandardIPGroupSelect() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/acl-standard-ip-group`); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| const selectElement = document.getElementById('aclStandardIPGroupSelect'); | |
| selectElement.innerHTML = ''; | |
| const blankOption = document.createElement('option'); | |
| blankOption.value = ''; | |
| blankOption.textContent = ''; | |
| blankOption.disabled = true; | |
| blankOption.selected = true; | |
| blankOption.hidden = true; | |
| selectElement.appendChild(blankOption); | |
| data.aclGroupNum.forEach(group => { | |
| const option = document.createElement('option'); | |
| option.value = group; | |
| option.textContent = group; | |
| selectElement.appendChild(option); | |
| }); | |
| } | |
| } catch (error) { | |
| alert("Error found", error) | |
| } | |
| } | |
| function selectStandardIPAllAcl(source) { | |
| const checkboxes = document.querySelectorAll('.rowCheckbox'); | |
| const isChecked = source.checked; | |
| checkboxes.forEach((checkbox) => { | |
| checkbox.checked = isChecked; | |
| }); | |
| } | |
| function handleSelectAllIPButtonClick() { | |
| const selectAllCheckbox = document.getElementById('selectAllCheckbox'); | |
| selectAllCheckbox.checked = !selectAllCheckbox.checked; | |
| selectStandardIPAllAcl(selectAllCheckbox); | |
| } | |
| async function addAclStandardIP() { | |
| const selectedGroup = document.getElementById('aclStandardIPGroupSelect').value; | |
| const sourceIp = document.getElementById('sourceIp').value.trim(); | |
| const sourceWildcard = document.getElementById('sourceWildcard').value.trim(); | |
| const action = document.querySelector('input[name="action"]:checked').value; | |
| const regex = /^(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)){3}$/; | |
| // Basic empty field check | |
| if (!selectedGroup || !sourceIp || !sourceWildcard) { | |
| return alert('Please fill in all fields.'); | |
| } | |
| if (!regex.test(sourceIp)) { | |
| return alert('Invalid Source IP address format.'); | |
| } | |
| if (!regex.test(sourceWildcard)) { | |
| return alert('Invalid Source Wildcard format.'); | |
| } | |
| const aclData = { | |
| groupNum: selectedGroup, | |
| sourceIp, | |
| sourceWildcard, | |
| action | |
| }; | |
| try { | |
| const response = await fetch(`${BASE_URL}/add-acl-standard-ip`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(aclData) | |
| }); | |
| const result = await response.json(); | |
| if (response.ok) { | |
| // Clear input fields | |
| document.getElementById('aclStandardIPGroupSelect').value = ''; | |
| document.getElementById('sourceIp').value = ''; | |
| document.getElementById('sourceWildcard').value = ''; | |
| document.getElementById('deny').checked = true; | |
| alert('ACL entry added successfully!'); | |
| fetchAclStandardIPData(selectedGroup); | |
| } else { | |
| alert(`Failed to add ACL entry: ${result.message}`); | |
| } | |
| } catch (error) { | |
| alert('Failed to add the new ACL entry.'); | |
| } | |
| } | |
| async function fetchAclStandardIPData(groupNum) { | |
| try { | |
| const url = groupNum | |
| ? `${BASE_URL}/get-acl-standard-ip-list?groupNum=${groupNum}` | |
| : `${BASE_URL}/get-acl-standard-ip-list`; | |
| const response = await fetch(url); | |
| if (!response.ok) throw new Error('Network response was not ok'); | |
| const aclData = await response.json(); | |
| const tableBody = document.getElementById('aclStandardIpTableBody'); | |
| tableBody.innerHTML = ''; | |
| aclData.forEach((entry, index) => { | |
| const row = document.createElement('tr'); | |
| row.innerHTML = ` | |
| <td><input type="checkbox" class="rowCheckbox" data-index="${index}" data-groupnum="${entry.groupNum}" data-sourceip="${entry.sourceIp}"></td> | |
| <td>${entry.groupNum}</td> | |
| <td>${entry.action}</td> | |
| <td>${entry.sourceIp}</td> | |
| <td>${entry.sourceWildcard}</td> | |
| `; | |
| tableBody.appendChild(row); | |
| }); | |
| } catch (error) { | |
| console.error('Error fetching data:', error); | |
| } | |
| } | |
| function deleteStandardAclGroups() { | |
| const checkboxes = document.querySelectorAll('.rowCheckbox:checked'); | |
| const selectAllCheckbox = document.getElementById('selectAllCheckbox'); | |
| let groupNumsToDelete = []; | |
| if (selectAllCheckbox.checked) { | |
| const allRows = document.querySelectorAll('.rowCheckbox'); | |
| groupNumsToDelete = Array.from(allRows).map(checkbox => ({ | |
| groupNum: checkbox.dataset.groupnum | |
| })); | |
| } else { | |
| if (checkboxes.length === 0) { | |
| alert("Please select at least one row to delete."); | |
| return; | |
| } | |
| groupNumsToDelete = Array.from(checkboxes).map(checkbox => ({ | |
| groupNum: checkbox.dataset.groupnum | |
| })); | |
| } | |
| // Get the currently selected group number | |
| const selectedGroup = document.getElementById('aclStandardIPGroupSelect').value; | |
| fetch(`${BASE_URL}/delete-acl-standard-ip`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(groupNumsToDelete) | |
| }) | |
| .then(response => { | |
| if (!response.ok) throw new Error("Failed to delete entries."); | |
| return response.json(); | |
| }) | |
| .then(result => { | |
| alert(result.message || "Selected entries deleted successfully."); | |
| selectAllCheckbox.checked = false; | |
| // Refresh the list with the current group number | |
| fetchAclStandardIPData(selectedGroup); | |
| }) | |
| .catch(error => { | |
| console.error("Error deleting ACL entries:", error); | |
| alert("An error occurred while deleting entries."); | |
| }); | |
| } | |
| // EXTENDED IP | |
| async function extendedIP() { | |
| showContent(` | |
| <h2>ACL Extended IP Configuration</h2> | |
| <hr/> | |
| <div style="font-family: Arial, sans-serif; font-size: 13px;"> | |
| <div style="margin-bottom: 10px;"> | |
| <label style="font-weight: bold;">ACL Extended IP Group Num:</label> | |
| <select id="aclExtendedIPGroupSelect" style="padding: 3px 5px; border: 1px solid #a0a0a0; border-radius: 2px; font-size: 13px; width:75px;"> | |
| </select> | |
| </div> | |
| <div style="display: flex; flex-direction: column; gap: 0px; margin-bottom: 10px; width:600px"> | |
| <div style="display: flex; gap: 20px;"> | |
| <div style="display: flex; align-items: center;"> | |
| <label class="label-style">Source IP</label> | |
| <input type="text" style="width:200px" id="sourceIpInput" /> | |
| </div> | |
| <div style="display: flex; align-items: center;"> | |
| <label class="label-style">Source Wildcard</label> | |
| <input type="text" style="width:200px" id="sourceWildcardInput" /> | |
| </div> | |
| </div> | |
| <div style="display: flex; gap: 20px;"> | |
| <div style="display: flex; align-items: center;"> | |
| <label class="label-style">Destination IP</label> | |
| <input type="text" style="width:200px" id="destinationIpInput"/> | |
| </div> | |
| <div style="display: flex; align-items: center;"> | |
| <label class="label-style">Destination Wildcard</label> | |
| <input type="text" style="width:200px" id="destinationWildcardInput" /> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Protocol Type --> | |
| <div style="display: flex; margin-bottom: 10px; height: 100px;"> | |
| <div style="width: 260px; background: #cce0ff; font-weight: bold; padding: 4px; height: 90px; display: flex; align-items: center;"> | |
| Protocol Type | |
| </div> | |
| <div style="display: flex; flex-direction: column; align-items: flex-start; margin-left:5px;"> | |
| <input type="text" id="protocolTypeInput" /> | |
| <select id="protocolType" style="width: 130px; font-size: 13px; height: 60px;" size="3"> | |
| </select> | |
| </div> | |
| </div> | |
| <!-- Source & Destination Port --> | |
| <div style="display: flex; gap: 30px; margin-bottom: 10px;"> | |
| <div style="display: flex; margin-bottom: 10px; height: 100px;"> | |
| <div style="width: 260px; background: #cce0ff; font-weight: bold; padding: 4px; height: 90px; display: flex; align-items: center;"> | |
| Source Port | |
| </div> | |
| <div class="flex-column-start"> | |
| <input type="text" id="sourcePortInput" /> | |
| <select id="sourcePort" style="width: 130px; font-size: 13px; height: 60px;" size="3"> | |
| </select> | |
| </div> | |
| </div> | |
| <div style="display: flex; margin-bottom: 10px; height: 100px;"> | |
| <div style="width: 260px; background: #cce0ff; font-weight: bold; padding: 4px; height: 90px; display: flex; align-items: center;"> | |
| Destination Port | |
| </div> | |
| <div class="flex-column-start"> | |
| <input type="text" id="destinationPortInput" /> | |
| <select id="destinationPort" style="width: 130px; font-size: 13px; height: 60px;" size="3"> | |
| </select> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- TCP Control Flag --> | |
| <div style="display: flex; margin-bottom: 10px; height: 65px;"> | |
| <div style="width: 260px; background: #cce0ff; font-weight: bold; padding: 4px; height: 50px; display: flex; align-items: center;"> | |
| TCP Control Flag | |
| </div> | |
| <div class="flex-column-start"> | |
| <input type="text" id="tcpFlagsInput" readonly /> | |
| <div style="margin-bottom: 10px;"> | |
| <label style="margin-left: 10px;"><input class="tcp-flag" type="checkbox" value="fin" /> fin</label> | |
| <label style="margin-left: 10px;"><input class="tcp-flag" type="checkbox" value="syn" /> syn</label> | |
| <label style="margin-left: 10px;"><input class="tcp-flag" type="checkbox" value="rst" /> rst</label> | |
| <label style="margin-left: 10px;"><input class="tcp-flag" type="checkbox" value="psh" /> psh</label> | |
| <label style="margin-left: 10px;"><input class="tcp-flag" type="checkbox" value="ack" /> ack</label> | |
| <label style="margin-left: 10px;"><input class="tcp-flag" type="checkbox" value="urg" /> urg</label> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Note --> | |
| <div style="font-size: 11px; color: #333; margin-bottom: 10px;"> | |
| <b>(e.g.: If input IP Address 192.168.1.2, ACL want to control 192.168.1.0, then Wildcard should be 0.0.0.255...)</b> | |
| </div> | |
| <!-- Deny / Permit --> | |
| <div style="margin-bottom: 10px;"> | |
| <label><input type="radio" name="rule" checked /> Deny</label> | |
| <label style="margin-left: 15px;"><input type="radio" name="rule" /> Permit</label> | |
| </div> | |
| <!-- Table --> | |
| <table style="border-collapse: collapse; margin-bottom: 10px;"> | |
| <thead> | |
| <tr> | |
| <th style="border: 1px solid #ccc;"> | |
| <input type="checkbox" id="selectAllCheckbox" onclick="selectStandardIPAllAcl(this)"> | |
| </th> | |
| <th style="border: 1px solid #ccc; font-size:">Group Num</th> | |
| <th style="border: 1px solid #ccc;">Deny/Permit</th> | |
| <th style="border: 1px solid #ccc;">Source IP</th> | |
| <th style="border: 1px solid #ccc;">Source Wildcard</th> | |
| <th style="border: 1px solid #ccc;">Destination IP</th> | |
| <th style="border: 1px solid #ccc;">Destination Wildcard</th> | |
| <th style="border: 1px solid #ccc;">Protocol Type</th> | |
| <th style="border: 1px solid #ccc;">Source Port</th> | |
| <th style="border: 1px solid #ccc;">Destination Port</th> | |
| <th style="border: 1px solid #ccc;">TCP Flag</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| </tbody> | |
| </table> | |
| <!-- Buttons --> | |
| <div style="text-align: center; margin-top: 20px;"> | |
| <button onclick="extendedIP()" id="refreshBtn">Refresh</button> | |
| <button onclick="handleSelectAllIPButtonClick()">Select-all</button> | |
| <button onclick="addAclExtendedIpData()" id="applyBtn">Add</button> | |
| <button onclick="deleteExtendedAclEntries()" id="deleteBtn">Delete</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| </div> | |
| `); | |
| aclExtendedIPGroupSelect(); | |
| fetchAclExtendedIpPort(); | |
| addSelectListeners(); | |
| selectTcpFlagInputExtendedIP(); | |
| const groupSelect = document.getElementById('aclExtendedIPGroupSelect'); | |
| groupSelect.addEventListener('change', function () { | |
| const selectedGroup = this.value; | |
| if (selectedGroup) { | |
| getExtendedIPData(selectedGroup); | |
| } | |
| }); | |
| } | |
| async function aclExtendedIPGroupSelect() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/acl-extended-ip-group`); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| const selectElement = document.getElementById('aclExtendedIPGroupSelect'); | |
| selectElement.innerHTML = ''; | |
| // Add a blank default option | |
| const blankOption = document.createElement('option'); | |
| blankOption.value = ''; | |
| blankOption.textContent = ''; // show nothing | |
| blankOption.disabled = true; | |
| blankOption.selected = true; | |
| blankOption.hidden = true; | |
| selectElement.appendChild(blankOption); | |
| // Add actual group options | |
| data.aclextendedGroupNum.forEach(group => { | |
| const option = document.createElement('option'); | |
| option.value = group; | |
| option.textContent = group; | |
| selectElement.appendChild(option); | |
| }); | |
| } else { | |
| console.error('Failed to fetch ACL group numbers:', response.statusText); | |
| } | |
| } catch (error) { | |
| console.error('Error fetching ACL group numbers:', error); | |
| } | |
| } | |
| function portSelectElementEXtendedIp(selectId, dataArray) { | |
| const selectElement = document.getElementById(selectId); | |
| if (!selectElement) return; | |
| selectElement.innerHTML = ''; | |
| dataArray.forEach(item => { | |
| const option = new Option(item, item); | |
| selectElement.appendChild(option); | |
| }); | |
| } | |
| async function fetchAclExtendedIpPort() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/extended-port-options`); | |
| if (!response.ok) throw new Error(response.statusText); | |
| const data = await response.json(); | |
| portSelectElementEXtendedIp('protocolType', data.protocolType); | |
| portSelectElementEXtendedIp('sourcePort', data.sourcePort); | |
| portSelectElementEXtendedIp('destinationPort', data.destinationPort); | |
| } catch (err) { | |
| console.error('Fetch error:', err); | |
| } | |
| } | |
| function selectAndShowInputExtendedIP(selectId, inputId) { | |
| const select = document.getElementById(selectId); | |
| const input = document.getElementById(inputId); | |
| if (select && input) { | |
| select.addEventListener('change', () => { | |
| input.value = select.value; | |
| }); | |
| } | |
| } | |
| function addSelectListeners() { | |
| selectAndShowInputExtendedIP('protocolType', 'protocolTypeInput'); | |
| selectAndShowInputExtendedIP('sourcePort', 'sourcePortInput'); | |
| selectAndShowInputExtendedIP('destinationPort', 'destinationPortInput'); | |
| } | |
| function selectTcpFlagInputExtendedIP() { | |
| const checkboxes = document.querySelectorAll('.tcp-flag'); | |
| const input = document.getElementById('tcpFlagsInput'); | |
| function updateInputValue() { | |
| const selected = Array.from(checkboxes) | |
| .filter(cb => cb.checked) | |
| .map(cb => cb.value) | |
| .join(', '); | |
| input.value = selected; | |
| } | |
| checkboxes.forEach(cb => cb.addEventListener('change', updateInputValue)); | |
| } | |
| async function getExtendedIPData(groupNum) { | |
| console.log("groupNum", groupNum) | |
| try { | |
| const url = groupNum | |
| ? `${BASE_URL}/get-extended-ip-list?groupNum=${groupNum}` | |
| : `${BASE_URL}/get-extended-ip-list`; | |
| const response = await fetch(url); | |
| const data = await response.json(); | |
| const tbody = document.querySelector('table tbody'); | |
| tbody.innerHTML = ''; | |
| data.forEach((entry, index) => { | |
| const row = document.createElement('tr'); | |
| row.innerHTML = ` | |
| <td style="border: 1px solid #ccc;"> | |
| <input type="checkbox" class="rowCheckbox" data-index="${index}"/> | |
| </td> | |
| <td style="border: 1px solid #ccc;">${entry.groupNum}</td> | |
| <td style="border: 1px solid #ccc;">${entry.action}</td> | |
| <td style="border: 1px solid #ccc;">${entry.sourceIp}</td> | |
| <td style="border: 1px solid #ccc;">${entry.sourceWildcard}</td> | |
| <td style="border: 1px solid #ccc;">${entry.destinationIp}</td> | |
| <td style="border: 1px solid #ccc;">${entry.destinationWildcard}</td> | |
| <td style="border: 1px solid #ccc;">${entry.protocolType}</td> | |
| <td style="border: 1px solid #ccc;">${entry.sourcePort}</td> | |
| <td style="border: 1px solid #ccc;">${entry.destinationPort}</td> | |
| <td style="border: 1px solid #ccc;">${entry.tcpFlags}</td> | |
| `; | |
| tbody.appendChild(row); | |
| }); | |
| } catch (error) { | |
| console.error('Error fetching ACL data:', error); | |
| } | |
| } | |
| async function addAclExtendedIpData() { | |
| const groupNum = document.getElementById('aclExtendedIPGroupSelect')?.value.trim(); | |
| const sourceIp = document.getElementById('sourceIpInput')?.value.trim(); | |
| const sourceWildcard = document.getElementById('sourceWildcardInput')?.value.trim(); | |
| const destinationIp = document.getElementById('destinationIpInput')?.value.trim(); | |
| const destinationWildcard = document.getElementById('destinationWildcardInput')?.value.trim(); | |
| const protocolInput = document.getElementById('protocolTypeInput')?.value.trim(); | |
| const protocolSelect = document.getElementById('protocolType')?.value.trim(); | |
| const protocolType = protocolInput || protocolSelect; | |
| const sourcePortInput = document.getElementById('sourcePortInput')?.value.trim(); | |
| const sourcePortSelect = document.getElementById('sourcePort')?.value.trim(); | |
| const sourcePort = sourcePortInput || sourcePortSelect; | |
| const destPortInput = document.getElementById('destinationPortInput')?.value.trim(); | |
| const destPortSelect = document.getElementById('destinationPort')?.value.trim(); | |
| const destinationPort = destPortInput || destPortSelect; | |
| const tcpFlags = document.getElementById('tcpFlagsInput')?.value.trim(); | |
| const action = document.querySelector('input[name="rule"]:checked')?.nextSibling?.textContent.trim().toLowerCase(); | |
| // Regex for IPv4 format | |
| const ipRegex = /^(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)){3}$/; | |
| // Validation | |
| if (!groupNum) return alert("Please select a Group Number."); | |
| if (!sourceIp || !ipRegex.test(sourceIp)) return alert("Invalid or missing Source IP."); | |
| if (!sourceWildcard || !ipRegex.test(sourceWildcard)) return alert("Invalid or missing Source Wildcard."); | |
| if (!destinationIp || !ipRegex.test(destinationIp)) return alert("Invalid or missing Destination IP."); | |
| if (!destinationWildcard || !ipRegex.test(destinationWildcard)) return alert("Invalid or missing Destination Wildcard."); | |
| if (!protocolType) return alert("Please enter or select a Protocol Type."); | |
| if (!sourcePort || !isNaN(sourcePort)) return alert("Source Port must be a valid name (not a number)."); | |
| if (!destinationPort || !isNaN(destinationPort)) return alert("Destination Port must be a valid name (not a number)."); | |
| if (!tcpFlags) return alert("Please select at least one TCP Flag."); | |
| if (!action) return alert("Please choose Deny or Permit."); | |
| const data = { | |
| groupNum, | |
| protocolType, | |
| sourcePort, | |
| destinationPort, | |
| tcpFlags, | |
| sourceIp, | |
| sourceWildcard, | |
| destinationIp, | |
| destinationWildcard, | |
| action | |
| }; | |
| try { | |
| const response = await fetch(`${BASE_URL}/add-acl-extended-ip`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(data) | |
| }); | |
| const result = await response.json(); | |
| if (response.ok) { | |
| alert('ACL saved successfully!'); | |
| await getExtendedIPData(groupNum); | |
| // Reset form | |
| document.getElementById('sourceIpInput').value = ''; | |
| document.getElementById('sourceWildcardInput').value = ''; | |
| document.getElementById('destinationIpInput').value = ''; | |
| document.getElementById('destinationWildcardInput').value = ''; | |
| document.getElementById('protocolTypeInput').value = ''; | |
| document.getElementById('sourcePortInput').value = ''; | |
| document.getElementById('destinationPortInput').value = ''; | |
| document.getElementById('tcpFlagsInput').value = ''; | |
| document.getElementById('protocolType').selectedIndex = -1; | |
| document.getElementById('sourcePort').selectedIndex = -1; | |
| document.getElementById('destinationPort').selectedIndex = -1; | |
| document.getElementById('aclExtendedIPGroupSelect').selectedIndex = 0; | |
| document.querySelectorAll('input.tcp-flag[type="checkbox"]').forEach(cb => cb.checked = false); | |
| document.querySelectorAll('input[name="rule"]').forEach((radio, idx) => { | |
| radio.checked = idx === 0; | |
| }); | |
| } else { | |
| alert('Failed to save ACL: ' + (result.message || 'Unknown error')); | |
| } | |
| } catch (error) { | |
| alert('Error saving ACL: ' + error.message); | |
| } | |
| } | |
| async function deleteExtendedAclEntries() { | |
| const checkboxes = document.querySelectorAll(".rowCheckbox"); | |
| const rows = document.querySelectorAll("tbody tr"); | |
| const entriesToDelete = Array.from(rows) | |
| .filter((row, index) => checkboxes[index]?.checked) | |
| .map(row => { | |
| const cells = row.querySelectorAll("td"); | |
| return { | |
| groupNum: cells[1].innerText.trim(), | |
| rule: cells[2].innerText.trim(), | |
| sourceIp: cells[3].innerText.trim(), | |
| sourceWildcard: cells[4].innerText.trim(), | |
| destinationIp: cells[5].innerText.trim(), | |
| destinationWildcard: cells[6].innerText.trim(), | |
| protocol: cells[7].innerText.trim(), | |
| portOperator: cells[8].innerText.trim(), | |
| portNumber: cells[9].innerText.trim() | |
| }; | |
| }); | |
| if (entriesToDelete.length === 0) { | |
| alert("Please select at least one entry to delete."); | |
| return; | |
| } | |
| try { | |
| const res = await fetch(`${BASE_URL}/delete-acl-extended-ip`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ entries: entriesToDelete }) | |
| }); | |
| const result = await res.json(); | |
| alert(result.message); | |
| document.getElementById("selectAllCheckbox").checked = false; | |
| getExtendedIPData(entriesToDelete[0].groupNum); | |
| } catch (error) { | |
| alert("Error deleting entries."); | |
| } | |
| } | |
| /// MAC IP // | |
| async function macIP() { | |
| showContent(` | |
| <h2>ACL MAC IP Configuration</h2> | |
| <hr/> | |
| <div style="font-family: Arial, sans-serif; font-size: 13px;"> | |
| <div style="margin-bottom: 10px;"> | |
| <label style="font-weight: bold;">ACL MAC IP Group Num:</label> | |
| <select id="aclMicIPGroupSelect" style="padding: 3px 5px; border: 1px solid #a0a0a0; border-radius: 2px; font-size: 13px; width: 70px;"></select> | |
| </div> | |
| <div style="display: flex; flex-direction: column; gap: 0px; margin-bottom: 10px; width:600px"> | |
| <div style="display: flex; gap: 20px;"> | |
| <div style="display: flex; align-items: center;"> | |
| <div style="width: 200px; background: #cce0ff; font-weight: bold; padding: 6px;">Source MAC</div> | |
| <input type="text" id="srcMac" style="width:200px" /> | |
| </div> | |
| <div style="display: flex; align-items: center;"> | |
| <div style="width: 200px; background: #cce0ff; font-weight: bold; padding: 6px;">Source MAC Wildcard</div> | |
| <input type="text" id="srcMacWildcard" style="width:200px" /> | |
| </div> | |
| </div> | |
| <div style="display: flex; gap: 20px;"> | |
| <div style="display: flex; align-items: center;"> | |
| <div style="width: 200px; background: #cce0ff; font-weight: bold; padding: 6px;">Source IP</div> | |
| <input type="text" id="srcIp" style="width:200px" /> | |
| </div> | |
| <div style="display: flex; align-items: center;"> | |
| <div style="width: 200px; background: #cce0ff; font-weight: bold; padding: 6px;">Source IP Wildcard</div> | |
| <input type="text" id="srcIpWildcard" style="width:200px" /> | |
| </div> | |
| </div> | |
| <div style="display: flex; gap: 20px;"> | |
| <div style="display: flex; align-items: center;"> | |
| <div style="width: 200px; background: #cce0ff; font-weight: bold; padding: 6px;">Destination IP</div> | |
| <input type="text" id="dstIp" style="width:200px" /> | |
| </div> | |
| <div style="display: flex; align-items: center;"> | |
| <div style="width: 200px; background: #cce0ff; font-weight: bold; padding: 6px;">Destination IP Wildcard</div> | |
| <input type="text" id="dstIpWildcard" style="width:200px" /> | |
| </div> | |
| </div> | |
| <div style="display: flex; gap: 20px;"> | |
| <div style="display: flex; align-items: center;"> | |
| <div style="width: 200px; background: #cce0ff; font-weight: bold; padding: 6px;">VLAN IP</div> | |
| <input type="text" id="vlanIp" value="0" style="width:200px" /> | |
| </div> | |
| <div style="display: flex; align-items: center;"> | |
| <div style="width: 200px; font-weight: bold; padding: 6px;">(0-4094,0 means all VLAN)</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div style="font-size: 11px; color: #333; margin-bottom: 10px;"> | |
| <b>(e.g.: If input IP Address 192.168.1.2, ACL want to control 192.168.1.0, then Wildcard should be 0.0.0.255...)</b> | |
| </div> | |
| <div style="margin-bottom: 10px;"> | |
| <label><input type="radio" name="rule" value="deny" checked /> Deny</label> | |
| <label style="margin-left: 15px;"><input type="radio" name="rule" value="permit" /> Permit</label> | |
| </div> | |
| <table style="border-collapse: collapse; margin-bottom: 10px; width:100%;font-size:10px"> | |
| <thead> | |
| <tr> | |
| <th style="border: 1px solid #ccc; "> | |
| <input type="checkbox" id="selectAllCheckbox" onclick="selectStandardIPAllAcl(this)"> | |
| </th> | |
| <th style="border: 1px solid #ccc; ">Group Num</th> | |
| <th style="border: 1px solid #ccc;">Deny/Permit</th> | |
| <th style="border: 1px solid #ccc;">Source MAC</th> | |
| <th style="border: 1px solid #ccc;">Source MAC Wildcard</th> | |
| <th style="border: 1px solid #ccc;">Protocol Type</th> | |
| <th style="border: 1px solid #ccc;">Source IP</th> | |
| <th style="border: 1px solid #ccc;">Source IP Wildcard</th> | |
| <th style="border: 1px solid #ccc;">Destination IP</th> | |
| <th style="border: 1px solid #ccc;">Destination IP Wildcard</th> | |
| <th style="border: 1px solid #ccc;">VLAN ID</th> | |
| </tr> | |
| </thead> | |
| <tbody id="aclMacIpTableBody"></tbody> | |
| </table> | |
| <div style="text-align: center; margin-top: 20px;"> | |
| <button onclick="macIP()" id="refreshBtn">Refresh</button> | |
| <button onclick="handleSelectAllIPButtonClick()">Select-all</button> | |
| <button onclick="addMacIP()" id="applyBtn">Add</button> | |
| <button onclick="deleteAclMacIp()" id="deleteBtn">Delete</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| </div> | |
| `); | |
| await aclMicIPGroupSelect(); | |
| const groupSelect = document.getElementById('aclMicIPGroupSelect'); | |
| groupSelect.addEventListener('change', function () { | |
| const selectedGroup = this.value; | |
| if (selectedGroup) { | |
| getAclMacIpData(selectedGroup); | |
| } | |
| }); | |
| } | |
| async function aclMicIPGroupSelect() { | |
| try { | |
| // Fetch ACL Group data from the server | |
| const response = await fetch(`${BASE_URL}/acl-mic-ip-group`); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| const selectElement = document.getElementById('aclMicIPGroupSelect'); | |
| selectElement.innerHTML = ''; | |
| // Add a blank default option | |
| const blankOption = document.createElement('option'); | |
| blankOption.value = ''; | |
| blankOption.textContent = ''; // show nothing | |
| blankOption.disabled = true; | |
| blankOption.selected = true; | |
| blankOption.hidden = true; | |
| selectElement.appendChild(blankOption); | |
| data.aclmicIpGroupNum.forEach(group => { | |
| const option = document.createElement('option'); | |
| option.value = group; | |
| option.textContent = group; | |
| selectElement.appendChild(option); | |
| }); | |
| } else { | |
| console.error('Failed to fetch ACL group numbers:', response.statusText); | |
| } | |
| } catch (error) { | |
| console.error('Error fetching ACL group numbers:', error); | |
| } | |
| } | |
| async function addMacIP() { | |
| const groupNum = document.getElementById("aclMicIPGroupSelect").value.trim(); | |
| const sourceMac = document.getElementById("srcMac").value.trim(); | |
| const sourceMacWildcard = document.getElementById("srcMacWildcard").value.trim(); | |
| const sourceIp = document.getElementById("srcIp").value.trim(); | |
| const sourceIpWildcard = document.getElementById("srcIpWildcard").value.trim(); | |
| const destinationIp = document.getElementById("dstIp").value.trim(); | |
| const destinationIpWildcard = document.getElementById("dstIpWildcard").value.trim(); | |
| const vlanIp = document.getElementById("vlanIp").value.trim(); | |
| const rule = document.querySelector('input[name="rule"]:checked')?.value; | |
| if ( | |
| !groupNum || !sourceMac || !sourceMacWildcard || | |
| !sourceIp || !sourceIpWildcard || !destinationIp || | |
| !destinationIpWildcard || vlanIp === "" || !rule | |
| ) { | |
| alert("All fields are required."); | |
| return; | |
| } | |
| const macRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/; | |
| const ipRegex = /^(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)){3}$/; | |
| if (!macRegex.test(sourceMac)) { | |
| alert("Invalid Source MAC address format."); | |
| return; | |
| } | |
| if (!macRegex.test(sourceMacWildcard)) { | |
| alert("Invalid Source MAC Wildcard format."); | |
| return; | |
| } | |
| if (!ipRegex.test(sourceIp)) { | |
| alert("Invalid Source IP format."); | |
| return; | |
| } | |
| if (!ipRegex.test(sourceIpWildcard)) { | |
| alert("Invalid Source IP Wildcard format."); | |
| return; | |
| } | |
| if (!ipRegex.test(destinationIp)) { | |
| alert("Invalid Destination IP format."); | |
| return; | |
| } | |
| if (!ipRegex.test(destinationIpWildcard)) { | |
| alert("Invalid Destination IP Wildcard format."); | |
| return; | |
| } | |
| const data = { | |
| groupNum, | |
| sourceMac, | |
| sourceMacWildcard, | |
| sourceIp, | |
| sourceIpWildcard, | |
| destinationIp, | |
| destinationIpWildcard, | |
| vlanIp, | |
| rule, | |
| }; | |
| try { | |
| const response = await fetch(`${BASE_URL}/add-acl-mac-ip`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(data), | |
| }); | |
| const result = await response.json(); | |
| if (response.ok) { | |
| alert("Submitted successfully: " + result.message); | |
| // ✅ Fixed: reload data for selected group | |
| await getAclMacIpData(groupNum); | |
| // Clear form | |
| document.getElementById("srcMac").value = ""; | |
| document.getElementById("srcMacWildcard").value = ""; | |
| document.getElementById("srcIp").value = ""; | |
| document.getElementById("srcIpWildcard").value = ""; | |
| document.getElementById("dstIp").value = ""; | |
| document.getElementById("dstIpWildcard").value = ""; | |
| document.getElementById("vlanIp").value = "0"; | |
| document.querySelector('input[name="rule"][value="deny"]').checked = true; | |
| } else { | |
| alert("Failed to add: " + result.message); | |
| } | |
| } catch (err) { | |
| alert("Error saving ACL: " + err.message); | |
| } | |
| } | |
| async function getAclMacIpData(groupNum) { | |
| try { | |
| const response = await fetch(`${BASE_URL}/get-acl-mac-ip-list?groupNum=${groupNum}`); | |
| const data = await response.json(); | |
| const tbody = document.getElementById('aclMacIpTableBody'); | |
| tbody.innerHTML = ''; | |
| data.forEach((entry, index) => { | |
| const row = document.createElement("tr"); | |
| row.innerHTML = ` | |
| <td><input type="checkbox" class="rowCheckbox" data-index="${index}" /></td> | |
| <td>${entry.groupNum}</td> | |
| <td>${entry.rule}</td> | |
| <td>${entry.sourceMac}</td> | |
| <td>${entry.sourceMacWildcard}</td> | |
| <td>${entry.protocolType || "ip"}</td> | |
| <td>${entry.sourceIp}</td> | |
| <td>${entry.sourceIpWildcard}</td> | |
| <td>${entry.destinationIp}</td> | |
| <td>${entry.destinationIpWildcard}</td> | |
| <td>${entry.vlanIp}</td> | |
| `; | |
| // Apply the same class to all td elements in the row | |
| row.querySelectorAll("td").forEach(td => { | |
| td.classList.add("lldpPortTableRowData"); | |
| }); | |
| tbody.appendChild(row); | |
| }); | |
| } catch (error) { | |
| console.error('Error fetching ACL MAC IP data:', error); | |
| } | |
| } | |
| async function deleteAclMacIp() { | |
| const selectedRows = Array.from(document.querySelectorAll('.rowCheckbox:checked')); | |
| if (selectedRows.length === 0) { | |
| alert("No entries selected for deletion."); | |
| return; | |
| } | |
| const groupNumsToDelete = [ | |
| ...new Set( | |
| selectedRows.map(row => { | |
| const rowData = row.closest("tr").querySelectorAll("td"); | |
| return rowData[1].innerText.trim(); | |
| }) | |
| ) | |
| ]; | |
| try { | |
| const response = await fetch(`${BASE_URL}/delete-acl-mac-ip`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ groupNums: groupNumsToDelete }) | |
| }); | |
| const result = await response.json(); | |
| if (response.ok) { | |
| alert("Deleted successfully!"); | |
| const currentGroup = document.getElementById("aclMicIPGroupSelect").value.trim(); | |
| await getAclMacIpData(currentGroup); | |
| } else { | |
| alert("Failed to delete: " + result.message); | |
| } | |
| } catch (err) { | |
| alert("Error deleting entries: " + err.message); | |
| } | |
| } | |
| // MAC ARP | |
| async function macARP() { | |
| showContent(` | |
| <h2>ACL MAC ARP Configuration</h2> | |
| <hr/> | |
| <div style="font-family: Arial, sans-serif; font-size: 13px;"> | |
| <div style="margin-bottom: 10px;"> | |
| <label style="font-weight: bold;">ACL MAC ARP Group Num: </label> | |
| <select id="aclMicARPGroupSelect" style="padding: 3px 5px; border: 1px solid #a0a0a0; border-radius: 2px; font-size: 13px; width: 70px;"></select> | |
| </div> | |
| <div style="display: flex; flex-direction: column; gap: 0px; margin-bottom: 10px; width:600px"> | |
| <div style="display: flex; gap: 20px;"> | |
| <div style="display: flex; align-items: center;"> | |
| <div style="width: 200px; background: #cce0ff; font-weight: bold; padding: 6px;">Source MAC</div> | |
| <input type="text" id="srcMac" style="width:200px" /> | |
| </div> | |
| <div style="display: flex; align-items: center;"> | |
| <div style="width: 200px; background: #cce0ff; font-weight: bold; padding: 6px;">Source MAC Wildcard</div> | |
| <input type="text" id="srcMacWildcard" style="width:200px" /> | |
| </div> | |
| </div> | |
| <div style="display: flex; gap: 20px;"> | |
| <div style="display: flex; align-items: center;"> | |
| <div style="width: 200px; background: #cce0ff; font-weight: bold; padding: 6px;">Source IP</div> | |
| <input type="text" id="srcIp" style="width:200px" /> | |
| </div> | |
| <div style="display: flex; align-items: center;"> | |
| <div style="width: 200px; background: #cce0ff; font-weight: bold; padding: 6px;">Source IP Wildcard</div> | |
| <input type="text" id="srcIpWildcard" style="width:200px" /> | |
| </div> | |
| </div> | |
| </div> | |
| <div style="font-size: 11px; color: #333; margin-bottom: 10px;"> | |
| <b>(e.g.: If input IP Address 192.168.1.2, ACL want to control 192.168.1.0, then Wildcard should be 0.0.0.255...)</b> | |
| </div> | |
| <div style="margin-bottom: 10px;"> | |
| <label><input type="radio" name="rule" value="deny" checked /> Deny</label> | |
| <label style="margin-left: 15px;"><input type="radio" name="rule" value="permit" /> Permit</label> | |
| </div> | |
| <table style="border-collapse: collapse; margin-bottom: 10px; font-size:10px"> | |
| <thead> | |
| <tr> | |
| <th style="border: 1px solid #ccc; text-align: center;"> | |
| <input type="checkbox" id="selectAllCheckbox" onclick="selectStandardIPAllAcl(this)"> | |
| </th> | |
| <th style="border: 1px solid #ccc; text-align: center; ;">Group Num</th> | |
| <th style="border: 1px solid #ccc; text-align: center; ">Deny/Permit</th> | |
| <th style="border: 1px solid #ccc; text-align: center; ">Source MAC</th> | |
| <th style="border: 1px solid #ccc; text-align: center; ">Source MAC Wildcard</th> | |
| <th style="border: 1px solid #ccc; text-align: center; ">Sender IP</th> | |
| <th style="border: 1px solid #ccc; text-align: center; ">Sender IP Wildcard</th> | |
| </tr> | |
| </thead> | |
| <tbody id="aclMacARPTableBody"></tbody> | |
| </table> | |
| <div style="text-align: center; margin-top: 20px;"> | |
| <button onclick="macARP()" id="refreshBtn">Refresh</button> | |
| <button onclick="handleSelectAllIPButtonClick()">Select-all</button> | |
| <button onclick="addmacARP()" id="applyBtn">Add</button> | |
| <button onclick="deleteMacARP()" id="deleteBtn">Delete</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| </div> | |
| `); | |
| await aclMicARPGroupSelect(); | |
| const groupSelect = document.getElementById("aclMicARPGroupSelect"); | |
| groupSelect.addEventListener("change", () => { | |
| const groupNum = groupSelect.value.trim(); | |
| fetchMacARPData(groupNum); | |
| }); | |
| } | |
| async function aclMicARPGroupSelect() { | |
| try { | |
| // Fetch ACL Group data from the server | |
| const response = await fetch(`${BASE_URL}/acl-mic-arp-group`); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| const selectElement = document.getElementById('aclMicARPGroupSelect'); | |
| selectElement.innerHTML = ''; | |
| // Add a blank default option | |
| const blankOption = document.createElement('option'); | |
| blankOption.value = ''; | |
| blankOption.textContent = ''; // show nothing | |
| blankOption.disabled = true; | |
| blankOption.selected = true; | |
| blankOption.hidden = true; | |
| selectElement.appendChild(blankOption); | |
| data.aclmicARPGroupNum.forEach(group => { | |
| const option = document.createElement('option'); | |
| option.value = group; | |
| option.textContent = group; | |
| selectElement.appendChild(option); | |
| }); | |
| } else { | |
| console.error('Failed to fetch ACL group numbers:', response.statusText); | |
| } | |
| } catch (error) { | |
| console.error('Error fetching ACL group numbers:', error); | |
| } | |
| } | |
| async function addmacARP() { | |
| const groupNum = document.getElementById("aclMicARPGroupSelect").value.trim(); | |
| const srcMac = document.getElementById("srcMac").value.trim(); | |
| const srcMacWildcard = document.getElementById("srcMacWildcard").value.trim(); | |
| const srcIp = document.getElementById("srcIp").value.trim(); | |
| const srcIpWildcard = document.getElementById("srcIpWildcard").value.trim(); | |
| const rule = document.querySelector('input[name="rule"]:checked')?.value; | |
| const macRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/; | |
| const ipRegex = /^(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)){3}$/; | |
| // Validation checks | |
| if (!groupNum || !srcMac || !srcMacWildcard || !srcIp || !srcIpWildcard || !rule) { | |
| alert("All fields are required."); | |
| return; | |
| } | |
| if (!macRegex.test(srcMac)) { | |
| alert("Invalid Source MAC address format."); | |
| return; | |
| } | |
| if (!macRegex.test(srcMacWildcard)) { | |
| alert("Invalid Source MAC Wildcard format."); | |
| return; | |
| } | |
| if (!ipRegex.test(srcIp)) { | |
| alert("Invalid Source IP format."); | |
| return; | |
| } | |
| if (!ipRegex.test(srcIpWildcard)) { | |
| alert("Invalid Source IP Wildcard format."); | |
| return; | |
| } | |
| const payload = { | |
| groupNum, | |
| srcMac, | |
| srcMacWildcard, | |
| srcIp, | |
| srcIpWildcard, | |
| rule | |
| }; | |
| try { | |
| const response = await fetch(`${BASE_URL}/add-acl-mac-arp`, { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json" | |
| }, | |
| body: JSON.stringify(payload) | |
| }); | |
| const result = await response.json(); | |
| if (!response.ok) throw new Error(result.message || "Failed to add entry"); | |
| alert("MAC ARP ACL Entry Added Successfully"); | |
| fetchMacARPData(groupNum); | |
| // Clear input fields | |
| document.getElementById("srcMac").value = ""; | |
| document.getElementById("srcMacWildcard").value = ""; | |
| document.getElementById("srcIp").value = ""; | |
| document.getElementById("srcIpWildcard").value = ""; | |
| } catch (error) { | |
| alert("Error adding entry: " + error.message); | |
| } | |
| } | |
| async function fetchMacARPData(groupNum) { | |
| try { | |
| const url = groupNum | |
| ? `${BASE_URL}/get-acl-mac-arp-list?groupNum=${encodeURIComponent(groupNum)}` | |
| : `${BASE_URL}/get-acl-mac-arp-list`; | |
| const response = await fetch(url); | |
| const data = await response.json(); | |
| const tbody = document.getElementById('aclMacARPTableBody'); | |
| tbody.innerHTML = ""; | |
| data.forEach(entry => { | |
| const row = document.createElement("tr"); | |
| row.innerHTML = ` | |
| <td style="border: 1px solid #ccc; text-align: center; font-size: 14px;"> | |
| <input type="checkbox" class="rowCheckbox"> | |
| </td> | |
| <td style="border: 1px solid #ccc; text-align: center; font-size: 14px;">${entry.groupNum}</td> | |
| <td style="border: 1px solid #ccc; text-align: center; font-size: 14px;">${entry.rule}</td> | |
| <td style="border: 1px solid #ccc; text-align: center; font-size: 14px;">${entry.srcMac}</td> | |
| <td style="border: 1px solid #ccc; text-align: center; font-size: 14px;">${entry.srcMacWildcard}</td> | |
| <td style="border: 1px solid #ccc; text-align: center; font-size: 14px;">${entry.srcIp}</td> | |
| <td style="border: 1px solid #ccc; text-align: center; font-size: 14px;">${entry.srcIpWildcard}</td> | |
| `; | |
| tbody.appendChild(row); | |
| }); | |
| } catch (err) { | |
| console.error("Fetch MAC ARP data error:", err); | |
| } | |
| } | |
| async function deleteMacARP() { | |
| const checkboxes = document.querySelectorAll('.rowCheckbox:checked'); | |
| if (checkboxes.length === 0) { | |
| alert("Please select entries to delete."); | |
| return; | |
| } | |
| const tbody = document.getElementById('aclMacARPTableBody'); | |
| const entriesToDelete = []; | |
| checkboxes.forEach(cb => { | |
| const row = cb.closest('tr'); | |
| const cells = row.querySelectorAll('td'); | |
| entriesToDelete.push({ | |
| groupNum: cells[1].innerText.trim(), | |
| rule: cells[2].innerText.trim(), | |
| srcMac: cells[3].innerText.trim(), | |
| srcMacWildcard: cells[4].innerText.trim(), | |
| srcIp: cells[5].innerText.trim(), | |
| srcIpWildcard: cells[6].innerText.trim() | |
| }); | |
| }); | |
| try { | |
| const response = await fetch(`${BASE_URL}/delete-acl-mac-arp`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ entries: entriesToDelete }) | |
| }); | |
| const result = await response.json(); | |
| if (!response.ok) throw new Error(result.message || "Failed to delete"); | |
| alert("Selected MAC ARP ACL entries deleted successfully."); | |
| const groupNum = document.getElementById("aclMicARPGroupSelect").value.trim(); | |
| document.getElementById("selectAllCheckbox").checked = false; | |
| fetchMacARPData(groupNum); | |
| } catch (err) { | |
| alert("Error deleting entries: " + err.message); | |
| } | |
| } | |
| // ACL Information // | |
| async function aclInformation() { | |
| showContent(` | |
| <h2>ACL Information</h2><hr/> | |
| <div id="aclStandardContent" style="white-space: pre-wrap; font-family: monospace;"></div> | |
| <div id="aclExtendedContent" style="white-space: pre-wrap; font-family: monospace; margin-top: 30px;"></div> | |
| <div id="aclMacIpContent" style="white-space: pre-wrap; font-family: monospace; margin-top: 30px;"></div> | |
| <div id="aclMacArpContent" style="white-space: pre-wrap; font-family: monospace; margin-top: 30px;"></div> | |
| <div style="text-align:center; margin-top:20px;"> | |
| <button onclick="aclInformation()">Refresh</button> | |
| <button onclick="showHelp()">Help</button> | |
| </div> | |
| `); | |
| const groupNum = document.getElementById("groupNum")?.value; | |
| const fetchAndRender = async (endpoint, containerId, formatter) => { | |
| try { | |
| const url = `${BASE_URL}/${endpoint}${groupNum ? `?groupNum=${encodeURIComponent(groupNum)}` : ''}`; | |
| const res = await fetch(url); | |
| if (!res.ok) throw new Error(); | |
| const data = await res.json(); | |
| document.getElementById(containerId).textContent = formatter(data); | |
| } catch { | |
| document.getElementById(containerId).textContent = `Error loading ${containerId.replace("acl", "").replace("Content", "")} ACL Information.`; | |
| } | |
| }; | |
| const formatStandard = (data) => { | |
| const grouped = data.reduce((acc, e) => { | |
| const line = ` ${e.action} ${e.sourceIp}${e.sourceWildcard !== "0.0.0.0" ? " " + e.sourceWildcard : ""}`; | |
| (acc[e.groupNum] ||= []).push(line); | |
| return acc; | |
| }, {}); | |
| return Object.entries(grouped) | |
| .map(([g, rules]) => `Standard IP access list ${g},\n${rules.join("\n")}`).join("\n\n") || "No Standard ACL entries found."; | |
| }; | |
| const formatExtended = (data) => { | |
| const grouped = data.reduce((acc, e) => { | |
| const parts = [ | |
| ` ${e.action} ${e.protocol}`, | |
| `${e.sourceIp}${e.sourceWildcard !== "0.0.0.0" ? " " + e.sourceWildcard : ""}`, | |
| `${e.destinationIp}${e.destinationWildcard !== "0.0.0.0" ? " " + e.destinationWildcard : ""}`, | |
| e.sourcePort ? `${e.operator || ""} ${e.sourcePort}` : "", | |
| e.destinationPort ? `${e.operator || ""} ${e.destinationPort}` : "" | |
| ].filter(Boolean).join(" "); | |
| (acc[e.groupNum] ||= []).push(parts); | |
| return acc; | |
| }, {}); | |
| return Object.entries(grouped) | |
| .map(([g, rules]) => `Extended IP access list ${g},\n${rules.join("\n")}`).join("\n\n") || "No Extended ACL entries found."; | |
| }; | |
| const formatMacIp = (data) => { | |
| const grouped = data.reduce((acc, e) => { | |
| const line = ` ${e.rule} ${e.sourceMac}` + | |
| (e.sourceMacWildcard !== "00:00:00:00:00:00" ? ` ${e.sourceMacWildcard}` : "") + | |
| ` -> ${e.destinationIp}` + | |
| (e.destinationIpWildcard !== "0.0.0.0" ? ` ${e.destinationIpWildcard}` : "") + | |
| (e.protocolType ? ` protocol ${e.protocolType}` : "") + | |
| (e.sourceIp ? ` source-ip ${e.sourceIp}` : "") + | |
| (e.sourceIpWildcard !== "0.0.0.0" ? ` ${e.sourceIpWildcard}` : "") + | |
| (e.vlanIp ? ` vlan ${e.vlanIp}` : ""); | |
| (acc[e.groupNum] ||= []).push(line); | |
| return acc; | |
| }, {}); | |
| return Object.entries(grouped) | |
| .map(([g, rules]) => `MAC/IP access list ${g},\n${rules.join("\n")}`).join("\n\n"); | |
| }; | |
| const formatMacArp = (data) => { | |
| const grouped = data.reduce((acc, e) => { | |
| const line = ` ${e.rule} ${e.srcMac}` + | |
| (e.srcMacWildcard !== "00:00:00:00:00:00" ? ` ${e.srcMacWildcard}` : "") + | |
| ` -> ${e.srcIp}` + | |
| (e.srcIpWildcard !== "0.0.0.0" ? ` ${e.srcIpWildcard}` : ""); | |
| (acc[e.groupNum] ||= []).push(line); | |
| return acc; | |
| }, {}); | |
| return Object.entries(grouped) | |
| .map(([g, rules]) => `MAC ARP access list ${g},\n${rules.join("\n")}`).join("\n\n"); | |
| }; | |
| // Load all ACLs | |
| await Promise.all([ | |
| fetchAndRender("get-acl-standard-ip-list", "aclStandardContent", formatStandard), | |
| fetchAndRender("get-extended-ip-list", "aclExtendedContent", formatExtended), | |
| fetchAndRender("get-acl-mac-ip-list", "aclMacIpContent", formatMacIp), | |
| fetchAndRender("get-acl-mac-arp-list", "aclMacArpContent", formatMacArp) | |
| ]); | |
| } | |
| //ACL Reference // Pending Code beacuse port | |
| async function aclReference() { | |
| showContent(` | |
| <h2>ACL Reference</h2> | |
| <hr/> | |
| <div style="width: 100%; max-width: 1000px; background-color: #ffffff; border: 1px solid #e0e0e0;"> | |
| <div style="display: flex; justify-content: space-around; align-items: flex-start; gap: 20px; margin-bottom: 20px;"> | |
| <!-- Port Column --> | |
| <div style="display: flex; justify-content: flex-start;"> | |
| <div style="flex: 1; min-width: 200px; max-width: 300px; border: 1px solid #a0a0a0; background-color: #f8f8f8;"> | |
| <div style="background-color: #dbeaff; padding: 8px 10px; font-weight: bold; color: #333; border-bottom: 1px solid #a0a0a0; text-align: center; font-size: 13px;"> | |
| Port | |
| </div> | |
| <div style="height: 300px; display: flex; align-items: center; justify-content: flex-start; padding-left: 10px;"> | |
| <select id="aclRefPortSelect" style="width: 80%; padding: 4px; border: 1px solid #a0a0a0; border-radius: 2px; font-size: 12px; height: 26px;"> | |
| <option>Loading...</option> | |
| </select> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- All ACL Groups Column --> | |
| <div style="flex: 1; min-width: 180px; max-width: 250px; border: 1px solid #a0a0a0; background-color: #f8f8f8;"> | |
| <div style="background-color: #dbeaff; padding: 8px 10px; font-weight: bold; color: #333; border-bottom: 1px solid #a0a0a0; text-align: center; font-size: 13px;"> | |
| All ACL Groups | |
| </div> | |
| <div style="height: 300px; display: flex; align-items: center;"> | |
| <select id="aclGroupSelect" multiple size="10" style="width:60%; font-size: 12px; border: 1px solid #a0a0a0; height:295px;"> | |
| <option>Loading...</option> | |
| </select> | |
| </div> | |
| </div> | |
| <!-- Control Buttons --> | |
| <div style="display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 15px; margin-top: 100px;"> | |
| <button onclick="addAclGroupToPort()" id="applyBtn">Add =></button> | |
| <button onclick="deleteAclGroupFromReference()" id="deleteBtn">Delete<=</button> | |
| </div> | |
| <div style="flex: 1; min-width: 180px; max-width: 250px; border: 1px solid #a0a0a0; background-color: #f8f8f8;"> | |
| <div style="background-color: #dbeaff; padding: 8px 10px; font-weight: bold; color: #333; border-bottom: 1px solid #a0a0a0; text-align: center; font-size: 13px;"> | |
| Referenced ACL Groups | |
| </div> | |
| <div style="height: 300px; justify-content: center; align-items: center;"> | |
| <select id="aclGroupRefrenceList" multiple size="10" style="width:60%; font-size: 12px; border: 1px solid #a0a0a0; height:295px;"> | |
| </select> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div style="display: flex; justify-content: center; gap: 20px; margin-top:6px"> | |
| <button id="refreshBtn">Refresh</button> | |
| <button id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| loadAclPortOptions() | |
| loadAclGroups(); | |
| } | |
| async function loadAclPortOptions() { | |
| const select = document.getElementById("aclRefPortSelect"); | |
| select.innerHTML = '<option value="">Loading...</option>'; | |
| try { | |
| const response = await fetch(`${BASE_URL}/get-acl-refrenece-port`); | |
| if (!response.ok) throw new Error("Failed to fetch ports"); | |
| const ports = await response.json(); | |
| if (Array.isArray(ports) && ports.length > 0) { | |
| select.innerHTML = ""; | |
| ports.forEach(port => { | |
| const option = document.createElement("option"); | |
| option.value = port; | |
| option.textContent = port; | |
| select.appendChild(option); | |
| }); | |
| } else { | |
| select.innerHTML = '<option value="">No ports available</option>'; | |
| } | |
| } catch (err) { | |
| select.innerHTML = '<option value="">Error loading ports</option>'; | |
| } | |
| } | |
| async function loadAclGroups() { | |
| const select = document.getElementById("aclGroupSelect"); | |
| select.innerHTML = '<option>Loading...</option>'; | |
| try { | |
| const endpoints = [ | |
| `${BASE_URL}/get-acl-standard-ip-list`, | |
| `${BASE_URL}/get-extended-ip-list`, | |
| `${BASE_URL}/get-acl-mac-ip-list`, | |
| `${BASE_URL}/get-acl-mac-arp-list` | |
| ]; | |
| // Fetch and parse data from all endpoints | |
| const responses = await Promise.all(endpoints.map(url => fetch(url))); | |
| const allData = await Promise.all(responses.map(res => res.json())); | |
| console.log("allData", allData) | |
| // Flatten nested arrays and extract unique groupNum values | |
| const groupNums = [...new Set( | |
| allData.flat().map(item => item.groupNum).filter(Boolean) | |
| )]; | |
| console.log("Unique group numbers:", groupNums); | |
| // Populate the dropdown | |
| if (groupNums.length > 0) { | |
| select.innerHTML = ""; // Clear old options | |
| groupNums.forEach(num => { | |
| const option = document.createElement("option"); | |
| option.value = num; | |
| option.textContent = num; | |
| select.appendChild(option); | |
| }); | |
| } else { | |
| select.innerHTML = '<option>No ACL groups available</option>'; | |
| } | |
| } catch (error) { | |
| console.error("Error loading ACL groups:", error); | |
| select.innerHTML = '<option>Error loading ACL groups</option>'; | |
| } | |
| } | |
| async function addAclGroupToPort() { | |
| const port = document.getElementById("aclRefPortSelect").value; | |
| const groupSelect = document.getElementById("aclGroupSelect"); | |
| const selectedGroups = Array.from(groupSelect.selectedOptions).map(opt => opt.value); | |
| if (!port || selectedGroups.length === 0) { | |
| alert("Please select both a Port and at least one ACL Group."); | |
| return; | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/add-acl-group-refrenece`, { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json" | |
| }, | |
| body: JSON.stringify({ | |
| port: port, | |
| aclGroups: selectedGroups | |
| }) | |
| }); | |
| const result = await response.json(); | |
| if (response.ok) { | |
| alert("ACL group(s) successfully added to port."); | |
| fetchAclReferenceGroups(); | |
| } else { | |
| alert("Error: " + result.message); | |
| } | |
| } catch (error) { | |
| alert("API request failed."); | |
| } | |
| } | |
| async function fetchAclReferenceGroups() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/get-acl-group-refrenece`); | |
| const data = await response.json(); | |
| const select = document.getElementById("aclGroupRefrenceList"); | |
| if (!select) return; | |
| select.innerHTML = ""; | |
| const groupSet = new Set(); | |
| data.assignments.forEach((assignment) => { | |
| assignment.aclGroups.forEach((group) => { | |
| if (!groupSet.has(group)) { | |
| groupSet.add(group); | |
| const option = document.createElement("option"); | |
| option.value = group; | |
| option.textContent = group; | |
| select.appendChild(option); | |
| } | |
| }); | |
| }); | |
| } catch (err) { | |
| alert("Failed to fetch ACL group reference."); | |
| } | |
| } | |
| async function deleteAclGroupFromReference() { | |
| const selectElement = document.getElementById("aclGroupRefrenceList"); | |
| const selectedOptions = Array.from(selectElement.selectedOptions); | |
| if (selectedOptions.length === 0) { | |
| alert("Please select one or more ACL groups to delete."); | |
| return; | |
| } | |
| const groupsToDelete = selectedOptions.map(option => option.value); | |
| try { | |
| const response = await fetch(`${BASE_URL}/delete-acl-group-refrenece`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ aclGroups: groupsToDelete }) | |
| }); | |
| const result = await response.json(); | |
| alert(result.message); | |
| // Refresh the list | |
| fetchAclReferenceGroups(); | |
| } catch (err) { | |
| alert("Error deleting ACL group reference."); | |
| } | |
| } | |
| //PORT TRUNKING | |
| function portTrunking() { | |
| showContent(` | |
| <h2>Port Trunking Configuration</h2> | |
| <hr/> | |
| <table border="1" cellspacing="0" cellpadding="5" style="border-collapse: collapse; width: 100%; text-align: center;"> | |
| <thead> | |
| <tr> | |
| <th style="width: 20%; font-size: 10px; text-align: center; background-color: #e0ecff;">Trunk Group ID</th> | |
| <th style="width: 20%; font-size: 10px; text-align: center; background-color: #e0ecff;">Trunk Method</th> | |
| <th style="width: 20%; font-size: 10px; text-align: center; background-color: #e0ecff;">Able Config Port</th> | |
| <th style="width: 20%; font-size: 10px; text-align: center;background-color: #fff;"> </th> | |
| <th style="width: 20%; font-size: 10px; text-align: center; background-color: #e0ecff;">Member Port</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <!-- Trunk Group ID --> | |
| <td style="vertical-align: top;"> | |
| <input type="text" id="trunkGroupInput" value="1" style="width: 80%; margin-bottom: 5px;" /><br /> | |
| <select id="trunkGroupSelect" size="10" style="width: 80%; height: 200px; margin-left:0px"></select> | |
| </td> | |
| <!-- Trunk Method --> | |
| <td> | |
| <select id="trunkMethodSelect" style="width:86%; margin-bottom: 5px;"></select><br /> | |
| <button id="setTrunkMethodBtn" style="margin-top: 5px;">Set Trunk Method</button> | |
| </td> | |
| <!-- Able Config Port --> | |
| <td style="vertical-align: top;"> | |
| <select id="ablePortSelect" size="10" style="width: 90%; height: 200px;"></select> | |
| </td> | |
| <!-- Action Buttons --> | |
| <td style="vertical-align: middle;"> | |
| <div style="display: flex; flex-direction: column; gap: 10px; align-items: center;"> | |
| <button id="createTrunkGroupBtn">Create Trunk Group</button> | |
| <button id="addMemberPortBtn">Member Port =></button> | |
| <button onclick="unMemberPorts()">Unmember Port <=</button> | |
| <button onclick="deleteTrunkGroup()">Delete Trunk Group</button> | |
| </div> | |
| </td> | |
| <!-- Member Port --> | |
| <td style="vertical-align: top;"> | |
| <select id="memberPortSelect" size="10" style="width: 75%; height: 240px;"></select> | |
| </td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <div style="margin-top: 20px; font-size: 12px; text-align: center; color: #333;"> | |
| <p>(Note: There must be at least one trunk group when configuring the trunk method. All trunks use the same method. Ports can only be added or removed if the trunk exists. Trunk groups should be deleted only when they have no member ports.)</p> | |
| </div> | |
| <div style="text-align: center; margin-top: 20px;"> | |
| <button onclick="portTrunking()">Refresh</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| `); | |
| loadPortTrunkingData(); | |
| handleSetTrunkMethod(); | |
| handleCreateTrunkGroup(); | |
| handleAddMemberPort(); | |
| } | |
| async function loadPortTrunkingData() { | |
| try { | |
| const res = await fetch(`${BASE_URL}/port-trunking`); | |
| if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`); | |
| const { trunkGroups, trunkMethod, ablePorts } = await res.json(); | |
| const trunkGroupSelect = document.getElementById('trunkGroupSelect'); | |
| const trunkMethodSelect = document.getElementById('trunkMethodSelect'); | |
| const ablePortSelect = document.getElementById('ablePortSelect'); | |
| const memberPortSelect = document.getElementById('memberPortSelect'); | |
| const trunkGroupInput = document.getElementById('trunkGroupInput'); | |
| // Populate trunk group select | |
| trunkGroupSelect.innerHTML = trunkGroups.map( | |
| g => `<option value="${g.id}">${g.id} - ${g.created ? "Created" : "Uncreated"}</option>` | |
| ).join(''); | |
| // Populate trunk method select | |
| trunkMethodSelect.innerHTML = [`<option value="">-- Select --</option>`] | |
| .concat(trunkMethod.map(m => `<option value="${m}">${m}</option>`)).join(''); | |
| // Populate able port select | |
| ablePortSelect.innerHTML = ablePorts.map(p => `<option value="${p}">${p}</option>`).join(''); | |
| // On trunk group change | |
| trunkGroupSelect.onchange = () => { | |
| const selectedGroupId = trunkGroupSelect.value; | |
| trunkGroupInput.value = selectedGroupId; | |
| const selectedGroup = trunkGroups.find(g => g.id === selectedGroupId); | |
| if (selectedGroup && selectedGroup.memberPorts) { | |
| memberPortSelect.innerHTML = selectedGroup.memberPorts.map( | |
| port => `<option value="${port}">${port}</option>` | |
| ).join(''); | |
| } else { | |
| memberPortSelect.innerHTML = ''; | |
| } | |
| }; | |
| // Trigger change for default selection | |
| if (trunkGroups.length > 0) { | |
| trunkGroupSelect.value = trunkGroups[0].id; | |
| trunkGroupSelect.dispatchEvent(new Event('change')); | |
| } | |
| } catch (err) { | |
| console.error('Failed to load trunking data:', err); | |
| } | |
| } | |
| function handleSetTrunkMethod() { | |
| const btn = document.getElementById('setTrunkMethodBtn'); | |
| if (!btn) return; | |
| btn.onclick = async () => { | |
| const method = document.getElementById('trunkMethodSelect').value; | |
| if (!method) return alert("Please select a trunk method."); | |
| try { | |
| const res = await fetch(`${BASE_URL}/set-trunk-method`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ method }) | |
| }); | |
| const data = await res.json(); | |
| if (!res.ok) throw new Error(data.message || 'Failed to set'); | |
| alert(data.message); | |
| } catch (e) { | |
| console.error(e); | |
| alert('An error occurred while setting trunk method.'); | |
| } | |
| }; | |
| } | |
| function handleCreateTrunkGroup() { | |
| const btn = document.getElementById('createTrunkGroupBtn'); | |
| if (!btn) return; | |
| btn.onclick = async () => { | |
| const trunkGroupId = document.getElementById('trunkGroupInput').value; | |
| if (!trunkGroupId) return alert("Enter a Trunk Group ID first."); | |
| try { | |
| const res = await fetch(`${BASE_URL}/create-trunk-group`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ id: trunkGroupId }) | |
| }); | |
| const data = await res.json(); | |
| if (!res.ok) throw new Error(data.message || 'Create failed'); | |
| alert(data.message || 'Trunk Group created.'); | |
| // Refresh the trunk group list and auto-select the newly created one | |
| await loadPortTrunkingData(); | |
| document.getElementById('trunkGroupSelect').value = trunkGroupId; | |
| document.getElementById('trunkGroupInput').value = trunkGroupId; | |
| } catch (err) { | |
| console.error(err); | |
| alert("Error creating trunk group."); | |
| } | |
| }; | |
| } | |
| function handleAddMemberPort() { | |
| const button = document.getElementById('addMemberPortBtn'); | |
| const ablePort = document.getElementById('ablePortSelect'); | |
| const trunkGroupId = document.getElementById('trunkGroupInput'); | |
| if (!button || !ablePort || !trunkGroupId) return; | |
| button.onclick = async () => { | |
| const selectedOptions = Array.from(ablePort.selectedOptions); | |
| if (!selectedOptions.length) return alert("Please select port(s) to add."); | |
| const selectedPorts = selectedOptions.map(opt => opt.value); | |
| const groupId = trunkGroupId.value; | |
| try { | |
| const res = await fetch(`${BASE_URL}/add-member-port`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ groupId, ports: selectedPorts }) | |
| }); | |
| const data = await res.json(); | |
| if (!res.ok) throw new Error(data.message || 'Failed to add port(s).'); | |
| alert(data.message || 'Ports added.'); | |
| // Reload full trunking data | |
| await loadPortTrunkingData(); | |
| // Reselect group to refresh member port list | |
| document.getElementById('trunkGroupSelect').value = groupId; | |
| document.getElementById('trunkGroupSelect').dispatchEvent(new Event('change')); | |
| } catch (err) { | |
| console.error(err); | |
| alert("Failed to add ports."); | |
| } | |
| }; | |
| } | |
| async function unMemberPorts() { | |
| const groupId = document.getElementById("trunkGroupInput").value; | |
| const memberPortSelect = document.getElementById("memberPortSelect"); | |
| const selectedPorts = Array.from(memberPortSelect.selectedOptions).map(opt => opt.value); | |
| if (!groupId || selectedPorts.length === 0) { | |
| return alert("Please select at least one member port to remove."); | |
| } | |
| try { | |
| const response = await fetch(`${BASE_URL}/remove-member-port`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ groupId, ports: selectedPorts }) | |
| }); | |
| const data = await response.json(); | |
| if (!response.ok) throw new Error(data.message); | |
| alert(data.message); | |
| // Refresh data | |
| await loadPortTrunkingData(); | |
| document.getElementById('trunkGroupSelect').value = groupId; | |
| document.getElementById('trunkGroupSelect').dispatchEvent(new Event('change')); | |
| } catch (err) { | |
| console.error(err); | |
| alert("Failed to remove ports."); | |
| } | |
| } | |
| async function deleteTrunkGroup() { | |
| const groupId = document.getElementById("trunkGroupInput").value; | |
| if (!groupId) return alert("Please enter a trunk group ID."); | |
| try { | |
| const response = await fetch(`${BASE_URL}/delete-trunk-group`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ id: groupId }) | |
| }); | |
| const data = await response.json(); | |
| if (!response.ok) throw new Error(data.message); | |
| alert(data.message); | |
| // Refresh after deletion | |
| await loadPortTrunkingData(); | |
| } catch (err) { | |
| console.error(err); | |
| alert("Failed to delete trunk group."); | |
| } | |
| } | |
| //MIRROR | |
| function mirror() { | |
| showContent(` | |
| <h2>Port Mirror Configuration</h2> | |
| <hr/> | |
| <div style="background-color: #fff; padding: 20px; border-radius: 5px;"> | |
| <table style="width: 100%; border-collapse: collapse; height:350px"> | |
| <thead> | |
| <tr> | |
| <th style="border: 1px solid #ccc; padding: 8px; background-color: #e0e0e0; text-align: center; width: 25%; font-size: 10px;">Mirror Port</th> | |
| <th style="border: 1px solid #ccc; padding: 8px; background-color: #e0e0e0; text-align: center; width: 25%; font-size: 10px;">Able Config Mirrored Ports</th> | |
| <th style="border: 1px solid #ccc; padding: 8px; background-color: #e0e0e0; text-align: center; width: 25%; font-size: 10px;">Mirror Direction</th> | |
| <th style="border: 1px solid #ccc; padding: 8px; background-color: #e0e0e0; text-align: center; width: 25%; font-size: 10px;">Mirror Config Info</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td style="border: 1px solid #ccc; padding: 8px;"> | |
| <input id="mirrorPortInput" type="text" style="width: calc(100% - 64px); padding: 8px; border: 1px solid #ccc; border-radius: 3px;"> | |
| <div style="margin-bottom: 10px; font-size: 12px; color: #555;">(Mirror port name like: ge1/1)</div> | |
| </td> | |
| <td style="border: 1px solid #ccc; padding: 8px; vertical-align: top;"> | |
| <select id="mirrorPortsSelect" multiple size="10" | |
| style="width: 60%; height: 180px; border: 1px solid #ccc; padding: 5px; border-radius: 3px; margin-left: 0px;"> | |
| <!-- Options added dynamically --> | |
| </select> | |
| </td> | |
| <td style="border: 1px solid #ccc; padding: 8px;"> | |
| <select id="mirrorDirectionSelect" | |
| style="width: calc(100% - 88px); padding: 8px; border: 1px solid #ccc; border-radius: 3px; margin-left:45px"> | |
| <option value="">-- Select --</option> | |
| <option value="in">In</option> | |
| <option value="out">Out</option> | |
| <option value="both">Both</option> | |
| </select> | |
| </td> | |
| <td style="border: 1px solid #ccc; padding: 8px; vertical-align: top;"> | |
| <textarea id="mirrorConfigInfo" | |
| style="width: calc(100% - 18px); height: 170px; border: 1px solid #ccc; padding: 8px; border-radius: 3px; resize: vertical;"></textarea> | |
| </td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <div style="text-align: center; margin-top: 20px;"> | |
| <button onclick="mirror()" id = "refreshBtn">Refresh</button> | |
| <button onclick="applyMirrorConfig()" id = "applyBtn">Apply</button> | |
| <button onclick="showHelp()" id="helpBtn">Help</button> | |
| </div> | |
| </div> | |
| `); | |
| mirrorPorts(); | |
| loadMirrorConfigInfo(); | |
| } | |
| async function mirrorPorts() { | |
| const select = document.getElementById("mirrorPortsSelect"); | |
| try { | |
| const response = await fetch(`${BASE_URL}/get-mirror-ports`); | |
| if (!response.ok) throw new Error("Failed to fetch ports"); | |
| const data = await response.json(); // Expected format: ["ge1/1", "ge1/2", ...] | |
| // Clear existing options | |
| select.innerHTML = ""; | |
| // Populate new options | |
| data.forEach(port => { | |
| const option = document.createElement("option"); | |
| option.value = port; | |
| option.textContent = port; | |
| select.appendChild(option); | |
| }); | |
| } catch (error) { | |
| console.error("Error loading mirror ports:", error); | |
| select.innerHTML = "<option disabled>Error loading ports</option>"; | |
| } | |
| } | |
| async function applyMirrorConfig() { | |
| const mirrorPort = document.getElementById("mirrorPortInput").value; | |
| const mirrorDirection = document.getElementById("mirrorDirectionSelect").value; | |
| const selectedOptions = Array.from(document.getElementById("mirrorPortsSelect").selectedOptions); | |
| const mirroredPorts = selectedOptions.map(option => option.value); | |
| if (!mirrorPort || mirroredPorts.length === 0 || !mirrorDirection) { | |
| alert("Please fill all fields: Mirror Port, Mirrored Ports, and Direction."); | |
| return; | |
| } | |
| const payload = { | |
| mirrorPort, | |
| mirroredPorts, | |
| mirrorDirection | |
| }; | |
| try { | |
| const response = await fetch(`${BASE_URL}/apply-mirror-config`, { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json" | |
| }, | |
| body: JSON.stringify(payload) | |
| }); | |
| const result = await response.json(); | |
| if (response.ok) { | |
| alert("Mirror configuration applied successfully."); | |
| loadMirrorConfigInfo(); | |
| } else { | |
| alert(`Error: ${result.message}`); | |
| } | |
| } catch (error) { | |
| console.error("Apply Mirror Config Error:", error); | |
| alert("An error occurred while applying configuration."); | |
| } | |
| } | |
| async function loadMirrorConfigInfo() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/get-mirror-config`); | |
| const result = await response.json(); | |
| if (response.ok) { | |
| const formatted = ` | |
| Mirror Port : ${result.mirrorPort} | |
| Mirrored Ports : ${result.mirroredPorts.join(', ')} | |
| Mirror Direction : ${result.mirrorDirection}`.trim(); | |
| const textarea = document.getElementById("mirrorConfigInfo"); | |
| if (textarea) { | |
| textarea.value = formatted; | |
| } | |
| } else { | |
| alert(`Error loading mirror config info: ${result.message}`); | |
| } | |
| } catch (error) { | |
| alert("Failed to load mirror config info."); | |
| } | |
| } | |
| //DDM INFO | |
| async function ddmConfiguration() { | |
| showContent(`<h2>DDM Configuration</h2><hr/><div id="ddmContent">Loading...</div>`); | |
| try { | |
| const response = await fetch(`${BASE_URL}/get-ddm-status`); | |
| const result = await response.json(); | |
| const html = result.interfaces.map((item) => ` | |
| <p> | |
| <strong>${item.name}</strong><br/> | |
| ${item.status} | |
| </p> | |
| `).join(''); | |
| document.getElementById("ddmContent").innerHTML = html; | |
| } catch (error) { | |
| document.getElementById("ddmContent").innerHTML = `<p style="color:red;">Failed to load DDM Configuration</p>`; | |
| console.error("DDM Config Error:", error); | |
| } | |
| } | |
| //CABLE DIAGNOSIS | |
| function cableDiagnosis() { | |
| showContent(` | |
| <h2>Cable Diagnosis</h2> | |
| <hr/> | |
| <div style="background-color: #e6f0ff; width:40%; border: 1px solid #cce0ff; display: flex; align-items: center; gap: 10px;"> | |
| <span style="font-weight: bold; color: #333;">Port:</span> | |
| <select id="portSelect" style="padding: 5px; width: 37%; border: 1px solid #a0c3e6; border-radius: 3px;"> | |
| <!-- Options will be added dynamically --> | |
| </select> | |
| <button onclick="runDiagnosis()">Diagnosis</button> | |
| <button onclick="showHelp()">Help</button> | |
| </div> | |
| <div id="diagnosisResult" style="margin-top: 20px;"></div> | |
| `); | |
| fetchCableDiagnosisPorts(); | |
| fetchDiagnosisData(); | |
| } | |
| async function fetchCableDiagnosisPorts() { | |
| try { | |
| const response = await fetch(`${BASE_URL}/get-cable-diagnosis`); | |
| const ports = await response.json(); | |
| const select = document.getElementById("portSelect"); | |
| ports.forEach(port => { | |
| const option = document.createElement("option"); | |
| option.value = port; | |
| option.textContent = port; | |
| select.appendChild(option); | |
| }); | |
| select.value = ""; | |
| } catch (error) { | |
| alert("Failed to load cable diagnosis ports."); | |
| } | |
| } | |
| async function fetchDiagnosisData() { | |
| try { | |
| const getResponse = await fetch(`${BASE_URL}/get-cable-diagnosis-data`); | |
| const data = await getResponse.json(); | |
| showDiagnosisResult(data); | |
| } catch (error) { | |
| console.error("Failed to fetch cable diagnosis data", error); | |
| } | |
| } | |
| async function runDiagnosis() { | |
| const port = document.getElementById("portSelect").value; | |
| if (!port) { | |
| alert("Please select a port"); | |
| return; | |
| } | |
| try { | |
| const postResponse = await fetch(`${BASE_URL}/run-cable-diagnosis`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ port }) | |
| }); | |
| const postResult = await postResponse.json(); | |
| alert(postResult.message); // Optional | |
| fetchDiagnosisData(); | |
| } catch (error) { | |
| alert("Failed to run cable diagnosis"); | |
| } | |
| } | |
| function showDiagnosisResult(data) { | |
| const container = document.getElementById("diagnosisResult"); | |
| container.innerHTML = ` | |
| <div style="display: grid; grid-template-columns: repeat(9, 1fr); gap: 10px; padding: 5px; font-style: italic; font-size: 12px;"> | |
| <div>Port</div> | |
| <div>Channel-A</div> | |
| <div>Length-A</div> | |
| <div>Channel-B</div> | |
| <div>Length-B</div> | |
| <div>Channel-C</div> | |
| <div>Length-C</div> | |
| <div>Channel-D</div> | |
| <div>Length-D</div> | |
| </div> | |
| <!-- Data row --> | |
| <div style="display: grid; grid-template-columns: repeat(9, 1fr); gap: 10px; padding: 5px; font-style: italic; font-size: 12px;"> | |
| <div>${data.port}</div> | |
| <div>${data.channelA}</div> | |
| <div>${data.lengthA}</div> | |
| <div>${data.channelB}</div> | |
| <div>${data.lengthB}</div> | |
| <div>${data.channelC}</div> | |
| <div>${data.lengthC}</div> | |
| <div>${data.channelD}</div> | |
| <div>${data.lengthD}</div> | |
| </div> | |
| `; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment