Pārlūkot izejas kodu

auto update script executed

tobychui 1 gadu atpakaļ
vecāks
revīzija
04c60a33d3

+ 21 - 7
mod/statistic/analytic/analytic.go

@@ -3,6 +3,7 @@ package analytic
 import (
 	"encoding/json"
 	"net/http"
+	"time"
 
 	"imuslab.com/zoraxy/mod/database"
 	"imuslab.com/zoraxy/mod/statistic"
@@ -10,13 +11,15 @@ import (
 )
 
 type DataLoader struct {
-	Database *database.Database
+	Database           *database.Database
+	StatisticCollector *statistic.Collector
 }
 
 //Create a new data loader for loading statistic from database
-func NewDataLoader(db *database.Database) *DataLoader {
+func NewDataLoader(db *database.Database, sc *statistic.Collector) *DataLoader {
 	return &DataLoader{
-		Database: db,
+		Database:           db,
+		StatisticCollector: sc,
 	}
 }
 
@@ -43,13 +46,24 @@ func (d *DataLoader) HandleLoadTargetDaySummary(w http.ResponseWriter, r *http.R
 		return
 	}
 
-	targetDailySummary := statistic.DailySummaryExport{}
-	err = d.Database.Read("stats", day, &targetDailySummary)
-	if err != nil {
-		utils.SendErrorResponse(w, "target day data not found")
+	if !statistic.IsBeforeToday(day) {
+		utils.SendErrorResponse(w, "given date is in the future")
 		return
 	}
 
+	var targetDailySummary statistic.DailySummaryExport
+
+	if day == time.Now().Format("2006_01_02") {
+		targetDailySummary = *d.StatisticCollector.GetExportSummary()
+	} else {
+		//Not today data
+		err = d.Database.Read("stats", day, &targetDailySummary)
+		if err != nil {
+			utils.SendErrorResponse(w, "target day data not found")
+			return
+		}
+	}
+
 	js, _ := json.Marshal(targetDailySummary)
 	utils.SendJSONResponse(w, string(js))
 }

+ 17 - 0
mod/statistic/utils.go

@@ -1,5 +1,10 @@
 package statistic
 
+import (
+	"fmt"
+	"time"
+)
+
 func isWebPageExtension(ext string) bool {
 	webPageExts := []string{".html", ".htm", ".php", ".jsp", ".aspx", ".js", ".jsx"}
 	for _, e := range webPageExts {
@@ -9,3 +14,15 @@ func isWebPageExtension(ext string) bool {
 	}
 	return false
 }
+
+func IsBeforeToday(dateString string) bool {
+	layout := "2006_01_02"
+	date, err := time.Parse(layout, dateString)
+	if err != nil {
+		fmt.Println("Error parsing date:", err)
+		return false
+	}
+
+	today := time.Now().UTC().Truncate(24 * time.Hour)
+	return date.Before(today) || dateString == time.Now().Format(layout)
+}

+ 1 - 1
start.go

@@ -178,5 +178,5 @@ func startupSequence() {
 	EmailSender = loadSMTPConfig()
 
 	//Create an analytic loader
-	AnalyticLoader = analytic.NewDataLoader(sysdb)
+	AnalyticLoader = analytic.NewDataLoader(sysdb, statisticCollector)
 }

+ 37 - 11
web/components/stats.html

@@ -1,8 +1,20 @@
 <script src="./script/useragent.js"></script>
+<link href="./script/datepicker/dp-light.css" rel="stylesheet">
+<script defer src="./script/datepicker/datepicker.js"></script>
+
 <div class="standardContainer">
     <div class="ui basic segment">
         <h2>Statistical Analysis</h2>
         <p>Statistic of your server in every aspects</p>
+        <div class="ui small input">
+            <input type="text" id="statsRangeStart" placeholder="From date">
+        </div>
+         To 
+        <div class="ui small input">
+            <input type="text" id="statsRangeEnd" placeholder="End date">
+        </div>
+        <button class="ui basic button"><i class="search icon"></i> Search</button><br>
+        <small>Leave end range as empty for showing starting day only statistic</small>
     </div>
     <div class="ui divider"></div>
     <div class="ui basic segment">
@@ -89,22 +101,36 @@
     <br><br>
 </div>
 <script>
-   function initStatisticSummery(){
-        $.getJSON("./example.json", function(data){
-            //Render visitor data
-            renderVisitorChart(data.RequestOrigin);
+   function initStatisticSummery(startdate="", enddate=""){
+        if (startdate == "" && enddate == "" ){
+            let todaykey = getTodayStatisticKey();
+            $.getJSON("/api/analytic/load?id=" + todaykey, function(data){
+                console.log(data, todaykey);
+                //Render visitor data
+                renderVisitorChart(data.RequestOrigin);
 
-            //Render IP versions
-            renderIPVersionChart(data.RequestClientIp);
+                //Render IP versions
+                renderIPVersionChart(data.RequestClientIp);
 
-            //Render user agent analysis
-            renderUserAgentCharts(data.UserAgent);
-        });
+                //Render user agent analysis
+                renderUserAgentCharts(data.UserAgent);
+            });
+        }
    }
    initStatisticSummery();
 
+   picker.attach({ target: document.getElementById("statsRangeStart") });
+   picker.attach({ target: document.getElementById("statsRangeEnd") });
+   
+   function getTodayStatisticKey(){
+        var today = new Date();
+        var year = today.getFullYear();
+        var month = String(today.getMonth() + 1).padStart(2, '0');
+        var day = String(today.getDate()).padStart(2, '0');
 
-  
+        var formattedDate = year + '_' + month + '_' + day;
+        return formattedDate
+   }
 
     function renderUserAgentCharts(userAgentsEntries){
         let userAgents = Object.keys(userAgentsEntries);
@@ -212,7 +238,7 @@
         for (let i = 0; i < sortedOSVersion.length; i++) {
             let osVersion = sortedOSVersion[i][0];
             let requestcount = abbreviateNumber(sortedOSVersion[i][1]);
-            let percentage = (sortedOSVersion[i][1] / totalRequestCounts).toFixed(3);
+            let percentage = (sortedOSVersion[i][1] / totalRequestCounts * 100).toFixed(3);
             
             // Create a new row in the table
             let row = $("<tr>");

+ 29 - 0
web/script/datepicker/README.txt

@@ -0,0 +1,29 @@
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+LICENSE
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+Copyright by Code Boxx
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+MORE
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+Please visit https://code-boxx.com/ for more!

+ 213 - 0
web/script/datepicker/datepicker.js

@@ -0,0 +1,213 @@
+var picker = {
+  // (A) COMMON MONTH NAMES
+  months : ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
+            "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
+
+  // (B) ATTACH DATEPICKER TO TARGET
+  //  target: field to populate
+  //  container: generate datepicker in here (for inline datepicker)
+  //  startmon: start on mon? (optional, default false)
+  //  yrange: year select range (optional, default 10)
+  //  disableday: days to disable, e.g. [2,7] to disable tue and sun (optional)
+  //  onpick : function to call on select date (optional)
+  attach : instance => {
+    // (B1) SET DEFAULT OPTIONS
+    instance.target.readOnly = true; // prevent onscreen keyboard
+    instance.startmon = instance.startmon ? true : false;
+    instance.yrange = instance.yrange ? instance.yrange : 10;
+
+    // (B2) CURRENT MONTH YEAR (UTC+0)
+    var today = new Date(),
+        thisMonth = today.getUTCMonth(), // jan is 0
+        thisYear = today.getUTCFullYear(); // yyyy
+
+    // (B3) GENERATE HTML
+    // (B3-1) DATEPICKER WRAPPER + BASIC STRUCTURE
+    instance.hWrap = document.createElement("div");
+    instance.hWrap.classList.add("picker-wrap");
+    instance.hWrap.innerHTML =
+    `<div class="picker">
+      <div class="picker-p">
+        <div class="picker-b">&lt;</div>
+        <select class="picker-m"></select>
+        <select class="picker-y"></select>
+        <div class="picker-n">&gt;</div>
+      </div>
+      <div class="picker-d"></div>
+    </div>`;
+    instance.hMonth = instance.hWrap.querySelector(".picker-m");
+    instance.hYear = instance.hWrap.querySelector(".picker-y");
+    instance.hDays = instance.hWrap.querySelector(".picker-d");
+
+    // (B3-2) SHIFT PERIOD
+    instance.hWrap.querySelector(".picker-b").onclick = () => picker.shift(instance);
+    instance.hWrap.querySelector(".picker-n").onclick = () => picker.shift(instance, 1);
+
+    // (B3-3) MONTH SELECTOR
+    for (let m in picker.months) {
+      let o = document.createElement("option");
+      o.value = +m + 1;
+      o.text = picker.months[m];
+      instance.hMonth.appendChild(o);
+    }
+    instance.hMonth.selectedIndex = thisMonth;
+    instance.hMonth.onchange = () => picker.draw(instance);
+
+    // (B3-4) YEAR SELECTOR
+    for (let y = thisYear-instance.yrange; y < thisYear+instance.yrange; y++) {
+      let o = document.createElement("option");
+      o.value = y;
+      o.text = y;
+      instance.hYear.appendChild(o);
+    }
+    instance.hYear.selectedIndex = instance.yrange;
+    instance.hYear.onchange = () => picker.draw(instance);
+
+    // (B4) INLINE DATEPICKER - ATTACH INTO CONTAINER
+    if (instance.container) { instance.container.appendChild(instance.hWrap); }
+
+    // (B5) POPUP DATEPICKER - ATTACH INTO HTML BODY
+    else {
+      instance.hWrap.classList.add("popup");
+      instance.target.onfocus = () => instance.hWrap.classList.add("show");
+      instance.hWrap.onclick = e => { if (e.target == instance.hWrap) { instance.hWrap.classList.remove("show"); }};
+      document.body.appendChild(instance.hWrap);
+    }
+
+    // (B6) INIT DRAW
+    picker.draw(instance);
+  },
+
+  // (C) SHIFT PERIOD (BY 1 MONTH)
+  shift : (instance, next) => {
+    var m = +instance.hMonth.value, y = +instance.hYear.value;
+    if (next) {
+      m++;
+      if (m>12) { m = 1; y++; }
+      let max = instance.hYear.querySelector("option:last-child").value;
+      if (y>max) { m = 12; y = max; }
+    } else {
+      m--;
+      if (m<1) { m = 12; y--; }
+      let min = instance.hYear.querySelector("option:first-child").value;
+      if (y<min) { m = 1; y = min; }
+    }
+    instance.hMonth.value = m;
+    instance.hYear.value = y;
+    picker.draw(instance);
+  },
+
+  // (D) DRAW DAYS IN MONTH
+  draw : instance => {
+    // (D1) A LOT OF CALCULATIONS
+    // (D1-1) SELECTED MONTH YEAR
+    var month = instance.hMonth.value,
+        year = instance.hYear.value;
+
+    // (D1-2) DATE RANGE CALCULATION (UTC+0)
+    var daysInMonth = new Date(Date.UTC(year, month, 0)).getUTCDate(),
+        startDay = new Date(Date.UTC(year, month-1, 1)).getUTCDay(), // sun is 0
+        endDay = new Date(Date.UTC(year, month-1, daysInMonth)).getUTCDay();
+    startDay = startDay==0 ? 7 : startDay,
+    endDay = endDay==0 ? 7 : endDay;
+
+    // (D1-3) TODAY (FOR HIGHLIGHTING "TODAY")
+    var today = new Date(), todayDate = null;
+    if (today.getUTCMonth()+1 == month && today.getUTCFullYear() == year) {
+      todayDate = today.getUTCDate();
+    }
+
+    // (D1-4) DAY NAMES
+    var daynames = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+    if (instance.startmon) { daynames.push("Sun"); }
+    else { daynames.unshift("Sun"); }
+
+    // (D2) CALCULATE DATE SQUARES
+    // (D2-1) EMPTY SQUARES BEFORE FIRST DAY OF MONTH
+    var squares = [];
+    if (instance.startmon && startDay!=1) {
+      for (let i=1; i<startDay; i++) { squares.push("B"); }
+    }
+    if (!instance.startmon && startDay!=7) {
+      for (let i=0; i<startDay; i++) { squares.push("B"); }
+    }
+
+    // (D2-2) DAYS OF MONTH (SOME DAYS DISABLED)
+    if (instance.disableday) {
+      let thisDay = startDay;
+      for (let i=1; i<=daysInMonth; i++) {
+        squares.push([i, instance.disableday.includes(thisDay)]);
+        thisDay++;
+        if (thisDay==8) { thisDay = 1; }
+      }
+    }
+
+    // (D2-3) DAYS OF MONTH (ALL DAYS ENABLED)
+    else {
+      for (let i=1; i<=daysInMonth; i++) { squares.push([i, false]);  }
+    }
+
+    // (D2-4) EMPTY SQUARES AFTER LAST DAY OF MONTH
+    if (instance.startmon && endDay!=7) {
+      for (let i=endDay; i<7; i++) { squares.push("B"); }
+    }
+    if (!instance.startmon && endDay!=6) {
+      for (let i=endDay; i<(endDay==7?13:6); i++) { squares.push("B"); }
+    }
+
+    // (D3) DRAW HTML FINALLY
+    // (D3-1) EMPTY CURRENT
+    instance.hDays.innerHTML = "";
+    
+    // (D3-2) FIRST ROW - DAY NAMES HEADER
+    var cell;
+    for (let d of daynames) {
+      cell = document.createElement("div");
+      cell.innerHTML = d;
+      cell.classList.add("picker-d-h");
+      instance.hDays.appendChild(cell);
+    }
+
+    // (D3-3) FOLLOWING ROWS - DATE CELLS
+    for (let i=0; i<squares.length; i++) {
+      cell = document.createElement("div");
+      if (squares[i] == "B") { cell.classList.add("picker-d-b"); }
+      else {
+        // (D3-2A) CELL DATE
+        cell.innerHTML = squares[i][0];
+
+        // (D3-2B) NOT ALLOWED TO CHOOSE THIS DAY
+        if (squares[i][1]) { cell.classList.add("picker-d-dd"); }
+
+        // (D3-2C) ALLOWED TO CHOOSE THIS DAY
+        else {
+          if (squares[i][0] == todayDate) { cell.classList.add("picker-d-td"); }
+          cell.classList.add("picker-d-d");
+          cell.onclick = () => picker.pick(instance, squares[i][0]);
+        }
+      }
+      instance.hDays.appendChild(cell);
+    }
+  },
+
+  // (E) CHOOSE A DATE
+  pick : (instance, d) => {
+    // (E1) GET MONTH YEAR
+    let m = instance.hMonth.value,
+        y = instance.hYear.value;
+
+    // (E2) FORMAT & SET SELECTED DAY (YYYY-MM-DD)
+    if (+m<10) { m = "0" + m; }
+    if (+d<10) { d = "0" + d; }
+    let formatted = `${y}-${m}-${d}`;
+    instance.target.value = formatted;
+
+    // (E3) POPUP ONLY - CLOSE
+    if (instance.hWrap.classList.contains("popup")) {
+      instance.hWrap.classList.remove("show");
+    }
+
+    // (E4) CALL ON PICK IF DEFINED
+    if (instance.onpick) { instance.onpick(formatted); }
+  }
+};

+ 85 - 0
web/script/datepicker/dp-dark.css

@@ -0,0 +1,85 @@
+/* (A) WRAPPER - POPUP MODE */
+.picker-wrap, .picker-wrap * { box-sizing: border-box; }
+.picker-wrap.popup {
+  /* (A1) FULLSCREEN COVER */
+  position: fixed;
+  top: 0; left: 0; z-index: 99;
+  width: 100vw; height: 100vh;
+
+  /* (A2) BACKGROUND + HIDE BY DEFAULT */
+  background: rgba(0, 0, 0, 0.5);
+  opacity: 0; visibility: hidden;
+  transition: opacity 0.2s;
+
+  /* (A3) CENTER DATE PICKER */
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+/* (A4) SHOW POPUP */
+.picker-wrap.show {
+  opacity: 1;
+  visibility: visible;
+}
+
+/* (B) CONTAINER */
+.picker {
+  max-width: 300px;
+  border: 1px solid #000;
+  background: #444;
+  padding: 10px;
+}
+
+/* (C) PERIOD SELECTOR */
+.picker-p {
+  width: 100%;
+  display: flex;
+  background: #333;
+}
+.picker-b, .picker-n {
+  font-weight: 700;
+  padding: 10px;
+  color: #fff;
+  background: #1a1a1a;
+  cursor: pointer;
+}
+.picker-m, .picker-y {
+  flex-grow: 1;
+  padding: 10px;
+  border: 0;
+  color: #fff;
+  background: none;
+}
+.picker-m:focus, .picker-y:focus { outline: none; }
+.picker-m option, .picker-y option { color: #000; }
+
+/* (D) DAYS IN MONTH */
+/* (D1) GRID LAYOUT */
+.picker-d {
+  color: #fff;
+  display: grid;
+  grid-template-columns: repeat(7, minmax(0, 1fr));
+}
+.picker-d div { padding: 5px; }
+
+/* (D2) HEADER - DAY NAMES */
+.picker-d-h { font-weight: 700; }
+
+/* (D3) BLANK DATES */
+.picker-d-b { background: #4e4e4e; }
+
+/* (D4) TODAY */
+.picker-d-td { background: #6e1c18; }
+
+/* (D5) PICKABLE DATES */
+.picker-d-d:hover {
+  cursor: pointer;
+  background: #a33c3c;
+}
+
+/* (D6) UNPICKABLE DATES */
+.picker-d-dd {
+  color: #888;
+  background: #4e4e4e;
+}

+ 83 - 0
web/script/datepicker/dp-light.css

@@ -0,0 +1,83 @@
+/* (A) WRAPPER - POPUP MODE */
+.picker-wrap, .picker-wrap * { box-sizing: border-box; }
+.picker-wrap.popup {
+  /* (A1) FULLSCREEN COVER */
+  position: fixed;
+  top: 0; left: 0; z-index: 99;
+  width: 100vw; height: 100vh;
+
+  /* (A2) BACKGROUND + HIDE BY DEFAULT */
+  background: rgba(0, 0, 0, 0.5);
+  opacity: 0; visibility: hidden;
+  transition: opacity 0.2s;
+
+  /* (A3) CENTER DATE PICKER */
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+/* (A4) SHOW POPUP */
+.picker-wrap.show {
+  opacity: 1;
+  visibility: visible;
+}
+
+/* (B) CONTAINER */
+.picker {
+  max-width: 300px;
+  border: 1px solid #eee;
+  background: #fafafa;
+  padding:1.2em;
+  border-radius: 0.4em;
+}
+
+/* (C) PERIOD SELECTOR */
+.picker-p {
+  width: 100%;
+  display: flex;
+  background: #fff;
+}
+.picker-b, .picker-n {
+  font-weight: 700;
+  padding: 10px;
+  background: #e7e7e7;
+  cursor: pointer;
+}
+.picker-m:focus, .picker-y:focus { outline: none; }
+.picker-m, .picker-y {
+  flex-grow: 1;
+  padding: 10px;
+  border: 0;
+  background: none;
+}
+
+/* (D) DAYS IN MONTH */
+/* (D1) GRID LAYOUT */
+.picker-d {
+  display: grid;
+  grid-template-columns: repeat(7, minmax(0, 1fr));
+}
+.picker-d div { padding: 5px; }
+
+/* (D2) HEADER - DAY NAMES */
+.picker-d-h { font-weight: 700; }
+
+/* (D3) BLANK DATES */
+.picker-d-b { background: #e7e7e7; }
+
+/* (D4) TODAY */
+.picker-d-td { background: #ffe5e5; }
+
+/* (D5) PICKABLE DATES */
+.picker-d-d:hover {
+  cursor: pointer;
+  background: #2d68c4;
+  color: #fff;
+}
+
+/* (D6) UNPICKABLE DATES */
+.picker-d-dd {
+  color: #aaa;
+  background: #ddd;
+}