status.html 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. <script src="script/chart.js"></script>
  2. <div class="ui stackable grid">
  3. <div class="ten wide column serverstatusWrapper">
  4. <div id="serverstatus" class="ui statustab inverted segment">
  5. <h1 class="ui header" style="margin-top: 1em; margin-left: 0.4em; padding-bottom: 1em;">
  6. <i id="rpStatusIcon" class="loading spinner icon"></i>
  7. <div class="content">
  8. <span id="statusTitle">Loading</span>
  9. <div class="sub header" id="statusText">Checking server status</div>
  10. </div>
  11. </h1>
  12. <div class="dot-container">
  13. <div class="dot"></div>
  14. <div class="dot"></div>
  15. <div class="dot"></div>
  16. <div class="dot"></div>
  17. </div>
  18. </div>
  19. </div>
  20. <div class="six wide column statisticWrapper">
  21. <div class="ui greybackground statustab segment">
  22. <h5 class="ui header">
  23. <i class="exchange icon"></i>
  24. <div class="content">
  25. <span id="summaryTotalCount"></span> <small>Req. Today</small>
  26. <div class="sub header" style="margin-top: 0.4em;">
  27. <i class="green circle check icon"></i> <span id="summarySuccCount"></span>
  28. / <i class="red red exclamation circle icon"></i> <span id="summaryErrCount"></span>
  29. </div>
  30. </div>
  31. </h5>
  32. <div class="ui divider"></div>
  33. <h5 class="ui header">
  34. <i class="arrows alternate horizontal icon"></i>
  35. <div class="content">
  36. <span id="forwardtype"></span>
  37. <div class="sub header" id="forwardtypeList">
  38. </div>
  39. </div>
  40. </h5>
  41. <div class="ui divider"></div>
  42. <h5 class="ui header">
  43. <i class="map marker alternate icon"></i>
  44. <div class="content">
  45. <span id="country"></span>
  46. <div class="sub header" id="countryList">
  47. </div>
  48. </div>
  49. </h5>
  50. </div>
  51. </div>
  52. </div>
  53. <div class="standardContainer" style="position: relative; margin-top: 1em;">
  54. <canvas id="networkActivity"></canvas>
  55. </div>
  56. <br>
  57. <div class="standardContainer">
  58. <h4>Basic Settings</h4>
  59. <p>Inbound Port (Port to be proxied)</p>
  60. <div class="ui action fluid notloopbackOnly input">
  61. <input type="text" id="incomingPort" placeholder="Incoming Port" value="80">
  62. <button class="ui basic green notloopbackOnly button" onclick="handlePortChange();">Apply</button>
  63. </div>
  64. <br>
  65. <div id="tls" class="ui toggle notloopbackOnly checkbox">
  66. <input type="checkbox">
  67. <label>Use TLS to serve proxy request</label>
  68. </div>
  69. <br>
  70. <div id="redirect" class="ui toggle notloopbackOnly checkbox" style="margin-top: 0.6em;">
  71. <input type="checkbox">
  72. <label>Force redirect HTTP request to HTTPS<br>
  73. <small>(Only apply when listening port is not 80)</small></label>
  74. </div>
  75. <br><br>
  76. <button id="startbtn" class="ui teal button" onclick="startService();">Start Service</button>
  77. <button id="stopbtn" class="ui red notloopbackOnly disabled button" onclick="stopService();">Stop Service</button>
  78. <div id="rploopbackWarning" class="ui segment" style="display:none;">
  79. <b><i class="yellow warning icon"></i> Loopback Routing Warning</b><br>
  80. <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>
  81. </div>
  82. <div class="ui divider"></div>
  83. <div class="">
  84. <h4>Statistic Overview</h4>
  85. <div class="ui two column stackable grid">
  86. <div class="column">
  87. <p>Visitor Counts</p>
  88. <table class="ui unstackable inverted celled table">
  89. <thead>
  90. <tr>
  91. <th>Country ISO Code</th>
  92. <th>Unique Visitors</th>
  93. </tr>
  94. </thead>
  95. <tbody id="countryCodetable">
  96. <tr>
  97. <td colspan="2">No Data</td>
  98. </tr>
  99. </tbody>
  100. </table>
  101. </div>
  102. <div class="column">
  103. <p>Proxy Request Types</p>
  104. <table class="ui unstackable inverted celled table">
  105. <thead>
  106. <tr>
  107. <th>Proxy Type</th>
  108. <th>Count</th>
  109. </tr>
  110. </thead>
  111. <tbody id="forwardTypeTable">
  112. <tr>
  113. <td colspan="2">No Data</td>
  114. </tr>
  115. </tbody>
  116. </table>
  117. </div>
  118. </div>
  119. </div>
  120. <br>
  121. <button class="ui right floated basic button" onclick="getDailySummaryDetails();"><i class="green refresh icon"></i> Refresh</button>
  122. <br><br>
  123. </div>
  124. <script>
  125. let loopbackProxiedInterface = false;
  126. //Initial the start stop button if this is reverse proxied
  127. $.get("/api/proxy/requestIsProxied", function(data){
  128. if (data == true){
  129. //This management interface is reverse proxied by itself
  130. //do not allow turning off the proxy
  131. $(".notloopbackOnly").addClass("disabled");
  132. loopbackProxiedInterface = true;
  133. $("#rploopbackWarning").show();
  134. }
  135. });
  136. //Get the latest server status from proxy server
  137. function initRPStaste(){
  138. $.get("/api/proxy/status", function(data){
  139. if (data.Running == true){
  140. $("#startbtn").addClass("disabled");
  141. if (!loopbackProxiedInterface){
  142. $("#stopbtn").removeClass("disabled");
  143. }
  144. $("#serverstatus").addClass("green");
  145. $("#statusTitle").text("Online");
  146. $("#rpStatusIcon").attr("class", "green circle check icon");
  147. $("#statusText").text("Serving request on port: " + data.Option.Port);
  148. }else{
  149. $("#startbtn").removeClass("disabled");
  150. $("#stopbtn").addClass("disabled");
  151. $("#statusTitle").text("Offline");
  152. $("#rpStatusIcon").attr("class", "black circle times icon")
  153. $("#statusText").text("Reverse proxy server is offline");
  154. $("#serverstatus").removeClass("green");
  155. }
  156. $("#incomingPort").val(data.Option.Port);
  157. });
  158. }
  159. function abbreviateNumber(value) {
  160. var newValue = value;
  161. var suffixes = ["", "k", "m", "b", "t"];
  162. var suffixNum = 0;
  163. while (newValue >= 1000 && suffixNum < suffixes.length - 1) {
  164. newValue /= 1000;
  165. suffixNum++;
  166. }
  167. if (value > 1000){
  168. newValue = newValue.toFixed(2);
  169. }
  170. return newValue + suffixes[suffixNum];
  171. }
  172. function getDailySummaryDetails(){
  173. function sortObjectByValue(obj) {
  174. // Convert object to array of [key, value] pairs
  175. const entries = Object.entries(obj);
  176. // Sort array based on value of each pair
  177. entries.sort((a, b) => {
  178. return b[1] - a[1];
  179. });
  180. // Convert sorted array back to object
  181. const sortedObj = {};
  182. for (const [key, value] of entries) {
  183. sortedObj[key] = value;
  184. }
  185. return sortedObj;
  186. }
  187. $.get("/api/stats/countries", function(data){
  188. data = sortObjectByValue(data);
  189. $("#country").html((Object.keys(data)[0])?Object.keys(data)[0]:"No Data");
  190. $("#countryList").html(`
  191. <div>
  192. ${(Object.keys(data)[1])?Object.keys(data)[1]:"-"}<br>
  193. ${(Object.keys(data)[2])?Object.keys(data)[2]:"-"}
  194. </div>
  195. `);
  196. //populate the table
  197. $("#countryCodetable").html("");
  198. for (const [key, value] of Object.entries(data)) {
  199. $("#countryCodetable").append(`<tr>
  200. <td>${key}</td>
  201. <td>${value}</td>
  202. </tr>`);
  203. }
  204. if (Object.keys(data).length == 0){
  205. $("#countryCodetable").append(`<tr>
  206. <td colspan="2"><i class="ui green circle check icon"></i> No Data</td>
  207. </tr>`);
  208. }
  209. });
  210. //Filter forward type
  211. function fft(ft){
  212. if (ft.indexOf("-") >= 0){
  213. ft = ft.replace("-", " (");
  214. ft = ft + ")";
  215. }
  216. ft = ft.charAt(0).toUpperCase() + ft.slice(1);
  217. return ft;
  218. }
  219. $.get("/api/stats/summary", function(data){
  220. data = sortObjectByValue(data.ForwardTypes);
  221. $("#forwardtype").html((Object.keys(data)[0])?fft(Object.keys(data)[0]) + ": " + abbreviateNumber(data[Object.keys(data)[0]]):"No Data");
  222. $("#forwardtypeList").html(`
  223. <div>
  224. ${(Object.keys(data)[1])?fft(Object.keys(data)[1]) + ": " + abbreviateNumber(data[Object.keys(data)[1]]):"-"}<br>
  225. ${(Object.keys(data)[2])?fft(Object.keys(data)[2]) + ": " + abbreviateNumber(data[Object.keys(data)[2]]):"-"}
  226. </div>
  227. `);
  228. $("#forwardTypeTable").html("");
  229. for (const [key, value] of Object.entries(data)) {
  230. $("#forwardTypeTable").append(`<tr>
  231. <td>${key}</td>
  232. <td>${value}</td>
  233. </tr>`);
  234. }
  235. if (Object.keys(data).length == 0){
  236. $("#forwardTypeTable").append(`<tr>
  237. <td colspan="2"><i class="ui green circle check icon"></i> No Data</td>
  238. </tr>`);
  239. }
  240. });
  241. }
  242. getDailySummaryDetails();
  243. function getDailySummary(){
  244. $.get("/api/stats/summary?fast=true", function(data){
  245. console.log(data);
  246. $("#summaryTotalCount").text(abbreviateNumber(data.TotalRequest));
  247. $("#summarySuccCount").text(abbreviateNumber(data.ValidRequest));
  248. $("#summaryErrCount").text(abbreviateNumber(data.ErrorRequest));
  249. });
  250. }
  251. setInterval(function(){
  252. getDailySummary();
  253. }, 10000);
  254. getDailySummary();
  255. //Start and stop service button
  256. function startService(){
  257. $.post("/api/proxy/enable", {enable: true}, function(data){
  258. if (data.error != undefined){
  259. msgbox(data.error, false, 5000);
  260. }
  261. initRPStaste();
  262. });
  263. }
  264. function stopService(){
  265. $.post("/api/proxy/enable", {enable: false}, function(data){
  266. if (data.error != undefined){
  267. msgbox(data.error, false, 5000);
  268. }
  269. initRPStaste();
  270. });
  271. }
  272. function handlePortChange(){
  273. var newPortValue = $("#incomingPort").val();
  274. if (isNaN(newPortValue - 1) || newPortValue < 1 || newPortValue > 65535){
  275. msgbox("Invalid incoming port value", false, 5000);
  276. return;
  277. }
  278. $.post("/api/proxy/setIncoming", {incoming: newPortValue}, function(data){
  279. if (data.error != undefined){
  280. msgbox(data.error, false, 5000);
  281. }
  282. msgbox("Setting Updated");
  283. initRPStaste();
  284. });
  285. }
  286. function initHTTPtoHTTPSRedirectSetting(){
  287. $.get("/api/proxy/useHttpsRedirect", function(data){
  288. if (data == true){
  289. $("#redirect").checkbox("set checked");
  290. }
  291. //Initiate the input listener on the checkbox
  292. $("#redirect").find("input").on("change", function(){
  293. let thisValue = $("#redirect").checkbox("is checked");
  294. $.ajax({
  295. url: "/api/proxy/useHttpsRedirect",
  296. data: {set: thisValue},
  297. success: function(data){
  298. if (data.error != undefined){
  299. alert(data.error);
  300. }else{
  301. //Updated
  302. msgbox("Setting Updated");
  303. initRPStaste();
  304. }
  305. }
  306. })
  307. });
  308. });
  309. }
  310. initHTTPtoHTTPSRedirectSetting();
  311. function initTlsSetting(){
  312. $.get("/api/cert/tls", function(data){
  313. if (data == true){
  314. $("#tls").checkbox("set checked");
  315. }else{
  316. $("#redirect").addClass('disabled');
  317. }
  318. //Initiate the input listener on the checkbox
  319. $("#tls").find("input").on("change", function(){
  320. let thisValue = $("#tls").checkbox("is checked");
  321. if (thisValue){
  322. $("#redirect").removeClass('disabled');
  323. }else{
  324. $("#redirect").addClass('disabled');
  325. }
  326. $.ajax({
  327. url: "/api/cert/tls",
  328. data: {set: thisValue},
  329. success: function(data){
  330. if (data.error != undefined){
  331. alert(data.error);
  332. }else{
  333. //Updated
  334. msgbox("Setting Updated");
  335. initRPStaste();
  336. }
  337. }
  338. })
  339. });
  340. })
  341. }
  342. initTlsSetting();
  343. </script>
  344. <script>
  345. /*
  346. Render Network Activity Graph
  347. */
  348. /*
  349. Setup Graph
  350. */
  351. let rxValues = [];
  352. let txValues = [];
  353. let lastRx = 0;
  354. let lastTx = 0;
  355. let timestamps = [];
  356. let netstatRecordTokeep = 60;
  357. function fetchData() {
  358. fetch('/api/stats/netstat')
  359. .then(response => response.json())
  360. .then(data => {
  361. let rx = data.RX;
  362. let tx = data.TX;
  363. // Calculate change from previous values
  364. if (lastRx == 0 && lastTx == 0){
  365. //inital value
  366. lastRx = rx;
  367. lastTx = tx;
  368. return;
  369. }
  370. let deltaRx = rx - lastRx;
  371. let deltaTx = tx - lastTx;
  372. if (rxValues.length < netstatRecordTokeep){
  373. //pad init into the array
  374. for(var i = 0;i<netstatRecordTokeep;i++){
  375. rxValues.push(deltaRx);
  376. txValues.push(deltaTx);
  377. timestamps.push(Date.now() - 1000 * (netstatRecordTokeep - i))
  378. }
  379. }
  380. // Add change to accumulated values
  381. rxValues.push(deltaRx);
  382. txValues.push(deltaTx);
  383. timestamps.push(Date.now());
  384. // Only keep last netstatRecordTokeep data points
  385. if (rxValues.length > netstatRecordTokeep) {
  386. rxValues.shift();
  387. txValues.shift();
  388. timestamps.shift();
  389. }
  390. lastRx = rx;
  391. lastTx = tx;
  392. updateChart();
  393. })
  394. .catch(error => {
  395. console.error('Failed to fetch data', error);
  396. });
  397. }
  398. function formatBandwidth(bps) {
  399. const KBPS = 1000;
  400. const MBPS = 1000 * KBPS;
  401. const GBPS = 1000 * MBPS;
  402. if (bps >= GBPS) {
  403. return (bps / GBPS).toFixed(2) + " Gbps";
  404. } else if (bps >= MBPS) {
  405. return (bps / MBPS).toFixed(2) + " Mbps";
  406. } else if (bps >= KBPS) {
  407. return (bps / KBPS).toFixed(2) + " Kbps";
  408. } else {
  409. return bps.toFixed(2) + " bps";
  410. }
  411. }
  412. var networkStatisticChart;
  413. function initChart(){
  414. $.get("/api/stats/netstat", function(data){
  415. networkStatisticChart = new Chart(
  416. document.getElementById('networkActivity'),
  417. {
  418. type: 'line',
  419. responsive: true,
  420. options: {
  421. animation: false,
  422. maintainAspectRatio: false,
  423. tooltips: {enabled: false},
  424. hover: {mode: null},
  425. plugins: {
  426. legend: {
  427. display: true,
  428. position: "right",
  429. },
  430. title: {
  431. display: false,
  432. text: 'Network Statistic'
  433. },
  434. },
  435. scales: {
  436. x: {
  437. display: false,
  438. },
  439. y: {
  440. display: true,
  441. scaleLabel: {
  442. display: true,
  443. labelString: 'Value'
  444. },
  445. ticks: {
  446. stepSize: 10000000,
  447. callback: function(label, index, labels) {
  448. return formatBandwidth(parseInt(label));
  449. }
  450. },
  451. gridLines: {
  452. display: true
  453. }
  454. }
  455. }
  456. },
  457. data: {
  458. labels: timestamps,
  459. datasets: [
  460. {
  461. label: 'Inbound',
  462. data: rxValues,
  463. borderColor: "#4d9dd9",
  464. backgroundColor: 'rgba(77, 157, 217, 0.2)',
  465. fill: true,
  466. },
  467. {
  468. label: 'Outbound',
  469. data: txValues,
  470. borderColor: '#3a9460',
  471. backgroundColor: 'rgba(58, 148, 96, 0.2)',
  472. fill: true,
  473. }
  474. ]
  475. }
  476. }
  477. );
  478. });
  479. }
  480. function updateChart() {
  481. //networkStatisticChart.data.datasets[0].data = rxValues;
  482. //networkStatisticChart.data.datasets[1].data = txValues;
  483. networkStatisticChart.update();
  484. }
  485. window.addEventListener('resize', () => {
  486. networkStatisticChart.resize($(".contentWindow").width() - 500, 200);
  487. });
  488. //Bind event to tab switch
  489. tabSwitchEventBind["status"] = function(){
  490. //On switch over to this page, resize the chart
  491. networkStatisticChart.resize($(".contentWindow").width() - 500, 200);
  492. }
  493. //Initialize chart data
  494. initChart();
  495. fetchData();
  496. setInterval(fetchData, 1000);
  497. </script>