This commit is contained in:
Dvorinka
2025-06-20 10:20:07 +02:00
parent e0ebc204cc
commit da06928087
2 changed files with 146 additions and 188 deletions
+15 -29
View File
@@ -1096,51 +1096,37 @@
<div class="mt-6"> <div class="mt-6">
<h4 class="text-lg font-semibold mb-4">Podrobné statistiky</h4> <h4 class="text-lg font-semibold mb-4">Podrobné statistiky</h4>
<!-- Charts Grid --> <!-- Stats Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<!-- Browser Stats Chart --> <!-- Browser Stats -->
<div class="bg-white p-4 rounded-lg shadow"> <div class="bg-white p-4 rounded-lg shadow">
<h5 class="font-semibold mb-2">Prohlížeče</h5> <h5 class="font-semibold mb-2">Prohlížeče</h5>
<canvas id="browserChart" class="w-full h-64"></canvas> <div id="browserStats" class="space-y-2"></div>
</div> </div>
<!-- OS Stats Chart --> <!-- OS Stats -->
<div class="bg-white p-4 rounded-lg shadow"> <div class="bg-white p-4 rounded-lg shadow">
<h5 class="font-semibold mb-2">Operační systémy</h5> <h5 class="font-semibold mb-2">Operační systémy</h5>
<canvas id="osChart" class="w-full h-64"></canvas> <div id="osStats" class="space-y-2"></div>
</div>
</div> </div>
<!-- Active Hours Chart --> <!-- Active Hours -->
<div class="bg-white p-4 rounded-lg shadow"> <div class="bg-white p-4 rounded-lg shadow mb-4">
<h5 class="font-semibold mb-2">Nejaktivnější hodiny</h5> <h5 class="font-semibold mb-2">Nejaktivnější hodiny</h5>
<canvas id="hoursChart" class="w-full h-64"></canvas> <div id="activeHours" class="space-y-2"></div>
</div> </div>
<!-- Active Days Chart --> <!-- Active Days -->
<div class="bg-white p-4 rounded-lg shadow"> <div class="bg-white p-4 rounded-lg shadow mb-4">
<h5 class="font-semibold mb-2">Nejaktivnější dny</h5> <h5 class="font-semibold mb-2">Nejaktivnější dny</h5>
<canvas id="daysChart" class="w-full h-64"></canvas> <div id="activeDays" class="space-y-2"></div>
</div>
</div> </div>
<!-- Unique Visitors List --> <!-- Unique Visitors List -->
<div class="bg-white p-4 rounded-lg shadow mt-4"> <div class="bg-white p-4 rounded-lg shadow">
<h5 class="font-semibold mb-2">Unikátní návštěvníci</h5> <h5 class="font-semibold mb-2">Unikátní návštěvníci</h5>
<div class="overflow-x-auto"> <div id="uniqueVisitors" class="space-y-2"></div>
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">IP Adresa</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Prohlížeč</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Návštěvy</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Poslední návštěva</th>
</tr>
</thead>
<tbody id="uniqueVisitors" class="bg-white divide-y divide-gray-200">
<!-- Rows will be populated by JavaScript -->
</tbody>
</table>
</div>
</div> </div>
</div> </div>
</div> </div>
+97 -125
View File
@@ -1,11 +1,14 @@
package main package main
import ( import (
"crypto/md5"
"crypto/rand"
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"log" "log"
"math/big"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"net/url" "net/url"
@@ -19,6 +22,14 @@ import (
"gopkg.in/gomail.v2" "gopkg.in/gomail.v2"
) )
type Visitor struct {
FirstVisit time.Time `json:"first_visit"`
LastVisit time.Time `json:"last_visit"`
Visits int `json:"visits"`
IP string `json:"ip"`
UserAgent string `json:"user_agent"`
}
type VisitorStats struct { type VisitorStats struct {
TotalVisits int `json:"total_visits"` TotalVisits int `json:"total_visits"`
TodayVisits int `json:"today_visits"` TodayVisits int `json:"today_visits"`
@@ -26,13 +37,7 @@ type VisitorStats struct {
MonthlyVisits int `json:"monthly_visits"` MonthlyVisits int `json:"monthly_visits"`
WeeklyVisits int `json:"weekly_visits"` WeeklyVisits int `json:"weekly_visits"`
LastUpdated time.Time `json:"last_updated"` LastUpdated time.Time `json:"last_updated"`
UniqueVisitors map[string]struct { UniqueVisitors map[string]*Visitor `json:"unique_visitors"`
FirstVisit time.Time `json:"first_visit"`
LastVisit time.Time `json:"last_visit"`
Visits int `json:"visits"`
IP string `json:"ip"`
UserAgent string `json:"user_agent"`
} `json:"unique_visitors"`
MostActiveHours []struct { MostActiveHours []struct {
Hour int `json:"hour"` Hour int `json:"hour"`
Count int `json:"count"` Count int `json:"count"`
@@ -48,33 +53,18 @@ type VisitorStats struct {
// Initialize VisitorStats with proper struct types // Initialize VisitorStats with proper struct types
func (v *VisitorStats) init() { func (v *VisitorStats) init() {
if v.UniqueVisitors == nil { if v.TotalVisits == 0 {
v.UniqueVisitors = make(map[string]struct { v.TotalVisits = 0
FirstVisit time.Time `json:"first_visit"` v.TodayVisits = 0
LastVisit time.Time `json:"last_visit"` v.MonthlyVisits = 0
Visits int `json:"visits"` v.WeeklyVisits = 0
IP string `json:"ip"` v.LastVisit = time.Now()
UserAgent string `json:"user_agent"` v.LastUpdated = time.Now()
}) v.UniqueVisitors = make(map[string]*Visitor)
}
if v.BrowserStats == nil {
v.BrowserStats = make(map[string]int)
}
if v.OSStats == nil {
v.OSStats = make(map[string]int)
}
if v.ReferrerStats == nil {
v.ReferrerStats = make(map[string]int)
}
if len(v.MostActiveHours) == 0 {
v.MostActiveHours = make([]struct { v.MostActiveHours = make([]struct {
Hour int `json:"hour"` Hour int `json:"hour"`
Count int `json:"count"` Count int `json:"count"`
}, 24) }, 0)
} }
if len(v.MostActiveDays) == 0 { if len(v.MostActiveDays) == 0 {
@@ -121,6 +111,42 @@ func saveVisitorStats(stats *VisitorStats) error {
return os.WriteFile(visitorStatsFile, data, 0644) return os.WriteFile(visitorStatsFile, data, 0644)
} }
func randomString(n int) string {
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
bytes := make([]byte, n)
for i := range bytes {
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
if err != nil {
log.Printf("Error generating random number: %v", err)
return ""
}
bytes[i] = letters[num.Int64()]
}
return string(bytes)
}
func getVisitorId(w http.ResponseWriter, r *http.Request) string {
// Check if visitor ID is in cookie
cookie, err := r.Cookie("visitor_id")
if err == nil && cookie.Value != "" {
return cookie.Value
}
// Generate a new unique ID
visitorId := fmt.Sprintf("%x", md5.Sum([]byte(time.Now().String() + randomString(16))))
// Set the cookie to expire in 1 year
http.SetCookie(w, &http.Cookie{
Name: "visitor_id",
Value: visitorId,
Path: "/",
MaxAge: 365 * 24 * 60 * 60, // 1 year
HttpOnly: true,
})
return visitorId
}
func trackVisit(w http.ResponseWriter, r *http.Request) { func trackVisit(w http.ResponseWriter, r *http.Request) {
stats, err := loadVisitorStats() stats, err := loadVisitorStats()
if err != nil { if err != nil {
@@ -132,119 +158,51 @@ func trackVisit(w http.ResponseWriter, r *http.Request) {
// Initialize stats if needed // Initialize stats if needed
stats.init() stats.init()
// Get visitor info
visitorId := getVisitorId(w, r)
ip := r.RemoteAddr
userAgent := r.UserAgent()
// Update visit counts // Update visit counts
stats.TotalVisits++ stats.TotalVisits++
stats.TodayVisits++
stats.LastVisit = time.Now() stats.LastVisit = time.Now()
stats.LastUpdated = time.Now() stats.LastUpdated = time.Now()
if stats.LastUpdated.Day() != time.Now().Day() {
stats.TodayVisits = 1
stats.WeeklyVisits++
stats.MonthlyVisits++
} else {
stats.TodayVisits++
}
// Get visitor ID (using IP and User-Agent)
visitorID := fmt.Sprintf("%s-%s", r.RemoteAddr, r.UserAgent())
// Update or create visitor stats
if visitor, exists := stats.UniqueVisitors[visitorID]; exists {
visitor.LastVisit = time.Now()
visitor.Visits++
stats.UniqueVisitors[visitorID] = visitor
} else {
stats.UniqueVisitors[visitorID] = struct {
FirstVisit time.Time `json:"first_visit"`
LastVisit time.Time `json:"last_visit"`
Visits int `json:"visits"`
IP string `json:"ip"`
UserAgent string `json:"user_agent"`
}{
FirstVisit: time.Now(),
LastVisit: time.Now(),
Visits: 1,
IP: r.RemoteAddr,
UserAgent: r.UserAgent(),
}
}
// Track browser
browser := detectBrowser(r.UserAgent())
stats.BrowserStats[browser]++
// Track OS
os := detectOS(r.UserAgent())
stats.OSStats[os]++
// Track referrer
referrer := r.Header.Get("Referer")
if referrer != "" {
stats.ReferrerStats[referrer]++
}
// Track active hours and days
now := time.Now()
hour := now.Hour()
day := now.Weekday().String()
// Update active hours
foundHour := false
for i := range stats.MostActiveHours {
if stats.MostActiveHours[i].Hour == hour {
stats.MostActiveHours[i].Count++
foundHour = true
break
}
}
if !foundHour {
stats.MostActiveHours = append(stats.MostActiveHours, struct {
Hour int `json:"hour"`
Count int `json:"count"`
}{
Hour: hour,
Count: 1,
})
}
// Update active days
foundDay := false
for i := range stats.MostActiveDays {
if stats.MostActiveDays[i].Day == day {
stats.MostActiveDays[i].Count++
foundDay = true
break
}
}
if !foundDay {
stats.MostActiveDays = append(stats.MostActiveDays, struct {
Day string `json:"day"`
Count int `json:"count"`
}{
Day: day,
Count: 1,
})
}
// Reset weekly visits on Monday // Reset weekly visits on Monday
if time.Now().Weekday() == time.Monday && stats.LastUpdated.Weekday() != time.Monday { if time.Now().Weekday() == time.Monday && stats.LastUpdated.Weekday() != time.Monday {
stats.WeeklyVisits = 1 stats.WeeklyVisits = 1
} else {
stats.WeeklyVisits++
} }
// Reset monthly visits at the start of the month // Reset monthly visits at the start of the month
if stats.LastUpdated.Month() != time.Now().Month() { if stats.LastUpdated.Month() != time.Now().Month() {
stats.MonthlyVisits = 1 stats.MonthlyVisits = 1
} else {
stats.MonthlyVisits++
} }
stats.LastUpdated = time.Now() // Update unique visitors
if _, ok := stats.UniqueVisitors[visitorId]; !ok {
stats.UniqueVisitors[visitorId] = &Visitor{
FirstVisit: time.Now(),
LastVisit: time.Now(),
Visits: 1,
IP: ip,
UserAgent: userAgent,
}
} else {
visitor := stats.UniqueVisitors[visitorId]
visitor.LastVisit = time.Now()
visitor.Visits++
stats.UniqueVisitors[visitorId] = visitor
}
// Save visitor stats
if err := saveVisitorStats(stats); err != nil { if err := saveVisitorStats(stats); err != nil {
log.Printf("Error saving visitor stats: %v", err) log.Printf("Error saving visitor stats: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
} }
w.WriteHeader(http.StatusOK)
} }
// Helper function to extract browser from User-Agent // Helper function to extract browser from User-Agent
@@ -263,6 +221,20 @@ func detectBrowser(userAgent string) string {
} else { } else {
return "Unknown" return "Unknown"
} }
userAgent = strings.ToLower(userAgent)
if strings.Contains(userAgent, "chrome") {
return "Chrome"
} else if strings.Contains(userAgent, "safari") && !strings.Contains(userAgent, "chrome") {
return "Safari"
} else if strings.Contains(userAgent, "firefox") {
return "Firefox"
} else if strings.Contains(userAgent, "msie") || strings.Contains(userAgent, "trident") {
return "Internet Explorer"
} else if strings.Contains(userAgent, "edge") {
return "Edge"
} else {
return "Unknown"
}
} }
// Helper function to extract OS from User-Agent // Helper function to extract OS from User-Agent