stats.html 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  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 class="ui small input">
  9. <input type="text" id="statsRangeStart" placeholder="From date">
  10. </div>
  11. To
  12. <div class="ui small input">
  13. <input type="text" id="statsRangeEnd" placeholder="End date">
  14. </div>
  15. <button onclick="handleLoadStatisticButtonPress();" class="ui basic button"><i class="search icon"></i> Search</button><br>
  16. <small>Leave end range as empty for showing starting day only statistic</small>
  17. </div>
  18. <div class="ui divider"></div>
  19. <div id="statisticRenderNotEnoughData" class="ui segment" style="padding: 2em;" align="center">
  20. <h4 class="ui icon header" style="color: #7c7c7c !important;">
  21. <i class="medium icons">
  22. <i class="chart pie icon"></i>
  23. <i class="small corner remove icon" style="
  24. font-size: 1.4em;
  25. margin-right: -0.2em;
  26. margin-bottom: -0em;
  27. "></i>
  28. </i>
  29. <div class="content" >
  30. Unable To Generate Analytics Report
  31. <div class="sub header" style="color: #adadad !important;">The selected period contains too little or no request data collected. <br>
  32. Please select a larger range or make sure there are enough traffic routing through this site.</div>
  33. </div>
  34. </h4>
  35. </div>
  36. <div id="statisticRenderElement" class="ui basic segment">
  37. <!-- Client Geolocation Analysis-->
  38. <h3>Visitors Countries</h3>
  39. <p>Distributions of visitors by country code. Access origin are estimated using open source GeoIP database and might not be accurate.</p>
  40. <div style="min-height: 400px;">
  41. <canvas id="stats_visitors"></canvas>
  42. </div>
  43. <div class="ui divider"></div>
  44. <!-- Client IP Analysis -->
  45. <div class="ui stackable grid">
  46. <div class="eight wide column">
  47. <h3>Requests IP Version</h3>
  48. <p>The version of Internet Protocol that the client is using. If the request client is pass through a DNS proxy like CloudFlare,
  49. the CloudFlare proxy server address will be filtered and only the first value in the RemoteAddress field will be analysised.</p>
  50. <div>
  51. <canvas id="stats_ipver"></canvas>
  52. </div>
  53. </div>
  54. <div class="eight wide column">
  55. <h3>Request Origins</h3>
  56. <p>Top 25 request origin sorted by request count</p>
  57. <div style="height: 500px; overflow-y: auto;">
  58. <table class="ui unstackable striped celled table">
  59. <thead>
  60. <tr>
  61. <th>Request Origin</th>
  62. <th>No of Requests</th>
  63. </tr></thead>
  64. <tbody id="stats_requestCountlist">
  65. </tbody>
  66. </table>
  67. </div>
  68. </div>
  69. </div>
  70. <div class="ui divider"></div>
  71. <!-- Client Device Analysis -->
  72. <div class="ui stackable grid">
  73. <div class="eight wide column">
  74. <h3>Client Devices</h3>
  75. <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>
  76. <div>
  77. <canvas id="stats_device"></canvas>
  78. </div>
  79. </div>
  80. <div class="eight wide column">
  81. <h3>Client Browsers</h3>
  82. <p>The browsers where your client is using to visit your site</p>
  83. <div>
  84. <canvas id="stats_browsers"></canvas>
  85. </div>
  86. </div>
  87. </div>
  88. <div class="ui stackable grid">
  89. <div class="eight wide column">
  90. <h3>Client OS</h3>
  91. <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>
  92. <div>
  93. <canvas id="stats_OS"></canvas>
  94. </div>
  95. </div>
  96. <div class="eight wide column">
  97. <h3>OS Versions</h3>
  98. <p>The OS versions most commonly used by your site's visitors.</p>
  99. <div style="height: 500px; overflow-y: auto;">
  100. <table class="ui unstackable striped celled table">
  101. <thead>
  102. <tr>
  103. <th>OS Version</th>
  104. <th>Request Counts</th>
  105. <th>Percentage</th>
  106. </tr></thead>
  107. <tbody id="stats_OSVersionList">
  108. </tbody>
  109. </table>
  110. </div>
  111. </div>
  112. </div>
  113. </div>
  114. <button class="ui icon right floated basic button" onclick="initStatisticSummery();"><i class="green refresh icon"></i> Refresh</button>
  115. <br><br>
  116. </div>
  117. <script>
  118. var statisticCharts = [];
  119. //Start day must be earlier than end date
  120. function initStatisticSummery(startdate="", enddate=""){
  121. if (startdate == "" && enddate == "" ){
  122. let todaykey = getTodayStatisticKey();
  123. loadStatisticByDate(todaykey);
  124. }else if ((startdate != "" && enddate == "") || startdate == enddate){
  125. //Load the target date
  126. let targetDate = startdate.trim();
  127. loadStatisticByDate(targetDate);
  128. }else{
  129. //Two dates are given and they are not identical
  130. console.log(startdate, enddate);
  131. }
  132. }
  133. function loadStatisticByDate(dateid){
  134. $.getJSON("/api/analytic/load?id=" + dateid, function(data){
  135. //Destroy all the previous charts
  136. statisticCharts.forEach(function(thisChart){
  137. thisChart.destroy();
  138. })
  139. if (data.TotalRequest == 0){
  140. //No data to analysis
  141. $("#statisticRenderElement").hide()
  142. $("#statisticRenderNotEnoughData").show();
  143. return;
  144. }else{
  145. $("#statisticRenderElement").show();
  146. $("#statisticRenderNotEnoughData").hide();
  147. }
  148. //Render visitor data
  149. renderVisitorChart(data.RequestOrigin);
  150. //Render IP versions
  151. renderIPVersionChart(data.RequestClientIp);
  152. //Render user agent analysis
  153. renderUserAgentCharts(data.UserAgent);
  154. });
  155. }
  156. initStatisticSummery();
  157. picker.attach({ target: document.getElementById("statsRangeStart") });
  158. picker.attach({ target: document.getElementById("statsRangeEnd") });
  159. function getTodayStatisticKey(){
  160. var today = new Date();
  161. var year = today.getFullYear();
  162. var month = String(today.getMonth() + 1).padStart(2, '0');
  163. var day = String(today.getDate()).padStart(2, '0');
  164. var formattedDate = year + '_' + month + '_' + day;
  165. return formattedDate
  166. }
  167. function handleLoadStatisticButtonPress(){
  168. var sd = $("#statsRangeStart").val();
  169. var ed = $("#statsRangeEnd").val();
  170. //Swap them if sd is later than ed
  171. if (sd > ed) {
  172. ed = [sd, sd = ed][0];
  173. $("#statsRangeStart").val(sd);
  174. $("#statsRangeEnd").val(ed);
  175. }
  176. initStatisticSummery(sd, ed);
  177. }
  178. function renderUserAgentCharts(userAgentsEntries){
  179. let userAgents = Object.keys(userAgentsEntries);
  180. let requestCounts = Object.values(userAgentsEntries);
  181. let mobileUser = 0;
  182. let desktopUser = 0;
  183. let osTypes = {};
  184. let osVersion = {};
  185. let browserTypes = {};
  186. let totalRequestCounts = 0;
  187. requestCounts.forEach(function(rc){
  188. totalRequestCounts += rc;
  189. })
  190. //Building a statistic summary
  191. userAgents.forEach(function(thisUA){
  192. var uaInfo = parseUserAgent(thisUA);
  193. if (uaInfo.isMobile){
  194. mobileUser+=userAgentsEntries[thisUA];
  195. }else{
  196. desktopUser+=userAgentsEntries[thisUA];
  197. }
  198. let currentNo = osTypes[uaInfo.os];
  199. let osVersionKey = uaInfo.os + " " + uaInfo.version.split("_").join(".");
  200. if (currentNo == undefined){
  201. osTypes[uaInfo.os] = userAgentsEntries[thisUA];
  202. }else{
  203. osTypes[uaInfo.os] = currentNo + userAgentsEntries[thisUA];
  204. }
  205. let p = osVersion[osVersionKey];
  206. if (p == undefined){
  207. osVersion[osVersionKey] = userAgentsEntries[thisUA];
  208. }else{
  209. osVersion[osVersionKey] = p + userAgentsEntries[thisUA];
  210. }
  211. let browserTypeKey = uaInfo.browser;
  212. if (browserTypeKey.indexOf("//") >= 0){
  213. //This is a uncatergorize browser, mostly a bot
  214. browserTypeKey = "Bots";
  215. }else if (browserTypeKey == ""){
  216. //No information
  217. browserTypeKey = "Unknown";
  218. }
  219. let b = browserTypes[browserTypeKey];
  220. if (b == undefined){
  221. browserTypes[browserTypeKey] = userAgentsEntries[thisUA];
  222. }else{
  223. browserTypes[browserTypeKey] = b + userAgentsEntries[thisUA];
  224. }
  225. });
  226. //Create the device chart
  227. let deviceTypeChart = new Chart(document.getElementById("stats_device"), {
  228. type: 'pie',
  229. data: {
  230. labels: ['Desktop', 'Mobile'],
  231. datasets: [{
  232. data: [desktopUser, mobileUser],
  233. backgroundColor: ['#e56b5e', '#6eb9c1'],
  234. hoverBackgroundColor: ['#e56b5e', '#6eb9c1']
  235. }]
  236. },
  237. options: {
  238. responsive: true,
  239. maintainAspectRatio: false,
  240. }
  241. });
  242. statisticCharts.push(deviceTypeChart);
  243. //Create the OS chart
  244. let OSnames = [];
  245. let OSCounts = [];
  246. let OSColors = [];
  247. for (const [key, value] of Object.entries(osTypes)) {
  248. OSnames.push(key);
  249. OSCounts.push(value);
  250. OSColors.push(getOSColorCode(key));
  251. }
  252. let osTypeChart = new Chart(document.getElementById("stats_OS"), {
  253. type: 'pie',
  254. data: {
  255. labels: OSnames,
  256. datasets: [{
  257. data: OSCounts,
  258. backgroundColor: OSColors,
  259. hoverBackgroundColor: OSColors
  260. }]
  261. },
  262. options: {
  263. responsive: true,
  264. maintainAspectRatio: false,
  265. }
  266. });
  267. statisticCharts.push(osTypeChart);
  268. //Populate the OS version table
  269. let sortedOSVersion = Object.entries(osVersion).sort((a, b) => b[1] - a[1]);
  270. $("#stats_OSVersionList").html("");
  271. // Loop through the sorted data and populate the table
  272. for (let i = 0; i < sortedOSVersion.length; i++) {
  273. let osVersion = sortedOSVersion[i][0];
  274. let requestcount = abbreviateNumber(sortedOSVersion[i][1]);
  275. let percentage = (sortedOSVersion[i][1] / totalRequestCounts * 100).toFixed(3);
  276. // Create a new row in the table
  277. let row = $("<tr>");
  278. // Add the OS version and percentage as columns in the row
  279. $("<td>").text(osVersion).appendTo(row);
  280. $("<td>").text(requestcount).appendTo(row);
  281. $("<td>").text(percentage + "%").appendTo(row);
  282. // Add the row to the table body
  283. $("#stats_OSVersionList").append(row);
  284. }
  285. //Create the browser charts
  286. let browserNames = [];
  287. let broserCounts = [];
  288. let browserColors = [];
  289. let sortedBrowserTypes = Object.entries(browserTypes).sort((a, b) => b[1] - a[1]);
  290. console.log(sortedBrowserTypes);
  291. sortedBrowserTypes.forEach(function(entry){
  292. browserNames.push(entry[0]);
  293. broserCounts.push(entry[1]);
  294. browserColors.push(generateColorFromHash(entry[0]));
  295. });
  296. let browserTypeChart = new Chart(document.getElementById("stats_browsers"), {
  297. type: 'pie',
  298. data: {
  299. labels: browserNames,
  300. datasets: [{
  301. data: broserCounts,
  302. backgroundColor: browserColors,
  303. hoverBackgroundColor: browserColors
  304. }]
  305. },
  306. options: {
  307. responsive: true,
  308. maintainAspectRatio: false,
  309. }
  310. });
  311. statisticCharts.push(browserTypeChart);
  312. }
  313. //Generate the IPversion pie chart
  314. function renderIPVersionChart(RequestClientIp){
  315. let ipv4Count = Object.keys(RequestClientIp).filter(ip => ip.includes('.')).length;
  316. let ipv6Count = Object.keys(RequestClientIp).filter(ip => ip.includes(':')).length;
  317. let totalCount = ipv4Count + ipv6Count;
  318. let ipv4Percent = ((ipv4Count / totalCount) * 100).toFixed(2);
  319. let ipv6Percent = ((ipv6Count / totalCount) * 100).toFixed(2);
  320. // Create the chart data object
  321. let chartData = {
  322. labels: ['IPv4', 'IPv6'],
  323. datasets: [{
  324. data: [ipv4Percent, ipv6Percent],
  325. backgroundColor: ['#9295f0', '#bff092'],
  326. hoverBackgroundColor: ['#9295f0', '#bff092']
  327. }]
  328. };
  329. // Create the chart options object
  330. let ipvChartOption = {
  331. responsive: true,
  332. maintainAspectRatio: false,
  333. };
  334. // Create the pie chart
  335. let ipTypeChart = new Chart(document.getElementById("stats_ipver"), {
  336. type: 'pie',
  337. data: chartData,
  338. options: ipvChartOption
  339. });
  340. statisticCharts.push(ipTypeChart);
  341. //Populate the request count table
  342. let requestCounts = Object.entries(RequestClientIp);
  343. // Sort the array by the value (count)
  344. requestCounts.sort((a, b) => b[1] - a[1]);
  345. // Select the table body and empty it
  346. let tableBody = $('#stats_requestCountlist');
  347. tableBody.empty();
  348. // Loop through the sorted array and add the top 25 requested IPs to the table
  349. for (let i = 0; i < 25 && i < requestCounts.length; i++) {
  350. let [ip, count] = requestCounts[i];
  351. let row = $('<tr>').appendTo(tableBody);
  352. $('<td style="word-break: break-all;">').text(ip).appendTo(row);
  353. $('<td>').text(count).appendTo(row);
  354. }
  355. }
  356. //Generate a fixed color code from string hash
  357. function generateColorFromHash(stringToHash){
  358. let hash = 0;
  359. for (let i = 0; i < stringToHash.length; i++) {
  360. hash = stringToHash.charCodeAt(i) + ((hash << 5) - hash);
  361. }
  362. const hue = hash % 300;
  363. const saturation = Math.floor(Math.random() * 20) + 70;
  364. const lightness = Math.floor(Math.random() * 20) + 60;
  365. return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
  366. }
  367. //Generate the visitor country pie chart
  368. function renderVisitorChart(visitorData){
  369. // Extract the labels and data from the visitor data object
  370. let labels = [];
  371. let data = Object.values(visitorData);
  372. Object.keys(visitorData).forEach(function(cc){
  373. if (cc == ""){
  374. labels.push("Local / Unknown")
  375. }else{
  376. labels.push(`${getCountryName(cc)} [${cc.toUpperCase()}]` );
  377. }
  378. });
  379. // Define the colors to be used in the pie chart
  380. let colors = [];
  381. labels.forEach(function(cc){
  382. colors.push(generateColorFromHash(cc));
  383. });
  384. // Create the chart data object
  385. let CCchartData = {
  386. labels: labels,
  387. datasets: [{
  388. data: data,
  389. backgroundColor: colors
  390. }]
  391. };
  392. // Create the chart options object
  393. let CCchartOptions = {
  394. responsive: true,
  395. maintainAspectRatio: false,
  396. };
  397. // Create the pie chart
  398. const visitorsChart = new Chart(document.getElementById("stats_visitors"), {
  399. type: 'pie',
  400. data: CCchartData,
  401. options: CCchartOptions
  402. });
  403. statisticCharts.push(visitorsChart);
  404. }
  405. </script>