stats.html 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808
  1. <script src="./script/useragent.js"></script>
  2. <link href="./script/datepicker/dp-light.css" rel="stylesheet">
  3. <script defer src="./script/datepicker/datepicker.js"></script>
  4. <div class="standardContainer">
  5. <div class="ui basic segment">
  6. <h2>Statistical Analysis</h2>
  7. <p>Statistic of your server in every aspects</p>
  8. <div style="margin-bottom: 0.4em;">
  9. <div class="ui small input">
  10. <input type="text" id="statsRangeStart" placeholder="From date">
  11. </div>
  12. <span style="padding-left: 0.6em; padding-right: 0.6em;"> <i class="ui right arrow icon"></i> </span>
  13. <div class="ui small input">
  14. <input type="text" id="statsRangeEnd" placeholder="End date">
  15. </div>
  16. </div>
  17. <button onclick="handleLoadStatisticButtonPress();" class="ui basic button"><i class="blue search icon"></i> Load</button>
  18. <button onclick="clearStatisticDateRange();" class="ui basic button"><i class="eraser icon"></i> Clear Search</button>
  19. <br>
  20. <small>Leave end range as empty for showing starting day only statistic</small>
  21. </div>
  22. <div class="ui divider"></div>
  23. <div id="statisticRenderNotEnoughData" class="ui segment" style="padding: 2em;" align="center">
  24. <h4 class="ui icon header">
  25. <i class="medium icons" style="color: #dbdbdb !important;">
  26. <i class="chart pie icon"></i>
  27. <i class="small corner remove icon" style="
  28. font-size: 1.4em;
  29. margin-right: -0.2em;
  30. margin-bottom: -0em;
  31. "></i>
  32. </i>
  33. <div class="content" style="margin-top: 1em; color: #7c7c7c !important;">
  34. No Data
  35. <div class="sub header" style="color: #adadad !important;">The selected period contains too little or no request data collected. <br>
  36. Please select a larger range or make sure there are enough traffic routing through this site.</div>
  37. </div>
  38. </h4>
  39. </div>
  40. <div id="statisticRenderElement" class="ui basic segment">
  41. <!-- View Counts Statistics -->
  42. <div class="ui three small statistics">
  43. <div class="statistic">
  44. <div class="value totalViewCount">
  45. </div>
  46. <div class="label">
  47. Total Requests
  48. </div>
  49. </div>
  50. <div class="statistic">
  51. <div class="value">
  52. <i class="ui green check circle icon"></i> <span class="totalSuccCount"></span>
  53. </div>
  54. <div class="label">
  55. Success Requests
  56. </div>
  57. </div>
  58. <div class="statistic">
  59. <div class="value">
  60. <i class="ui red times circle icon"></i> <span class="totalErrorCount"></span>
  61. </div>
  62. <div class="label">
  63. Error Requests
  64. </div>
  65. </div>
  66. </div>
  67. <!-- Forward Type Data -->
  68. <h3>Forward Traffic Types</h3>
  69. <p>Traffic forwarding type classified by their protocols and proxy rules.</p>
  70. <table class="ui celled unstackable table">
  71. <thead>
  72. <tr><th>Forward Type</th>
  73. <th>Counts</th>
  74. <th>Percentage</th>
  75. </tr></thead>
  76. <tbody class="forwardTypeCounts">
  77. </tbody>
  78. </table>
  79. <!-- Client Geolocation Analysis-->
  80. <h3>Visitors Countries</h3>
  81. <p>Distributions of visitors by country code. Access origin are estimated using open source GeoIP database and might not be accurate.</p>
  82. <div style="min-height: 400px;">
  83. <canvas id="stats_visitors"></canvas>
  84. </div>
  85. <div class="ui divider"></div>
  86. <!-- Client IP Analysis -->
  87. <div class="ui stackable grid">
  88. <div class="eight wide column">
  89. <h3>Requests IP Version</h3>
  90. <p>The version of Internet Protocol that the client is using. If the request client is pass through a DNS proxy like CloudFlare,
  91. the CloudFlare proxy server address will be filtered and only the first value in the RemoteAddress field will be analysised.</p>
  92. <div>
  93. <canvas id="stats_ipver"></canvas>
  94. </div>
  95. </div>
  96. <div class="eight wide column">
  97. <h3>Request Origins</h3>
  98. <p>Top 25 request origin sorted by request count</p>
  99. <div style="height: 500px; overflow-y: auto;">
  100. <table class="ui unstackable striped celled table">
  101. <thead>
  102. <tr>
  103. <th class="no-sort">Request Origin</th>
  104. <th class="no-sort">No of Requests</th>
  105. </tr></thead>
  106. <tbody id="stats_requestCountlist">
  107. </tbody>
  108. </table>
  109. </div>
  110. </div>
  111. </div>
  112. <div class="ui divider"></div>
  113. <!-- Client Device Analysis -->
  114. <div class="ui stackable grid">
  115. <div class="eight wide column">
  116. <h3>Client Devices</h3>
  117. <p>Device type analysis by its request interactions.The number of iteration count does not means the number unique device, as no cookie is used to track the devices identify.</p>
  118. <div>
  119. <canvas id="stats_device"></canvas>
  120. </div>
  121. </div>
  122. <div class="eight wide column">
  123. <h3>Client Browsers</h3>
  124. <p>The browsers where your client is using to visit your site</p>
  125. <div>
  126. <canvas id="stats_browsers"></canvas>
  127. </div>
  128. </div>
  129. </div>
  130. <div class="ui stackable grid">
  131. <div class="eight wide column">
  132. <h3>Client OS</h3>
  133. <p>The OS where your client is using. Estimated using the UserAgent header sent by the client browser while requesting a resources from one of your host.</p>
  134. <div>
  135. <canvas id="stats_OS"></canvas>
  136. </div>
  137. </div>
  138. <div class="eight wide column">
  139. <h3>OS Versions</h3>
  140. <p>The OS versions most commonly used by your site's visitors.</p>
  141. <div style="height: 500px; overflow-y: auto;">
  142. <table class="ui unstackable striped celled table">
  143. <thead>
  144. <tr>
  145. <th>OS Version</th>
  146. <th>Request Counts</th>
  147. <th>Percentage</th>
  148. </tr></thead>
  149. <tbody id="stats_OSVersionList">
  150. </tbody>
  151. </table>
  152. </div>
  153. </div>
  154. </div>
  155. <div class="ui divider"></div>
  156. <div class="ui stackable grid">
  157. <div class="eight wide column">
  158. <h3>Request File Types</h3>
  159. <p>The file types being served by this proxy</p>
  160. <div>
  161. <canvas id="stats_filetype"></canvas>
  162. </div>
  163. </div>
  164. <div class="eight wide column">
  165. <h3>Referring Sites</h3>
  166. <p>The Top 100 sources of traffic according to referer header</p>
  167. <div>
  168. <div style="height: 500px; overflow-y: auto;">
  169. <table class="ui unstackable striped celled table">
  170. <thead>
  171. <tr>
  172. <th class="no-sort">Referer</th>
  173. <th class="no-sort">Requests</th>
  174. </tr></thead>
  175. <tbody id="stats_RefererTable">
  176. </tbody>
  177. </table>
  178. </div>
  179. </div>
  180. </div>
  181. </div>
  182. <div class="ui divider"></div>
  183. <div class="ui basic segment" id="trendGraphs">
  184. <h3>Visitor Trend Analysis</h3>
  185. <p>Request trends in the selected time range</p>
  186. <div>
  187. <canvas id="requestTrends"></canvas>
  188. </div>
  189. </div>
  190. <button onclick="showSideWrapper('snippet/advanceStatsOprs.html?t=' + Date.now() + '#' + encodeURIComponent(JSON.stringify(getStatisticDateRange())));" class="ui basic right floated black button"><i class="external square alternate icon"></i> Advance Operations</button>
  191. </div>
  192. <!-- <button class="ui icon right floated basic button" onclick="initStatisticSummery();"><i class="green refresh icon"></i> Refresh</button> -->
  193. <br><br>
  194. </div>
  195. <script>
  196. var statisticCharts = [];
  197. //Start day must be earlier than end date
  198. function initStatisticSummery(startdate="", enddate=""){
  199. if (startdate == "" && enddate == "" ){
  200. let todaykey = getTodayStatisticKey();
  201. loadStatisticByDate(todaykey);
  202. }else if ((startdate != "" && enddate == "") || startdate == enddate){
  203. //Load the target date
  204. let targetDate = startdate.trim();
  205. loadStatisticByDate(targetDate);
  206. }else{
  207. //Two dates are given and they are not identical
  208. loadStatisticByRange(startdate, enddate);
  209. //console.log(startdate, enddate);
  210. }
  211. }
  212. function loadStatisticByDate(dateid){
  213. $.getJSON("/api/analytic/load?id=" + dateid, function(data){
  214. if (data.error != undefined){
  215. msgbox(data.error, false, 6000);
  216. return;
  217. }
  218. //Destroy all the previous charts
  219. statisticCharts.forEach(function(thisChart){
  220. thisChart.destroy();
  221. })
  222. if (data.TotalRequest == 0){
  223. //No data to analysis
  224. $("#statisticRenderElement").hide()
  225. $("#statisticRenderNotEnoughData").show();
  226. return;
  227. }else{
  228. $("#statisticRenderElement").show();
  229. $("#statisticRenderNotEnoughData").hide();
  230. }
  231. //Render the text values
  232. $("#statisticRenderElement").find(".totalViewCount").text(abbreviateNumber(data.TotalRequest));
  233. $("#statisticRenderElement").find(".totalSuccCount").text(abbreviateNumber(data.ValidRequest));
  234. $("#statisticRenderElement").find(".totalErrorCount").text(abbreviateNumber(data.ErrorRequest));
  235. //Render forward type data
  236. renderForwardTypeCounts(data.ForwardTypes);
  237. //Render visitor data
  238. renderVisitorChart(data.RequestOrigin);
  239. //Render IP versions
  240. renderIPVersionChart(data.RequestClientIp);
  241. //Render user agent analysis
  242. renderUserAgentCharts(data.UserAgent);
  243. //Render file type by analysising request URL paths
  244. renderFileTypeGraph(data.RequestURL);
  245. //Render Referer header
  246. renderRefererTable(data.Referer);
  247. //Hide the trend graphs
  248. $("#trendGraphs").hide();
  249. });
  250. }
  251. initStatisticSummery();
  252. $("#statsRangeStart").val(getTodayStatisticKey().split("_").join("-"));
  253. function loadStatisticByRange(startdate, endDate){
  254. $.getJSON("/api/analytic/loadRange?start=" + startdate + "&end=" + endDate, function(data){
  255. //console.log(data);
  256. //Destroy all the previous charts
  257. statisticCharts.forEach(function(thisChart){
  258. thisChart.destroy();
  259. })
  260. if (data.Summary.TotalRequest == 0){
  261. //No data to analysis
  262. $("#statisticRenderElement").hide()
  263. $("#statisticRenderNotEnoughData").show();
  264. return;
  265. }else{
  266. $("#statisticRenderElement").show();
  267. $("#statisticRenderNotEnoughData").hide();
  268. }
  269. //Render the text values
  270. $("#statisticRenderElement").find(".totalViewCount").text(abbreviateNumber(data.Summary.TotalRequest));
  271. $("#statisticRenderElement").find(".totalSuccCount").text(abbreviateNumber(data.Summary.ValidRequest));
  272. $("#statisticRenderElement").find(".totalErrorCount").text(abbreviateNumber(data.Summary.ErrorRequest));
  273. //Render forward type data
  274. renderForwardTypeCounts(data.Summary.ForwardTypes);
  275. //Render visitor data
  276. renderVisitorChart(data.Summary.RequestOrigin);
  277. //Render IP versions
  278. renderIPVersionChart(data.Summary.RequestClientIp);
  279. //Render user agent analysis
  280. renderUserAgentCharts(data.Summary.UserAgent);
  281. //Render file type by analysising request URL paths
  282. renderFileTypeGraph(data.Summary.RequestURL);
  283. //Render Referer header
  284. renderRefererTable(data.Summary.Referer);
  285. //Render the trend graph
  286. $("#trendGraphs").show();
  287. renderTrendGraph(data.Records);
  288. });
  289. }
  290. picker.attach({ target: document.getElementById("statsRangeStart") });
  291. picker.attach({ target: document.getElementById("statsRangeEnd") });
  292. function renderForwardTypeCounts(forwardTypes){
  293. let tablBody = $("#statisticRenderElement").find(".forwardTypeCounts");
  294. tablBody.empty();
  295. let totalForwardCounts = 0;
  296. for (let [key, value] of Object.entries(forwardTypes)) {
  297. totalForwardCounts += value;
  298. }
  299. for (let [key, value] of Object.entries(forwardTypes)) {
  300. tablBody.append(`<tr>
  301. <td>${key}</td>
  302. <td>${abbreviateNumber(value)} (${value})</td>
  303. <td>${((value/totalForwardCounts)*100).toFixed(3)}%</td>
  304. </tr>
  305. `);
  306. }
  307. }
  308. function getTodayStatisticKey(){
  309. var today = new Date();
  310. var year = today.getFullYear();
  311. var month = String(today.getMonth() + 1).padStart(2, '0');
  312. var day = String(today.getDate()).padStart(2, '0');
  313. var formattedDate = year + '_' + month + '_' + day;
  314. return formattedDate
  315. }
  316. function handleLoadStatisticButtonPress(){
  317. var sd = $("#statsRangeStart").val();
  318. var ed = $("#statsRangeEnd").val();
  319. //Swap them if sd is later than ed
  320. if (sd != "" && ed != "" && sd > ed) {
  321. ed = [sd, sd = ed][0];
  322. $("#statsRangeStart").val(sd);
  323. $("#statsRangeEnd").val(ed);
  324. }
  325. initStatisticSummery(sd, ed);
  326. }
  327. function getStatisticDateRange(){
  328. var sd = $("#statsRangeStart").val();
  329. var ed = $("#statsRangeEnd").val();
  330. if (ed == ""){
  331. ed = sd;
  332. }
  333. if (sd == "" && ed == ""){
  334. var sk = getTodayStatisticKey();
  335. sd = sk;
  336. ed = sk;
  337. }
  338. //Swap them if sd is later than ed
  339. if (sd != "" && ed != "" && sd > ed) {
  340. ed = [sd, sd = ed][0];
  341. }
  342. return [sd, ed];
  343. }
  344. function clearStatisticDateRange(){
  345. $("#statsRangeStart").val("");
  346. $("#statsRangeEnd").val("");
  347. }
  348. function renderRefererTable(refererList){
  349. const sortedEntries = Object.entries(refererList).sort(([, valueA], [, valueB]) => valueB - valueA);
  350. $("#stats_RefererTable").html("");
  351. let endStop = 100;
  352. if (sortedEntries.length < 100){
  353. endStop = sortedEntries.length;
  354. }
  355. for (var i = 0; i < endStop; i++) {
  356. let referer = (decodeURIComponent(sortedEntries[i][0])).replace(/(<([^>]+)>)/ig,"");
  357. if (sortedEntries[i][0] == ""){
  358. //Root
  359. referer = `<span style="color: #b5b5b5;">(<i class="eye slash outline icon"></i> Unknown or Hidden)</span>`;
  360. }
  361. $("#stats_RefererTable").append(`<tr>
  362. <td>${referer}</td>
  363. <td>${sortedEntries[i][1]}</td>
  364. </tr>`);
  365. }
  366. }
  367. function renderFileTypeGraph(requestURLs){
  368. //Create the device chart
  369. let fileExtensions = {};
  370. for (const [url, count] of Object.entries(requestURLs)) {
  371. let filename = url.split("/").pop();
  372. let ext = "";
  373. if (filename == ""){
  374. //Loading from a folder
  375. ext = "Folder path"
  376. }else{
  377. if (filename.includes(".")){
  378. ext = filename.split(".").pop();
  379. }else{
  380. ext = "API call"
  381. }
  382. }
  383. if (fileExtensions[ext] != undefined){
  384. fileExtensions[ext] = fileExtensions[ext] + count;
  385. }else{
  386. //First time this ext show up
  387. fileExtensions[ext] = count;
  388. }
  389. }
  390. //Convert the key-value pairs to array for graph render
  391. let fileTypes = [];
  392. let fileCounts = [];
  393. let colors = [];
  394. for (const [ftype, count] of Object.entries(fileExtensions)) {
  395. fileTypes.push(ftype);
  396. fileCounts.push(count);
  397. colors.push(generateColorFromHash(ftype));
  398. }
  399. let filetypeChart = new Chart(document.getElementById("stats_filetype"), {
  400. type: 'pie',
  401. data: {
  402. labels: fileTypes,
  403. datasets: [{
  404. data: fileCounts,
  405. backgroundColor: colors,
  406. hoverBackgroundColor: colors,
  407. }]
  408. },
  409. options: {
  410. responsive: true,
  411. maintainAspectRatio: false,
  412. }
  413. });
  414. statisticCharts.push(filetypeChart);
  415. }
  416. function renderTrendGraph(dailySummary){
  417. // Get the canvas element
  418. const canvas = document.getElementById('requestTrends');
  419. //Generate the X axis labels
  420. let datesLabel = [];
  421. let succData = [];
  422. let errorData = [];
  423. let totalData = [];
  424. for (var i = 0; i < dailySummary.length; i++){
  425. let thisDayData = dailySummary[i];
  426. datesLabel.push("Day " + i);
  427. succData.push(thisDayData.ValidRequest);
  428. errorData.push(thisDayData.ErrorRequest);
  429. totalData.push(thisDayData.TotalRequest);
  430. }
  431. // Create the chart
  432. let TrendChart = new Chart(canvas, {
  433. type: 'line',
  434. data: {
  435. labels: datesLabel,
  436. datasets: [
  437. {
  438. label: 'All Requests',
  439. data: totalData,
  440. borderColor: '#7d99f7',
  441. backgroundColor: 'rgba(125, 153, 247, 0.4)',
  442. fill: false
  443. },
  444. {
  445. label: 'Success Requests',
  446. data: succData,
  447. borderColor: '#6dad7c',
  448. backgroundColor: "rgba(109, 173, 124, 0.4)",
  449. fill: true
  450. },
  451. {
  452. label: 'Error Requests',
  453. data: errorData,
  454. borderColor: '#de7373',
  455. backgroundColor: "rgba(222, 115, 115, 0.4)",
  456. fill: true
  457. },
  458. ]
  459. },
  460. options: {
  461. responsive: true,
  462. maintainAspectRatio: true,
  463. title: {
  464. display: true,
  465. //text: 'Line Chart Example'
  466. },
  467. scales: {
  468. x: {
  469. display: true,
  470. title: {
  471. display: false,
  472. text: 'Time'
  473. }
  474. },
  475. y: {
  476. display: true,
  477. title: {
  478. display: true,
  479. text: 'Request Counts'
  480. }
  481. }
  482. }
  483. }
  484. });
  485. statisticCharts.push(TrendChart);
  486. }
  487. function renderUserAgentCharts(userAgentsEntries){
  488. let userAgents = Object.keys(userAgentsEntries);
  489. let requestCounts = Object.values(userAgentsEntries);
  490. let mobileUser = 0;
  491. let desktopUser = 0;
  492. let osTypes = {};
  493. let osVersion = {};
  494. let browserTypes = {};
  495. let totalRequestCounts = 0;
  496. requestCounts.forEach(function(rc){
  497. totalRequestCounts += rc;
  498. })
  499. //Building a statistic summary
  500. userAgents.forEach(function(thisUA){
  501. var uaInfo = parseUserAgent(thisUA);
  502. if (uaInfo.isMobile){
  503. mobileUser+=userAgentsEntries[thisUA];
  504. }else{
  505. desktopUser+=userAgentsEntries[thisUA];
  506. }
  507. let currentNo = osTypes[uaInfo.os];
  508. let osVersionKey = uaInfo.os + " " + uaInfo.version.split("_").join(".");
  509. if (currentNo == undefined){
  510. osTypes[uaInfo.os] = userAgentsEntries[thisUA];
  511. }else{
  512. osTypes[uaInfo.os] = currentNo + userAgentsEntries[thisUA];
  513. }
  514. let p = osVersion[osVersionKey];
  515. if (p == undefined){
  516. osVersion[osVersionKey] = userAgentsEntries[thisUA];
  517. }else{
  518. osVersion[osVersionKey] = p + userAgentsEntries[thisUA];
  519. }
  520. let browserTypeKey = uaInfo.browser;
  521. if (browserTypeKey.indexOf("//") >= 0){
  522. //This is a uncatergorize browser, mostly a bot
  523. browserTypeKey = "Bots";
  524. }else if (browserTypeKey == ""){
  525. //No information
  526. browserTypeKey = "Unknown";
  527. }
  528. let b = browserTypes[browserTypeKey];
  529. if (b == undefined){
  530. browserTypes[browserTypeKey] = userAgentsEntries[thisUA];
  531. }else{
  532. browserTypes[browserTypeKey] = b + userAgentsEntries[thisUA];
  533. }
  534. });
  535. //Create the device chart
  536. let deviceTypeChart = new Chart(document.getElementById("stats_device"), {
  537. type: 'pie',
  538. data: {
  539. labels: ['Desktop', 'Mobile'],
  540. datasets: [{
  541. data: [desktopUser, mobileUser],
  542. backgroundColor: ['#e56b5e', '#6eb9c1'],
  543. hoverBackgroundColor: ['#e56b5e', '#6eb9c1']
  544. }]
  545. },
  546. options: {
  547. responsive: true,
  548. maintainAspectRatio: false,
  549. }
  550. });
  551. statisticCharts.push(deviceTypeChart);
  552. //Create the OS chart
  553. let OSnames = [];
  554. let OSCounts = [];
  555. let OSColors = [];
  556. for (const [key, value] of Object.entries(osTypes)) {
  557. OSnames.push(key);
  558. OSCounts.push(value);
  559. OSColors.push(getOSColorCode(key));
  560. }
  561. let osTypeChart = new Chart(document.getElementById("stats_OS"), {
  562. type: 'pie',
  563. data: {
  564. labels: OSnames,
  565. datasets: [{
  566. data: OSCounts,
  567. backgroundColor: OSColors,
  568. hoverBackgroundColor: OSColors
  569. }]
  570. },
  571. options: {
  572. responsive: true,
  573. maintainAspectRatio: false,
  574. }
  575. });
  576. statisticCharts.push(osTypeChart);
  577. //Populate the OS version table
  578. let sortedOSVersion = Object.entries(osVersion).sort((a, b) => b[1] - a[1]);
  579. $("#stats_OSVersionList").html("");
  580. // Loop through the sorted data and populate the table
  581. for (let i = 0; i < sortedOSVersion.length; i++) {
  582. let osVersion = sortedOSVersion[i][0];
  583. let requestcount = abbreviateNumber(sortedOSVersion[i][1]);
  584. let percentage = (sortedOSVersion[i][1] / totalRequestCounts * 100).toFixed(3);
  585. // Create a new row in the table
  586. let row = $("<tr>");
  587. // Add the OS version and percentage as columns in the row
  588. $("<td>").text(osVersion).appendTo(row);
  589. $("<td>").text(requestcount).appendTo(row);
  590. $("<td>").text(percentage + "%").appendTo(row);
  591. // Add the row to the table body
  592. $("#stats_OSVersionList").append(row);
  593. }
  594. //Create the browser charts
  595. let browserNames = [];
  596. let broserCounts = [];
  597. let browserColors = [];
  598. let sortedBrowserTypes = Object.entries(browserTypes).sort((a, b) => b[1] - a[1]);
  599. console.log(sortedBrowserTypes);
  600. sortedBrowserTypes.forEach(function(entry){
  601. browserNames.push(entry[0]);
  602. broserCounts.push(entry[1]);
  603. browserColors.push(generateColorFromHash(entry[0]));
  604. });
  605. let browserTypeChart = new Chart(document.getElementById("stats_browsers"), {
  606. type: 'pie',
  607. data: {
  608. labels: browserNames,
  609. datasets: [{
  610. data: broserCounts,
  611. backgroundColor: browserColors,
  612. hoverBackgroundColor: browserColors
  613. }]
  614. },
  615. options: {
  616. responsive: true,
  617. maintainAspectRatio: false,
  618. }
  619. });
  620. statisticCharts.push(browserTypeChart);
  621. }
  622. //Generate the IPversion pie chart
  623. function renderIPVersionChart(RequestClientIp){
  624. let ipv4Count = Object.keys(RequestClientIp).filter(ip => ip.includes('.')).length;
  625. let ipv6Count = Object.keys(RequestClientIp).filter(ip => ip.includes(':')).length;
  626. let totalCount = ipv4Count + ipv6Count;
  627. let ipv4Percent = ((ipv4Count / totalCount) * 100).toFixed(2);
  628. let ipv6Percent = ((ipv6Count / totalCount) * 100).toFixed(2);
  629. // Create the chart data object
  630. let chartData = {
  631. labels: ['IPv4', 'IPv6'],
  632. datasets: [{
  633. data: [ipv4Percent, ipv6Percent],
  634. backgroundColor: ['#9295f0', '#bff092'],
  635. hoverBackgroundColor: ['#9295f0', '#bff092']
  636. }]
  637. };
  638. // Create the chart options object
  639. let ipvChartOption = {
  640. responsive: true,
  641. maintainAspectRatio: false,
  642. };
  643. // Create the pie chart
  644. let ipTypeChart = new Chart(document.getElementById("stats_ipver"), {
  645. type: 'pie',
  646. data: chartData,
  647. options: ipvChartOption
  648. });
  649. statisticCharts.push(ipTypeChart);
  650. //Populate the request count table
  651. let requestCounts = Object.entries(RequestClientIp);
  652. // Sort the array by the value (count)
  653. requestCounts.sort((a, b) => b[1] - a[1]);
  654. // Select the table body and empty it
  655. let tableBody = $('#stats_requestCountlist');
  656. tableBody.empty();
  657. // Loop through the sorted array and add the top 25 requested IPs to the table
  658. for (let i = 0; i < 25 && i < requestCounts.length; i++) {
  659. let [ip, count] = requestCounts[i];
  660. let row = $('<tr>').appendTo(tableBody);
  661. $('<td style="word-break: break-all;">').text(ip).appendTo(row);
  662. $('<td>').text(count).appendTo(row);
  663. }
  664. }
  665. //Generate a fixed color code from string hash
  666. function generateColorFromHash(stringToHash){
  667. let hash = 0;
  668. for (let i = 0; i < stringToHash.length; i++) {
  669. hash = stringToHash.charCodeAt(i) + ((hash << 5) - hash);
  670. }
  671. const hue = hash % 300;
  672. const saturation = Math.floor(Math.random() * 20) + 70;
  673. const lightness = Math.floor(Math.random() * 20) + 60;
  674. return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
  675. }
  676. //Generate the visitor country pie chart
  677. function renderVisitorChart(visitorData){
  678. // Extract the labels and data from the visitor data object
  679. let labels = [];
  680. let data = Object.values(visitorData);
  681. Object.keys(visitorData).forEach(function(cc){
  682. console.log(cc);
  683. if (cc == ""){
  684. labels.push("Unknown")
  685. }else if (cc == "lan"){
  686. labels.push(`LAN / Loopback`);
  687. }else{
  688. labels.push(`${getCountryName(cc)} [${cc.toUpperCase()}]` );
  689. }
  690. });
  691. // Define the colors to be used in the pie chart
  692. let colors = [];
  693. labels.forEach(function(cc){
  694. colors.push(generateColorFromHash(cc));
  695. });
  696. // Create the chart data object
  697. let CCchartData = {
  698. labels: labels,
  699. datasets: [{
  700. data: data,
  701. backgroundColor: colors
  702. }]
  703. };
  704. // Create the chart options object
  705. let CCchartOptions = {
  706. responsive: true,
  707. maintainAspectRatio: false,
  708. };
  709. // Create the pie chart
  710. const visitorsChart = new Chart(document.getElementById("stats_visitors"), {
  711. type: 'pie',
  712. data: CCchartData,
  713. options: CCchartOptions
  714. });
  715. statisticCharts.push(visitorsChart);
  716. }
  717. </script>