stats.html 16 KB

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