mirror of
https://github.com/Dvorinka/PPve.git
synced 2026-06-04 20:42:59 +00:00
rwr
This commit is contained in:
+20
-34
@@ -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>
|
|
||||||
|
|
||||||
<!-- Active Hours Chart -->
|
|
||||||
<div class="bg-white p-4 rounded-lg shadow">
|
|
||||||
<h5 class="font-semibold mb-2">Nejaktivnější hodiny</h5>
|
|
||||||
<canvas id="hoursChart" class="w-full h-64"></canvas>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Active Days Chart -->
|
|
||||||
<div class="bg-white p-4 rounded-lg shadow">
|
|
||||||
<h5 class="font-semibold mb-2">Nejaktivnější dny</h5>
|
|
||||||
<canvas id="daysChart" class="w-full h-64"></canvas>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Active Hours -->
|
||||||
|
<div class="bg-white p-4 rounded-lg shadow mb-4">
|
||||||
|
<h5 class="font-semibold mb-2">Nejaktivnější hodiny</h5>
|
||||||
|
<div id="activeHours" class="space-y-2"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Active Days -->
|
||||||
|
<div class="bg-white p-4 rounded-lg shadow mb-4">
|
||||||
|
<h5 class="font-semibold mb-2">Nejaktivnější dny</h5>
|
||||||
|
<div id="activeDays" class="space-y-2"></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>
|
||||||
|
|||||||
@@ -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,134 +111,116 @@ 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 {
|
||||||
log.Printf("Error loading visitor stats: %v", err)
|
log.Printf("Error loading visitor stats: %v", err)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize stats if needed
|
// Initialize stats if needed
|
||||||
stats.init()
|
stats.init()
|
||||||
|
|
||||||
// Update visit counts
|
// Get visitor info
|
||||||
stats.TotalVisits++
|
visitorId := getVisitorId(w, r)
|
||||||
stats.LastVisit = time.Now()
|
ip := r.RemoteAddr
|
||||||
stats.LastUpdated = time.Now()
|
userAgent := r.UserAgent()
|
||||||
|
|
||||||
if stats.LastUpdated.Day() != time.Now().Day() {
|
// Update visit counts
|
||||||
stats.TodayVisits = 1
|
stats.TotalVisits++
|
||||||
stats.WeeklyVisits++
|
stats.TodayVisits++
|
||||||
stats.MonthlyVisits++
|
stats.LastVisit = time.Now()
|
||||||
} else {
|
stats.LastUpdated = time.Now()
|
||||||
stats.TodayVisits++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get visitor ID (using IP and User-Agent)
|
// Reset weekly visits on Monday
|
||||||
visitorID := fmt.Sprintf("%s-%s", r.RemoteAddr, r.UserAgent())
|
if time.Now().Weekday() == time.Monday && stats.LastUpdated.Weekday() != time.Monday {
|
||||||
|
stats.WeeklyVisits = 1
|
||||||
|
} else {
|
||||||
|
stats.WeeklyVisits++
|
||||||
|
}
|
||||||
|
|
||||||
// Update or create visitor stats
|
// Reset monthly visits at the start of the month
|
||||||
if visitor, exists := stats.UniqueVisitors[visitorID]; exists {
|
if stats.LastUpdated.Month() != time.Now().Month() {
|
||||||
visitor.LastVisit = time.Now()
|
stats.MonthlyVisits = 1
|
||||||
visitor.Visits++
|
} else {
|
||||||
stats.UniqueVisitors[visitorID] = visitor
|
stats.MonthlyVisits++
|
||||||
} 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
|
// Update unique visitors
|
||||||
browser := detectBrowser(r.UserAgent())
|
if _, ok := stats.UniqueVisitors[visitorId]; !ok {
|
||||||
stats.BrowserStats[browser]++
|
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
|
||||||
|
}
|
||||||
|
|
||||||
// Track OS
|
// Save visitor stats
|
||||||
os := detectOS(r.UserAgent())
|
if err := saveVisitorStats(stats); err != nil {
|
||||||
stats.OSStats[os]++
|
log.Printf("Error saving visitor stats: %v", err)
|
||||||
|
}
|
||||||
// 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
|
|
||||||
if time.Now().Weekday() == time.Monday && stats.LastUpdated.Weekday() != time.Monday {
|
|
||||||
stats.WeeklyVisits = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset monthly visits at the start of the month
|
|
||||||
if stats.LastUpdated.Month() != time.Now().Month() {
|
|
||||||
stats.MonthlyVisits = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
stats.LastUpdated = time.Now()
|
|
||||||
|
|
||||||
if err := saveVisitorStats(stats); err != nil {
|
|
||||||
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
|
||||||
func detectBrowser(userAgent string) string {
|
func detectBrowser(userAgent string) string {
|
||||||
|
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"
|
||||||
|
}
|
||||||
userAgent = strings.ToLower(userAgent)
|
userAgent = strings.ToLower(userAgent)
|
||||||
if strings.Contains(userAgent, "chrome") {
|
if strings.Contains(userAgent, "chrome") {
|
||||||
return "Chrome"
|
return "Chrome"
|
||||||
|
|||||||
Reference in New Issue
Block a user