<script src="script/chart.js"></script> <div class="ui stackable grid"> <div class="ten wide column serverstatusWrapper"> <div id="serverstatus" class="ui statustab inverted segment"> <h1 class="ui header" style="margin-top: 1em; margin-left: 0.4em; padding-bottom: 1em;"> <i id="rpStatusIcon" class="loading spinner icon"></i> <div class="content"> <span id="statusTitle">Loading</span> <div class="sub header" id="statusText">Checking server status</div> </div> </h1> <div class="dot-container"> <div class="dot"></div> <div class="dot"></div> <div class="dot"></div> <div class="dot"></div> </div> </div> </div> <div class="six wide column statisticWrapper"> <div class="ui greybackground statustab segment"> <h5 class="ui header"> <i class="exchange icon"></i> <div class="content"> <span id="summaryTotalCount"></span> <small>Req. Today</small> <div class="sub header" style="margin-top: 0.4em;"> <i class="green circle check icon"></i> <span id="summarySuccCount"></span> / <i class="red red exclamation circle icon"></i> <span id="summaryErrCount"></span> </div> </div> </h5> <div class="ui divider"></div> <h5 class="ui header"> <i class="arrows alternate horizontal icon"></i> <div class="content"> <span id="forwardtype"></span> <div class="sub header" id="forwardtypeList"> </div> </div> </h5> <div class="ui divider"></div> <h5 class="ui header"> <i class="map marker alternate icon"></i> <div class="content"> <span id="country"></span> <div class="sub header" id="countryList"> </div> </div> </h5> </div> </div> </div> <div id="networkActWrapper" class="standardContainer" style="position: relative; margin-top: 1em;"> <canvas id="networkActivity"></canvas> </div> <div id="networkActivityPlaceHolder"> <p style="opacity: 0.5;"><i class="ui pause icon"></i> Graph Render Paused</p> </div> <br> <div class="standardContainer"> <h4>Basic Settings</h4> <p>Inbound Port (Port to be proxied)</p> <div class="ui action fluid notloopbackOnly input"> <input type="text" id="incomingPort" placeholder="Incoming Port" value="80"> <button class="ui basic green notloopbackOnly button" onclick="handlePortChange();">Apply</button> </div> <br> <div id="tls" class="ui toggle notloopbackOnly checkbox"> <input type="checkbox"> <label>Use TLS to serve proxy request</label> </div> <br> <div id="redirect" class="ui toggle notloopbackOnly checkbox" style="margin-top: 0.6em;"> <input type="checkbox"> <label>Force redirect HTTP request to HTTPS<br> <small>(Only apply when listening port is not 80)</small></label> </div> <br><br> <button id="startbtn" class="ui teal button" onclick="startService();">Start Service</button> <button id="stopbtn" class="ui red notloopbackOnly disabled button" onclick="stopService();">Stop Service</button> <div id="rploopbackWarning" class="ui segment" style="display:none;"> <b><i class="yellow warning icon"></i> Loopback Routing Warning</b><br> <small>This management interface is a loopback proxied service. <br>If you want to shutdown the reverse proxy server, please remove the proxy rule for the management interface and refresh.</small> </div> <div class="ui divider"></div> <div class=""> <h4>Statistic Overview</h4> <div class="ui two column stackable grid"> <div class="column"> <p>Visitor Counts</p> <table class="ui unstackable inverted celled table"> <thead> <tr> <th>Country ISO Code</th> <th>Unique Visitors</th> </tr> </thead> <tbody id="countryCodetable"> <tr> <td colspan="2">No Data</td> </tr> </tbody> </table> </div> <div class="column"> <p>Proxy Request Types</p> <table class="ui unstackable inverted celled table"> <thead> <tr> <th>Proxy Type</th> <th>Count</th> </tr> </thead> <tbody id="forwardTypeTable"> <tr> <td colspan="2">No Data</td> </tr> </tbody> </table> </div> </div> </div> <br> <button class="ui right floated basic button" onclick="getDailySummaryDetails();"><i class="green refresh icon"></i> Refresh</button> <br><br> </div> <script> let loopbackProxiedInterface = false; //Initial the start stop button if this is reverse proxied $.get("/api/proxy/requestIsProxied", function(data){ if (data == true){ //This management interface is reverse proxied by itself //do not allow turning off the proxy $(".notloopbackOnly").addClass("disabled"); loopbackProxiedInterface = true; $("#rploopbackWarning").show(); } }); //Get the latest server status from proxy server function initRPStaste(){ $.get("/api/proxy/status", function(data){ if (data.Running == true){ $("#startbtn").addClass("disabled"); if (!loopbackProxiedInterface){ $("#stopbtn").removeClass("disabled"); } $("#serverstatus").addClass("green"); $("#statusTitle").text("Online"); $("#rpStatusIcon").attr("class", "green circle check icon"); $("#statusText").text("Serving request on port: " + data.Option.Port); }else{ $("#startbtn").removeClass("disabled"); $("#stopbtn").addClass("disabled"); $("#statusTitle").text("Offline"); $("#rpStatusIcon").attr("class", "black circle times icon") $("#statusText").text("Reverse proxy server is offline"); $("#serverstatus").removeClass("green"); } $("#incomingPort").val(data.Option.Port); }); } function abbreviateNumber(value) { var newValue = value; var suffixes = ["", "k", "m", "b", "t"]; var suffixNum = 0; while (newValue >= 1000 && suffixNum < suffixes.length - 1) { newValue /= 1000; suffixNum++; } if (value > 1000){ newValue = newValue.toFixed(2); } return newValue + suffixes[suffixNum]; } function getDailySummaryDetails(){ function sortObjectByValue(obj) { // Convert object to array of [key, value] pairs const entries = Object.entries(obj); // Sort array based on value of each pair entries.sort((a, b) => { return b[1] - a[1]; }); // Convert sorted array back to object const sortedObj = {}; for (const [key, value] of entries) { sortedObj[key] = value; } return sortedObj; } $.get("/api/stats/countries", function(data){ data = sortObjectByValue(data); $("#country").html((Object.keys(data)[0])?Object.keys(data)[0]:"No Data"); $("#countryList").html(` <div> ${(Object.keys(data)[1])?Object.keys(data)[1]:"-"}<br> ${(Object.keys(data)[2])?Object.keys(data)[2]:"-"} </div> `); //populate the table $("#countryCodetable").html(""); for (const [key, value] of Object.entries(data)) { var countryName = getCountryName(key); if (countryName == ""){ countryName = "LAN" } $("#countryCodetable").append(`<tr> <td>${key} (${countryName})</td> <td>${value}</td> </tr>`); } if (Object.keys(data).length == 0){ $("#countryCodetable").append(`<tr> <td colspan="2"><i class="ui green circle check icon"></i> No Data</td> </tr>`); } }); //Filter forward type function fft(ft){ if (ft.indexOf("-") >= 0){ ft = ft.replace("-", " ("); ft = ft + ")"; } ft = ft.charAt(0).toUpperCase() + ft.slice(1); return ft; } $.get("/api/stats/summary", function(data){ data = sortObjectByValue(data.ForwardTypes); $("#forwardtype").html((Object.keys(data)[0])?fft(Object.keys(data)[0]) + ": " + abbreviateNumber(data[Object.keys(data)[0]]):"No Data"); $("#forwardtypeList").html(` <div> ${(Object.keys(data)[1])?fft(Object.keys(data)[1]) + ": " + abbreviateNumber(data[Object.keys(data)[1]]):"-"}<br> ${(Object.keys(data)[2])?fft(Object.keys(data)[2]) + ": " + abbreviateNumber(data[Object.keys(data)[2]]):"-"} </div> `); $("#forwardTypeTable").html(""); for (const [key, value] of Object.entries(data)) { $("#forwardTypeTable").append(`<tr> <td>${key}</td> <td>${value}</td> </tr>`); } if (Object.keys(data).length == 0){ $("#forwardTypeTable").append(`<tr> <td colspan="2"><i class="ui green circle check icon"></i> No Data</td> </tr>`); } }); } getDailySummaryDetails(); function getDailySummary(){ $.get("/api/stats/summary?fast=true", function(data){ $("#summaryTotalCount").text(abbreviateNumber(data.TotalRequest)); $("#summarySuccCount").text(abbreviateNumber(data.ValidRequest)); $("#summaryErrCount").text(abbreviateNumber(data.ErrorRequest)); }); } setInterval(function(){ getDailySummary(); }, 10000); getDailySummary(); //Start and stop service button function startService(){ $.post("/api/proxy/enable", {enable: true}, function(data){ if (data.error != undefined){ msgbox(data.error, false, 5000); } initRPStaste(); }); } function stopService(){ $.post("/api/proxy/enable", {enable: false}, function(data){ if (data.error != undefined){ msgbox(data.error, false, 5000); } initRPStaste(); }); } function handlePortChange(){ var newPortValue = $("#incomingPort").val(); if (isNaN(newPortValue - 1) || newPortValue < 1 || newPortValue > 65535){ msgbox("Invalid incoming port value", false, 5000); return; } $.post("/api/proxy/setIncoming", {incoming: newPortValue}, function(data){ if (data.error != undefined){ msgbox(data.error, false, 5000); } msgbox("Setting Updated"); initRPStaste(); }); } function initHTTPtoHTTPSRedirectSetting(){ $.get("/api/proxy/useHttpsRedirect", function(data){ if (data == true){ $("#redirect").checkbox("set checked"); } //Initiate the input listener on the checkbox $("#redirect").find("input").on("change", function(){ let thisValue = $("#redirect").checkbox("is checked"); $.ajax({ url: "/api/proxy/useHttpsRedirect", data: {set: thisValue}, success: function(data){ if (data.error != undefined){ alert(data.error); }else{ //Updated msgbox("Setting Updated"); initRPStaste(); } } }) }); }); } initHTTPtoHTTPSRedirectSetting(); function initTlsSetting(){ $.get("/api/cert/tls", function(data){ if (data == true){ $("#tls").checkbox("set checked"); }else{ $("#redirect").addClass('disabled'); } //Initiate the input listener on the checkbox $("#tls").find("input").on("change", function(){ let thisValue = $("#tls").checkbox("is checked"); if (thisValue){ $("#redirect").removeClass('disabled'); }else{ $("#redirect").addClass('disabled'); } $.ajax({ url: "/api/cert/tls", data: {set: thisValue}, success: function(data){ if (data.error != undefined){ alert(data.error); }else{ //Updated msgbox("Setting Updated"); initRPStaste(); } } }) }); }) } initTlsSetting(); </script> <script> /* Render Network Activity Graph */ /* Setup Graph */ let rxValues = []; let txValues = []; let lastRx = 0; let lastTx = 0; let timestamps = []; let netstatRecordTokeep = 60; function fetchData() { fetch('/api/stats/netstat') .then(response => response.json()) .then(data => { let rx = data.RX; let tx = data.TX; // Calculate change from previous values if (lastRx == 0 && lastTx == 0){ //inital value lastRx = rx; lastTx = tx; return; } let deltaRx = rx - lastRx; let deltaTx = tx - lastTx; if (rxValues.length < netstatRecordTokeep){ //pad init into the array for(var i = 0;i<netstatRecordTokeep;i++){ rxValues.push(deltaRx); txValues.push(deltaTx); timestamps.push(Date.now() - 1000 * (netstatRecordTokeep - i)) } } // Add change to accumulated values rxValues.push(deltaRx); txValues.push(deltaTx); timestamps.push(Date.now()); // Only keep last netstatRecordTokeep data points if (rxValues.length > netstatRecordTokeep) { rxValues.shift(); txValues.shift(); timestamps.shift(); } lastRx = rx; lastTx = tx; updateChart(); }) .catch(error => { //console.error('Failed to fetch data', error); }); } function formatBandwidth(bps) { const KBPS = 1000; const MBPS = 1000 * KBPS; const GBPS = 1000 * MBPS; if (bps >= GBPS) { return (bps / GBPS).toFixed(2) + " Gbps"; } else if (bps >= MBPS) { return (bps / MBPS).toFixed(2) + " Mbps"; } else if (bps >= KBPS) { return (bps / KBPS).toFixed(2) + " Kbps"; } else { return bps.toFixed(2) + " bps"; } } var networkStatisticChart; function initChart(){ $.get("/api/stats/netstat", function(data){ networkStatisticChart = new Chart( document.getElementById('networkActivity'), { type: 'line', responsive: true, resizeDelay: 100, options: { animation: false, maintainAspectRatio: false, tooltips: {enabled: false}, hover: {mode: null}, //stepped: 'middle', plugins: { legend: { display: true, position: "right", }, title: { display: false, text: 'Network Statistic' }, }, scales: { x: { display: false, }, y: { display: true, scaleLabel: { display: true, labelString: 'Value' }, ticks: { stepSize: 10000000, callback: function(label, index, labels) { return formatBandwidth(parseInt(label)); } }, gridLines: { display: true } } } }, data: { labels: timestamps, datasets: [ { label: 'Inbound', data: rxValues, borderColor: "#4d9dd9", borderWidth: 2, backgroundColor: 'rgba(77, 157, 217, 0.2)', fill: true, pointStyle: false, }, { label: 'Outbound', data: txValues, borderColor: '#ffe32b', borderWidth: 2, backgroundColor: 'rgba(255, 227, 43, 0.2)', fill: true, pointStyle: false, } ] } } ); }); } function updateChart() { //networkStatisticChart.data.datasets[0].data = rxValues; //networkStatisticChart.data.datasets[1].data = txValues; if (networkStatisticChart != undefined){ networkStatisticChart.update(); } } function updateChartSize(){ let newSize = $("#networkActWrapper").width() - 300; if (window.innerWidth > 750){ newSize = window.innerWidth - $(".toolbar").width() - 500; }else{ newSize = $("#networkActWrapper").width() - 500; } if (networkStatisticChart != undefined){ networkStatisticChart.resize(newSize, 200); } } function handleChartAccumulateResize(){ $("#networkActivity").hide(); $("#networkActivityPlaceHolder").show(); if (chartResizeTimeout != undefined){ clearTimeout(chartResizeTimeout); } chartResizeTimeout = setTimeout(function(){ chartResizeTimeout = undefined; $("#networkActivityPlaceHolder").hide(); $("#networkActivity").show(); updateChartSize(); }, 300); } var chartResizeTimeout; window.addEventListener('resize', () => { handleChartAccumulateResize(); }); //Bind event to tab switch tabSwitchEventBind["status"] = function(){ //On switch over to this page, resize the chart $("#networkActivityPlaceHolder").hide(); $("#networkActivity").show().delay(100, function(){ updateChartSize(); }); } window.addEventListener("focus", function(event){ handleChartAccumulateResize(); }); document.addEventListener("visibilitychange", () => { // it could be either hidden or visible //handleChartAccumulateResize(); }); //Initialize chart data initChart(); fetchData(); setInterval(fetchData, 1000); </script>