mirror of
https://github.com/Dvorinka/PPve.git
synced 2026-06-05 04:52:58 +00:00
fes
This commit is contained in:
+28
-9
@@ -96,17 +96,36 @@ async function checkAchievements() {
|
|||||||
try {
|
try {
|
||||||
const response = await fetch('/api/visitor-stats');
|
const response = await fetch('/api/visitor-stats');
|
||||||
const stats = await response.json();
|
const stats = await response.json();
|
||||||
|
const visitorId = getCookie('visitor_id');
|
||||||
// Check for monthly achievements
|
|
||||||
Object.values(ACHIEVEMENTS).forEach(achievement => {
|
|
||||||
if (achievement.period === "monthly" && stats.monthly_visits >= achievement.threshold) {
|
|
||||||
unlockAchievement(achievement);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// First visit achievement
|
// First visit achievement
|
||||||
if (stats.total_visits === 1) {
|
if (!localStorage.getItem('first_visit_' + visitorId)) {
|
||||||
unlockAchievement(ACHIEVEMENTS.first_visit);
|
unlockAchievement('first_visit');
|
||||||
|
localStorage.setItem('first_visit_' + visitorId, 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get visitor's stats
|
||||||
|
const visitor = stats.unique_visitors[visitorId];
|
||||||
|
if (!visitor) return;
|
||||||
|
|
||||||
|
// Monthly achievements
|
||||||
|
const monthlyVisits = stats.monthly_visits;
|
||||||
|
if (monthlyVisits >= ACHIEVEMENTS.frequent_visitor.threshold &&
|
||||||
|
!localStorage.getItem('frequent_visitor_' + visitorId)) {
|
||||||
|
unlockAchievement('frequent_visitor');
|
||||||
|
localStorage.setItem('frequent_visitor_' + visitorId, 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (monthlyVisits >= ACHIEVEMENTS.power_user.threshold &&
|
||||||
|
!localStorage.getItem('power_user_' + visitorId)) {
|
||||||
|
unlockAchievement('power_user');
|
||||||
|
localStorage.setItem('power_user_' + visitorId, 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (monthlyVisits >= ACHIEVEMENTS.super_fan.threshold &&
|
||||||
|
!localStorage.getItem('super_fan_' + visitorId)) {
|
||||||
|
unlockAchievement('super_fan');
|
||||||
|
localStorage.setItem('super_fan_' + visitorId, 'true');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply highest unlocked achievement theme
|
// Apply highest unlocked achievement theme
|
||||||
|
|||||||
+66
-45
@@ -4367,36 +4367,48 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
// Browser stats
|
// Browser stats
|
||||||
const browserStats = document.getElementById('browserStats');
|
const browserStats = document.getElementById('browserStats');
|
||||||
browserStats.innerHTML = Object.entries(stats.browser_stats)
|
if (stats.browser_stats) {
|
||||||
.sort((a, b) => b[1] - a[1])
|
browserStats.innerHTML = Object.entries(stats.browser_stats)
|
||||||
.map(([browser, count]) => `
|
.sort((a, b) => b[1] - a[1])
|
||||||
<div class="flex justify-between items-center">
|
.map(([browser, count]) => `
|
||||||
<span class="text-sm">${browser}</span>
|
<div class="flex justify-between items-center">
|
||||||
<span class="text-sm text-gray-600">${count}</span>
|
<span class="text-sm">${browser}</span>
|
||||||
</div>
|
<span class="text-sm text-gray-600">${count}</span>
|
||||||
`).join('');
|
</div>
|
||||||
|
`).join('');
|
||||||
|
} else {
|
||||||
|
browserStats.innerHTML = '<div class="text-sm text-gray-500">Žádná data</div>';
|
||||||
|
}
|
||||||
|
|
||||||
// OS stats
|
// OS stats
|
||||||
const osStats = document.getElementById('osStats');
|
const osStats = document.getElementById('osStats');
|
||||||
osStats.innerHTML = Object.entries(stats.os_stats)
|
if (stats.os_stats) {
|
||||||
.sort((a, b) => b[1] - a[1])
|
osStats.innerHTML = Object.entries(stats.os_stats)
|
||||||
.map(([os, count]) => `
|
.sort((a, b) => b[1] - a[1])
|
||||||
<div class="flex justify-between items-center">
|
.map(([os, count]) => `
|
||||||
<span class="text-sm">${os}</span>
|
<div class="flex justify-between items-center">
|
||||||
<span class="text-sm text-gray-600">${count}</span>
|
<span class="text-sm">${os}</span>
|
||||||
</div>
|
<span class="text-sm text-gray-600">${count}</span>
|
||||||
`).join('');
|
</div>
|
||||||
|
`).join('');
|
||||||
|
} else {
|
||||||
|
osStats.innerHTML = '<div class="text-sm text-gray-500">Žádná data</div>';
|
||||||
|
}
|
||||||
|
|
||||||
// Active hours
|
// Active hours
|
||||||
const activeHours = document.getElementById('activeHours');
|
const activeHours = document.getElementById('activeHours');
|
||||||
activeHours.innerHTML = stats.most_active_hours
|
if (stats.most_active_hours && stats.most_active_hours.length > 0) {
|
||||||
.sort((a, b) => b.count - a.count)
|
activeHours.innerHTML = stats.most_active_hours
|
||||||
.map(hour => `
|
.sort((a, b) => b.count - a.count)
|
||||||
<div class="flex justify-between items-center">
|
.map(hour => `
|
||||||
<span class="text-sm">${hour.hour}:00</span>
|
<div class="flex justify-between items-center">
|
||||||
<span class="text-sm text-gray-600">${hour.count}</span>
|
<span class="text-sm">${hour.hour}:00</span>
|
||||||
</div>
|
<span class="text-sm text-gray-600">${hour.count}</span>
|
||||||
`).join('');
|
</div>
|
||||||
|
`).join('');
|
||||||
|
} else {
|
||||||
|
activeHours.innerHTML = '<div class="text-sm text-gray-500">Žádná data</div>';
|
||||||
|
}
|
||||||
|
|
||||||
// Active days
|
// Active days
|
||||||
const activeDays = document.getElementById('activeDays');
|
const activeDays = document.getElementById('activeDays');
|
||||||
@@ -4410,31 +4422,40 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
'Sunday': 'Neděle'
|
'Sunday': 'Neděle'
|
||||||
};
|
};
|
||||||
|
|
||||||
activeDays.innerHTML = stats.most_active_days
|
if (stats.most_active_days && stats.most_active_days.length > 0) {
|
||||||
.sort((a, b) => b.count - a.count)
|
activeDays.innerHTML = stats.most_active_days
|
||||||
.map(day => `
|
.sort((a, b) => b.count - a.count)
|
||||||
<div class="flex justify-between items-center">
|
.filter(day => day.count > 0) // Filter out days with 0 visits
|
||||||
<span class="text-sm">${dayMap[day.day] || day.day}</span>
|
.map(day => `
|
||||||
<span class="text-sm text-gray-600">${day.count}</span>
|
<div class="flex justify-between items-center">
|
||||||
</div>
|
<span class="text-sm">${dayMap[day.day] || day.day}</span>
|
||||||
`).join('');
|
<span class="text-sm text-gray-600">${day.count}</span>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
} else {
|
||||||
|
activeDays.innerHTML = '<div class="text-sm text-gray-500">Žádná data</div>';
|
||||||
|
}
|
||||||
|
|
||||||
// Unique visitors
|
// Unique visitors
|
||||||
const uniqueVisitors = document.getElementById('uniqueVisitors');
|
const uniqueVisitors = document.getElementById('uniqueVisitors');
|
||||||
uniqueVisitors.innerHTML = Object.entries(stats.unique_visitors)
|
if (stats.unique_visitors) {
|
||||||
.map(([id, visitor]) => `
|
uniqueVisitors.innerHTML = Object.entries(stats.unique_visitors)
|
||||||
<div class="flex justify-between items-center p-2 hover:bg-gray-50 rounded">
|
.map(([id, visitor]) => `
|
||||||
<div class="flex-1">
|
<div class="flex justify-between items-center p-2 hover:bg-gray-50 rounded">
|
||||||
<div class="text-sm">${visitor.ip}</div>
|
<div class="flex-1">
|
||||||
<div class="text-xs text-gray-500">${visitor.user_agent}</div>
|
<div class="text-sm">${visitor.ip}</div>
|
||||||
|
<div class="text-xs text-gray-500">${visitor.user_agent}</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<span class="text-xs text-gray-600">${visitor.visits} návštěv</span>
|
||||||
|
<span class="text-xs text-gray-400">|</span>
|
||||||
|
<span class="text-xs text-gray-600">${visitor.last_visit.toLocaleString()}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-2">
|
`).join('');
|
||||||
<span class="text-xs text-gray-600">${visitor.visits} návštěv</span>
|
} else {
|
||||||
<span class="text-xs text-gray-400">|</span>
|
uniqueVisitors.innerHTML = '<div class="text-sm text-gray-500">Žádná data</div>';
|
||||||
<span class="text-xs text-gray-600">${visitor.last_visit.toLocaleString()}</span>
|
}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`).join('');
|
|
||||||
|
|
||||||
initializeCharts();
|
initializeCharts();
|
||||||
updateVisitorStats(stats);
|
updateVisitorStats(stats);
|
||||||
|
|||||||
+29
-21
@@ -618,33 +618,41 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<!-- Achievements display -->
|
<!-- Achievements display -->
|
||||||
<div id="achievementsDisplay">
|
<div id="achievementsDisplay" class="hidden">
|
||||||
<div class="achievement-item">
|
<div class="achievement-item bg-white rounded-lg shadow-md p-4 mb-4">
|
||||||
<i class="fas fa-star achievement-icon text-yellow-500"></i>
|
<div class="flex items-center space-x-4">
|
||||||
<div>
|
<i class="fas fa-star achievement-icon text-yellow-500"></i>
|
||||||
<h4 class="font-bold">Nováček</h4>
|
<div>
|
||||||
<p class="text-sm text-gray-600">První návštěva na portálu</p>
|
<h4 class="font-bold">Nováček</h4>
|
||||||
|
<p class="text-sm text-gray-600">První návštěva na portálu</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="achievement-item">
|
<div class="achievement-item bg-white rounded-lg shadow-md p-4 mb-4">
|
||||||
<i class="fas fa-clock-rotate-left achievement-icon text-blue-500"></i>
|
<div class="flex items-center space-x-4">
|
||||||
<div>
|
<i class="fas fa-clock-rotate-left achievement-icon text-blue-500"></i>
|
||||||
<h4 class="font-bold">Pravidelný návštěvník</h4>
|
<div>
|
||||||
<p class="text-sm text-gray-600">10 návštěv za měsíc</p>
|
<h4 class="font-bold">Pravidelný návštěvník</h4>
|
||||||
|
<p class="text-sm text-gray-600">10 návštěv za měsíc</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="achievement-item">
|
<div class="achievement-item bg-white rounded-lg shadow-md p-4 mb-4">
|
||||||
<i class="fas fa-rocket achievement-icon text-purple-500"></i>
|
<div class="flex items-center space-x-4">
|
||||||
<div>
|
<i class="fas fa-rocket achievement-icon text-purple-500"></i>
|
||||||
<h4 class="font-bold">Power User</h4>
|
<div>
|
||||||
<p class="text-sm text-gray-600">50 návštěv za měsíc</p>
|
<h4 class="font-bold">Power User</h4>
|
||||||
|
<p class="text-sm text-gray-600">50 návštěv za měsíc</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="achievement-item">
|
<div class="achievement-item bg-white rounded-lg shadow-md p-4 mb-4">
|
||||||
<i class="fas fa-award achievement-icon text-gold"></i>
|
<div class="flex items-center space-x-4">
|
||||||
<div>
|
<i class="fas fa-award achievement-icon text-gold"></i>
|
||||||
<h4 class="font-bold">Super Fan</h4>
|
<div>
|
||||||
<p class="text-sm text-gray-600">100 návštěv za měsíc</p>
|
<h4 class="font-bold">Super Fan</h4>
|
||||||
|
<p class="text-sm text-gray-600">100 návštěv za měsíc</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -199,6 +199,63 @@ func trackVisit(w http.ResponseWriter, r *http.Request) {
|
|||||||
stats.UniqueVisitors[visitorId] = visitor
|
stats.UniqueVisitors[visitorId] = visitor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update browser stats
|
||||||
|
browser := detectBrowser(userAgent)
|
||||||
|
if stats.BrowserStats == nil {
|
||||||
|
stats.BrowserStats = make(map[string]int)
|
||||||
|
}
|
||||||
|
stats.BrowserStats[browser]++
|
||||||
|
|
||||||
|
// Update OS stats
|
||||||
|
os := detectOS(userAgent)
|
||||||
|
if stats.OSStats == nil {
|
||||||
|
stats.OSStats = make(map[string]int)
|
||||||
|
}
|
||||||
|
stats.OSStats[os]++
|
||||||
|
|
||||||
|
// Update referrer stats
|
||||||
|
referrer := r.Referer()
|
||||||
|
if referrer != "" {
|
||||||
|
if stats.ReferrerStats == nil {
|
||||||
|
stats.ReferrerStats = make(map[string]int)
|
||||||
|
}
|
||||||
|
stats.ReferrerStats[referrer]++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update active hours
|
||||||
|
hour := time.Now().Hour()
|
||||||
|
found := false
|
||||||
|
for i, h := range stats.MostActiveHours {
|
||||||
|
if h.Hour == hour {
|
||||||
|
stats.MostActiveHours[i].Count++
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
stats.MostActiveHours = append(stats.MostActiveHours, struct {
|
||||||
|
Hour int `json:"hour"`
|
||||||
|
Count int `json:"count"`
|
||||||
|
}{Hour: hour, Count: 1})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update active days
|
||||||
|
day := time.Now().Weekday().String()
|
||||||
|
found = false
|
||||||
|
for i, d := range stats.MostActiveDays {
|
||||||
|
if d.Day == day {
|
||||||
|
stats.MostActiveDays[i].Count++
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
stats.MostActiveDays = append(stats.MostActiveDays, struct {
|
||||||
|
Day string `json:"day"`
|
||||||
|
Count int `json:"count"`
|
||||||
|
}{Day: day, Count: 1})
|
||||||
|
}
|
||||||
|
|
||||||
// Save visitor stats
|
// 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)
|
||||||
@@ -218,41 +275,31 @@ func detectBrowser(userAgent string) string {
|
|||||||
return "Internet Explorer"
|
return "Internet Explorer"
|
||||||
} else if strings.Contains(userAgent, "edge") {
|
} else if strings.Contains(userAgent, "edge") {
|
||||||
return "Edge"
|
return "Edge"
|
||||||
|
} else if strings.Contains(userAgent, "opera") {
|
||||||
|
return "Opera"
|
||||||
} 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
|
||||||
func detectOS(userAgent string) string {
|
func detectOS(userAgent string) string {
|
||||||
userAgent = strings.ToLower(userAgent)
|
userAgent = strings.ToLower(userAgent)
|
||||||
if strings.Contains(userAgent, "windows") {
|
if strings.Contains(userAgent, "windows") {
|
||||||
return "Windows"
|
return "Windows"
|
||||||
} else if strings.Contains(userAgent, "mac os") {
|
} else if strings.Contains(userAgent, "mac os") || strings.Contains(userAgent, "macintosh") {
|
||||||
return "MacOS"
|
return "macOS"
|
||||||
} else if strings.Contains(userAgent, "linux") {
|
} else if strings.Contains(userAgent, "iphone") || strings.Contains(userAgent, "ipad") || strings.Contains(userAgent, "ipod") {
|
||||||
return "Linux"
|
return "iOS"
|
||||||
} else if strings.Contains(userAgent, "android") {
|
} else if strings.Contains(userAgent, "android") {
|
||||||
return "Android"
|
return "Android"
|
||||||
} else if strings.Contains(userAgent, "ios") {
|
} else if strings.Contains(userAgent, "linux") {
|
||||||
return "iOS"
|
return "Linux"
|
||||||
} else {
|
} else if strings.Contains(userAgent, "bsd") {
|
||||||
return "Unknown"
|
return "BSD"
|
||||||
}
|
} else {
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to extract OS from User-Agent
|
// Helper function to extract OS from User-Agent
|
||||||
|
|||||||
Reference in New Issue
Block a user