Added referrer analytics. You can now see what site a visitor has come from.

Added the viewchunks_referrers table.
This commit is contained in:
Azareal 2018-02-05 10:29:13 +00:00
parent e56e7cd467
commit 1ac47bfdfd
15 changed files with 554 additions and 216 deletions

View File

@ -92,29 +92,33 @@ var PreRenderHooks = map[string][]func(http.ResponseWriter, *http.Request, *User
"pre_render_ban": nil, "pre_render_ban": nil,
"pre_render_ip_search": nil, "pre_render_ip_search": nil,
"pre_render_panel_dashboard": nil, "pre_render_panel_dashboard": nil,
"pre_render_panel_forums": nil, "pre_render_panel_forums": nil,
"pre_render_panel_delete_forum": nil, "pre_render_panel_delete_forum": nil,
"pre_render_panel_edit_forum": nil, "pre_render_panel_edit_forum": nil,
"pre_render_panel_analytics_views": nil,
"pre_render_panel_analytics_routes": nil, "pre_render_panel_analytics_views": nil,
"pre_render_panel_analytics_agents": nil, "pre_render_panel_analytics_routes": nil,
"pre_render_panel_analytics_systems": nil, "pre_render_panel_analytics_agents": nil,
"pre_render_panel_analytics_route_views": nil, "pre_render_panel_analytics_systems": nil,
"pre_render_panel_analytics_agent_views": nil, "pre_render_panel_analytics_referrers": nil,
"pre_render_panel_analytics_system_views": nil, "pre_render_panel_analytics_route_views": nil,
"pre_render_panel_settings": nil, "pre_render_panel_analytics_agent_views": nil,
"pre_render_panel_setting": nil, "pre_render_panel_analytics_system_views": nil,
"pre_render_panel_word_filters": nil, "pre_render_panel_analytics_referrer_views": nil,
"pre_render_panel_word_filters_edit": nil,
"pre_render_panel_plugins": nil, "pre_render_panel_settings": nil,
"pre_render_panel_users": nil, "pre_render_panel_setting": nil,
"pre_render_panel_edit_user": nil, "pre_render_panel_word_filters": nil,
"pre_render_panel_groups": nil, "pre_render_panel_word_filters_edit": nil,
"pre_render_panel_edit_group": nil, "pre_render_panel_plugins": nil,
"pre_render_panel_edit_group_perms": nil, "pre_render_panel_users": nil,
"pre_render_panel_themes": nil, "pre_render_panel_edit_user": nil,
"pre_render_panel_modlogs": nil, "pre_render_panel_groups": nil,
"pre_render_panel_edit_group": nil,
"pre_render_panel_edit_group_perms": nil,
"pre_render_panel_themes": nil,
"pre_render_panel_modlogs": nil,
"pre_render_error": nil, // Note: This hook isn't run for a few errors whose templates are computed at startup and reused, such as InternalError. This hook is also not available in JS mode. "pre_render_error": nil, // Note: This hook isn't run for a few errors whose templates are computed at startup and reused, such as InternalError. This hook is also not available in JS mode.
"pre_render_security_error": nil, "pre_render_security_error": nil,

View File

@ -1,20 +1,20 @@
package common package common
import ( import (
"database/sql"
"sync" "sync"
"sync/atomic" "sync/atomic"
"../query_gen/lib"
) )
// Add ReferrerItems here after they've had zero views for a while var ReferrerTracker *DefaultReferrerTracker
var referrersToDelete = make(map[string]ReferrerDeletable)
type ReferrerDeletable struct { // Add ReferrerItems here after they've had zero views for a while
item *ReferrerItem var referrersToDelete = make(map[string]*ReferrerItem)
scheduledAt int64 //unixtime
}
type ReferrerItem struct { type ReferrerItem struct {
Counter int64 Count int64
} }
// ? We'll track referrer domains here rather than the exact URL they arrived from for now, we'll think about expanding later // ? We'll track referrer domains here rather than the exact URL they arrived from for now, we'll think about expanding later
@ -24,24 +24,79 @@ type DefaultReferrerTracker struct {
even map[string]*ReferrerItem even map[string]*ReferrerItem
oddLock sync.RWMutex oddLock sync.RWMutex
evenLock sync.RWMutex evenLock sync.RWMutex
insert *sql.Stmt
} }
func NewDefaultReferrerTracker() *DefaultReferrerTracker { func NewDefaultReferrerTracker() (*DefaultReferrerTracker, error) {
return &DefaultReferrerTracker{ acc := qgen.Builder.Accumulator()
odd: make(map[string]*ReferrerItem), refTracker := &DefaultReferrerTracker{
even: make(map[string]*ReferrerItem), odd: make(map[string]*ReferrerItem),
even: make(map[string]*ReferrerItem),
insert: acc.Insert("viewchunks_referrers").Columns("count, createdAt, domain").Fields("?,UTC_TIMESTAMP(),?").Prepare(), // TODO: Do something more efficient than doing a query for each referrer
} }
//AddScheduledFifteenMinuteTask(refTracker.Tick)
AddScheduledSecondTask(refTracker.Tick)
AddShutdownTask(refTracker.Tick)
return refTracker, acc.FirstError()
} }
// TODO: Move this and the other view tickers out of the main task loop to avoid blocking other tasks?
func (ref *DefaultReferrerTracker) Tick() (err error) { func (ref *DefaultReferrerTracker) Tick() (err error) {
for _, del := range referrersToDelete { for referrer, counter := range referrersToDelete {
_ = del // Handle views which squeezed through the gaps at the last moment
// TODO: Calculate the gap between now and the times they were scheduled count := counter.Count
if count != 0 {
err := ref.insertChunk(referrer, count) // TODO: Bulk insert for speed?
if err != nil {
return err
}
}
delete(referrersToDelete, referrer)
} }
// TODO: Run the queries and schedule zero view refs for deletion from memory
// Run the queries and schedule zero view refs for deletion from memory
ref.oddLock.Lock()
for referrer, counter := range ref.odd {
if counter.Count == 0 {
referrersToDelete[referrer] = counter
delete(ref.odd, referrer)
}
count := atomic.SwapInt64(&counter.Count, 0)
err := ref.insertChunk(referrer, count) // TODO: Bulk insert for speed?
if err != nil {
return err
}
}
ref.oddLock.Unlock()
ref.evenLock.Lock()
for referrer, counter := range ref.even {
if counter.Count == 0 {
referrersToDelete[referrer] = counter
delete(ref.even, referrer)
}
count := atomic.SwapInt64(&counter.Count, 0)
err := ref.insertChunk(referrer, count) // TODO: Bulk insert for speed?
if err != nil {
return err
}
}
ref.evenLock.Unlock()
return nil return nil
} }
func (ref *DefaultReferrerTracker) insertChunk(referrer string, count int64) error {
if count == 0 {
return nil
}
debugDetailf("Inserting a viewchunk with a count of %d for referrer %s", count, referrer)
_, err := ref.insert.Exec(count, referrer)
return err
}
func (ref *DefaultReferrerTracker) Bump(referrer string) { func (ref *DefaultReferrerTracker) Bump(referrer string) {
if referrer == "" { if referrer == "" {
return return
@ -53,22 +108,22 @@ func (ref *DefaultReferrerTracker) Bump(referrer string) {
ref.evenLock.RLock() ref.evenLock.RLock()
refItem = ref.even[referrer] refItem = ref.even[referrer]
ref.evenLock.RUnlock() ref.evenLock.RUnlock()
if ref != nil { if refItem != nil {
atomic.AddInt64(&refItem.Counter, 1) atomic.AddInt64(&refItem.Count, 1)
} else { } else {
ref.evenLock.Lock() ref.evenLock.Lock()
ref.even[referrer] = &ReferrerItem{Counter: 1} ref.even[referrer] = &ReferrerItem{Count: 1}
ref.evenLock.Unlock() ref.evenLock.Unlock()
} }
} else { } else {
ref.oddLock.RLock() ref.oddLock.RLock()
refItem = ref.odd[referrer] refItem = ref.odd[referrer]
ref.oddLock.RUnlock() ref.oddLock.RUnlock()
if ref != nil { if refItem != nil {
atomic.AddInt64(&refItem.Counter, 1) atomic.AddInt64(&refItem.Count, 1)
} else { } else {
ref.oddLock.Lock() ref.oddLock.Lock()
ref.odd[referrer] = &ReferrerItem{Counter: 1} ref.odd[referrer] = &ReferrerItem{Count: 1}
ref.oddLock.Unlock() ref.oddLock.Unlock()
} }
} }

View File

@ -57,9 +57,11 @@ var RouteMap = map[string]interface{}{
"routePanelAnalyticsRoutes": routePanelAnalyticsRoutes, "routePanelAnalyticsRoutes": routePanelAnalyticsRoutes,
"routePanelAnalyticsAgents": routePanelAnalyticsAgents, "routePanelAnalyticsAgents": routePanelAnalyticsAgents,
"routePanelAnalyticsSystems": routePanelAnalyticsSystems, "routePanelAnalyticsSystems": routePanelAnalyticsSystems,
"routePanelAnalyticsReferrers": routePanelAnalyticsReferrers,
"routePanelAnalyticsRouteViews": routePanelAnalyticsRouteViews, "routePanelAnalyticsRouteViews": routePanelAnalyticsRouteViews,
"routePanelAnalyticsAgentViews": routePanelAnalyticsAgentViews, "routePanelAnalyticsAgentViews": routePanelAnalyticsAgentViews,
"routePanelAnalyticsSystemViews": routePanelAnalyticsSystemViews, "routePanelAnalyticsSystemViews": routePanelAnalyticsSystemViews,
"routePanelAnalyticsReferrerViews": routePanelAnalyticsReferrerViews,
"routePanelAnalyticsPosts": routePanelAnalyticsPosts, "routePanelAnalyticsPosts": routePanelAnalyticsPosts,
"routePanelAnalyticsTopics": routePanelAnalyticsTopics, "routePanelAnalyticsTopics": routePanelAnalyticsTopics,
"routePanelGroups": routePanelGroups, "routePanelGroups": routePanelGroups,
@ -157,61 +159,63 @@ var routeMapEnum = map[string]int{
"routePanelAnalyticsRoutes": 38, "routePanelAnalyticsRoutes": 38,
"routePanelAnalyticsAgents": 39, "routePanelAnalyticsAgents": 39,
"routePanelAnalyticsSystems": 40, "routePanelAnalyticsSystems": 40,
"routePanelAnalyticsRouteViews": 41, "routePanelAnalyticsReferrers": 41,
"routePanelAnalyticsAgentViews": 42, "routePanelAnalyticsRouteViews": 42,
"routePanelAnalyticsSystemViews": 43, "routePanelAnalyticsAgentViews": 43,
"routePanelAnalyticsPosts": 44, "routePanelAnalyticsSystemViews": 44,
"routePanelAnalyticsTopics": 45, "routePanelAnalyticsReferrerViews": 45,
"routePanelGroups": 46, "routePanelAnalyticsPosts": 46,
"routePanelGroupsEdit": 47, "routePanelAnalyticsTopics": 47,
"routePanelGroupsEditPerms": 48, "routePanelGroups": 48,
"routePanelGroupsEditSubmit": 49, "routePanelGroupsEdit": 49,
"routePanelGroupsEditPermsSubmit": 50, "routePanelGroupsEditPerms": 50,
"routePanelGroupsCreateSubmit": 51, "routePanelGroupsEditSubmit": 51,
"routePanelBackups": 52, "routePanelGroupsEditPermsSubmit": 52,
"routePanelLogsMod": 53, "routePanelGroupsCreateSubmit": 53,
"routePanelDebug": 54, "routePanelBackups": 54,
"routePanelDashboard": 55, "routePanelLogsMod": 55,
"routes.AccountEditCritical": 56, "routePanelDebug": 56,
"routeAccountEditCriticalSubmit": 57, "routePanelDashboard": 57,
"routeAccountEditAvatar": 58, "routes.AccountEditCritical": 58,
"routeAccountEditAvatarSubmit": 59, "routeAccountEditCriticalSubmit": 59,
"routeAccountEditUsername": 60, "routeAccountEditAvatar": 60,
"routeAccountEditUsernameSubmit": 61, "routeAccountEditAvatarSubmit": 61,
"routeAccountEditEmail": 62, "routeAccountEditUsername": 62,
"routeAccountEditEmailTokenSubmit": 63, "routeAccountEditUsernameSubmit": 63,
"routeProfile": 64, "routeAccountEditEmail": 64,
"routes.BanUserSubmit": 65, "routeAccountEditEmailTokenSubmit": 65,
"routes.UnbanUser": 66, "routeProfile": 66,
"routes.ActivateUser": 67, "routes.BanUserSubmit": 67,
"routes.IPSearch": 68, "routes.UnbanUser": 68,
"routes.CreateTopicSubmit": 69, "routes.ActivateUser": 69,
"routes.EditTopicSubmit": 70, "routes.IPSearch": 70,
"routes.DeleteTopicSubmit": 71, "routes.CreateTopicSubmit": 71,
"routes.StickTopicSubmit": 72, "routes.EditTopicSubmit": 72,
"routes.UnstickTopicSubmit": 73, "routes.DeleteTopicSubmit": 73,
"routes.LockTopicSubmit": 74, "routes.StickTopicSubmit": 74,
"routes.UnlockTopicSubmit": 75, "routes.UnstickTopicSubmit": 75,
"routes.MoveTopicSubmit": 76, "routes.LockTopicSubmit": 76,
"routeLikeTopicSubmit": 77, "routes.UnlockTopicSubmit": 77,
"routes.ViewTopic": 78, "routes.MoveTopicSubmit": 78,
"routeCreateReplySubmit": 79, "routeLikeTopicSubmit": 79,
"routes.ReplyEditSubmit": 80, "routes.ViewTopic": 80,
"routes.ReplyDeleteSubmit": 81, "routeCreateReplySubmit": 81,
"routeReplyLikeSubmit": 82, "routes.ReplyEditSubmit": 82,
"routeProfileReplyCreateSubmit": 83, "routes.ReplyDeleteSubmit": 83,
"routes.ProfileReplyEditSubmit": 84, "routeReplyLikeSubmit": 84,
"routes.ProfileReplyDeleteSubmit": 85, "routeProfileReplyCreateSubmit": 85,
"routes.PollVote": 86, "routes.ProfileReplyEditSubmit": 86,
"routes.PollResults": 87, "routes.ProfileReplyDeleteSubmit": 87,
"routes.AccountLogin": 88, "routes.PollVote": 88,
"routes.AccountRegister": 89, "routes.PollResults": 89,
"routeLogout": 90, "routes.AccountLogin": 90,
"routes.AccountLoginSubmit": 91, "routes.AccountRegister": 91,
"routes.AccountRegisterSubmit": 92, "routeLogout": 92,
"routeDynamic": 93, "routes.AccountLoginSubmit": 93,
"routeUploads": 94, "routes.AccountRegisterSubmit": 94,
"BadRoute": 95, "routeDynamic": 95,
"routeUploads": 96,
"BadRoute": 97,
} }
var reverseRouteMapEnum = map[int]string{ var reverseRouteMapEnum = map[int]string{
0: "routeAPI", 0: "routeAPI",
@ -255,61 +259,63 @@ var reverseRouteMapEnum = map[int]string{
38: "routePanelAnalyticsRoutes", 38: "routePanelAnalyticsRoutes",
39: "routePanelAnalyticsAgents", 39: "routePanelAnalyticsAgents",
40: "routePanelAnalyticsSystems", 40: "routePanelAnalyticsSystems",
41: "routePanelAnalyticsRouteViews", 41: "routePanelAnalyticsReferrers",
42: "routePanelAnalyticsAgentViews", 42: "routePanelAnalyticsRouteViews",
43: "routePanelAnalyticsSystemViews", 43: "routePanelAnalyticsAgentViews",
44: "routePanelAnalyticsPosts", 44: "routePanelAnalyticsSystemViews",
45: "routePanelAnalyticsTopics", 45: "routePanelAnalyticsReferrerViews",
46: "routePanelGroups", 46: "routePanelAnalyticsPosts",
47: "routePanelGroupsEdit", 47: "routePanelAnalyticsTopics",
48: "routePanelGroupsEditPerms", 48: "routePanelGroups",
49: "routePanelGroupsEditSubmit", 49: "routePanelGroupsEdit",
50: "routePanelGroupsEditPermsSubmit", 50: "routePanelGroupsEditPerms",
51: "routePanelGroupsCreateSubmit", 51: "routePanelGroupsEditSubmit",
52: "routePanelBackups", 52: "routePanelGroupsEditPermsSubmit",
53: "routePanelLogsMod", 53: "routePanelGroupsCreateSubmit",
54: "routePanelDebug", 54: "routePanelBackups",
55: "routePanelDashboard", 55: "routePanelLogsMod",
56: "routes.AccountEditCritical", 56: "routePanelDebug",
57: "routeAccountEditCriticalSubmit", 57: "routePanelDashboard",
58: "routeAccountEditAvatar", 58: "routes.AccountEditCritical",
59: "routeAccountEditAvatarSubmit", 59: "routeAccountEditCriticalSubmit",
60: "routeAccountEditUsername", 60: "routeAccountEditAvatar",
61: "routeAccountEditUsernameSubmit", 61: "routeAccountEditAvatarSubmit",
62: "routeAccountEditEmail", 62: "routeAccountEditUsername",
63: "routeAccountEditEmailTokenSubmit", 63: "routeAccountEditUsernameSubmit",
64: "routeProfile", 64: "routeAccountEditEmail",
65: "routes.BanUserSubmit", 65: "routeAccountEditEmailTokenSubmit",
66: "routes.UnbanUser", 66: "routeProfile",
67: "routes.ActivateUser", 67: "routes.BanUserSubmit",
68: "routes.IPSearch", 68: "routes.UnbanUser",
69: "routes.CreateTopicSubmit", 69: "routes.ActivateUser",
70: "routes.EditTopicSubmit", 70: "routes.IPSearch",
71: "routes.DeleteTopicSubmit", 71: "routes.CreateTopicSubmit",
72: "routes.StickTopicSubmit", 72: "routes.EditTopicSubmit",
73: "routes.UnstickTopicSubmit", 73: "routes.DeleteTopicSubmit",
74: "routes.LockTopicSubmit", 74: "routes.StickTopicSubmit",
75: "routes.UnlockTopicSubmit", 75: "routes.UnstickTopicSubmit",
76: "routes.MoveTopicSubmit", 76: "routes.LockTopicSubmit",
77: "routeLikeTopicSubmit", 77: "routes.UnlockTopicSubmit",
78: "routes.ViewTopic", 78: "routes.MoveTopicSubmit",
79: "routeCreateReplySubmit", 79: "routeLikeTopicSubmit",
80: "routes.ReplyEditSubmit", 80: "routes.ViewTopic",
81: "routes.ReplyDeleteSubmit", 81: "routeCreateReplySubmit",
82: "routeReplyLikeSubmit", 82: "routes.ReplyEditSubmit",
83: "routeProfileReplyCreateSubmit", 83: "routes.ReplyDeleteSubmit",
84: "routes.ProfileReplyEditSubmit", 84: "routeReplyLikeSubmit",
85: "routes.ProfileReplyDeleteSubmit", 85: "routeProfileReplyCreateSubmit",
86: "routes.PollVote", 86: "routes.ProfileReplyEditSubmit",
87: "routes.PollResults", 87: "routes.ProfileReplyDeleteSubmit",
88: "routes.AccountLogin", 88: "routes.PollVote",
89: "routes.AccountRegister", 89: "routes.PollResults",
90: "routeLogout", 90: "routes.AccountLogin",
91: "routes.AccountLoginSubmit", 91: "routes.AccountRegister",
92: "routes.AccountRegisterSubmit", 92: "routeLogout",
93: "routeDynamic", 93: "routes.AccountLoginSubmit",
94: "routeUploads", 94: "routes.AccountRegisterSubmit",
95: "BadRoute", 95: "routeDynamic",
96: "routeUploads",
97: "BadRoute",
} }
var osMapEnum = map[string]int{ var osMapEnum = map[string]int{
"unknown": 0, "unknown": 0,
@ -681,6 +687,17 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
common.OSViewCounter.Bump(osMapEnum[os]) common.OSViewCounter.Bump(osMapEnum[os])
} }
referrer := req.Header.Get("Referer") // Check the referrer header too? :P
if referrer != "" {
// ? Optimise this a little?
referrer = strings.TrimPrefix(strings.TrimPrefix(referrer,"http://"),"https://")
referrer = strings.Split(referrer,"/")[0]
portless := strings.Split(referrer,":")[0]
if portless != "localhost" && portless != "127.0.0.1" && portless == common.Site.Host {
common.ReferrerTracker.Bump(referrer)
}
}
// Deal with the session stuff, etc. // Deal with the session stuff, etc.
user, ok := common.PreRoute(w, req) user, ok := common.PreRoute(w, req)
if !ok { if !ok {
@ -1011,15 +1028,27 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
common.RouteViewCounter.Bump(40) common.RouteViewCounter.Bump(40)
err = routePanelAnalyticsSystems(w,req,user) err = routePanelAnalyticsSystems(w,req,user)
case "/panel/analytics/route/": case "/panel/analytics/referrers/":
err = common.ParseForm(w,req,user)
if err != nil {
router.handleError(err,w,req,user)
return
}
common.RouteViewCounter.Bump(41) common.RouteViewCounter.Bump(41)
err = routePanelAnalyticsReferrers(w,req,user)
case "/panel/analytics/route/":
common.RouteViewCounter.Bump(42)
err = routePanelAnalyticsRouteViews(w,req,user,extraData) err = routePanelAnalyticsRouteViews(w,req,user,extraData)
case "/panel/analytics/agent/": case "/panel/analytics/agent/":
common.RouteViewCounter.Bump(42) common.RouteViewCounter.Bump(43)
err = routePanelAnalyticsAgentViews(w,req,user,extraData) err = routePanelAnalyticsAgentViews(w,req,user,extraData)
case "/panel/analytics/system/": case "/panel/analytics/system/":
common.RouteViewCounter.Bump(43) common.RouteViewCounter.Bump(44)
err = routePanelAnalyticsSystemViews(w,req,user,extraData) err = routePanelAnalyticsSystemViews(w,req,user,extraData)
case "/panel/analytics/referrer/":
common.RouteViewCounter.Bump(45)
err = routePanelAnalyticsReferrerViews(w,req,user,extraData)
case "/panel/analytics/posts/": case "/panel/analytics/posts/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
if err != nil { if err != nil {
@ -1027,7 +1056,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(44) common.RouteViewCounter.Bump(46)
err = routePanelAnalyticsPosts(w,req,user) err = routePanelAnalyticsPosts(w,req,user)
case "/panel/analytics/topics/": case "/panel/analytics/topics/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
@ -1036,16 +1065,16 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(45) common.RouteViewCounter.Bump(47)
err = routePanelAnalyticsTopics(w,req,user) err = routePanelAnalyticsTopics(w,req,user)
case "/panel/groups/": case "/panel/groups/":
common.RouteViewCounter.Bump(46) common.RouteViewCounter.Bump(48)
err = routePanelGroups(w,req,user) err = routePanelGroups(w,req,user)
case "/panel/groups/edit/": case "/panel/groups/edit/":
common.RouteViewCounter.Bump(47) common.RouteViewCounter.Bump(49)
err = routePanelGroupsEdit(w,req,user,extraData) err = routePanelGroupsEdit(w,req,user,extraData)
case "/panel/groups/edit/perms/": case "/panel/groups/edit/perms/":
common.RouteViewCounter.Bump(48) common.RouteViewCounter.Bump(50)
err = routePanelGroupsEditPerms(w,req,user,extraData) err = routePanelGroupsEditPerms(w,req,user,extraData)
case "/panel/groups/edit/submit/": case "/panel/groups/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1054,7 +1083,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(49) common.RouteViewCounter.Bump(51)
err = routePanelGroupsEditSubmit(w,req,user,extraData) err = routePanelGroupsEditSubmit(w,req,user,extraData)
case "/panel/groups/edit/perms/submit/": case "/panel/groups/edit/perms/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1063,7 +1092,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(50) common.RouteViewCounter.Bump(52)
err = routePanelGroupsEditPermsSubmit(w,req,user,extraData) err = routePanelGroupsEditPermsSubmit(w,req,user,extraData)
case "/panel/groups/create/": case "/panel/groups/create/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1072,7 +1101,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(51) common.RouteViewCounter.Bump(53)
err = routePanelGroupsCreateSubmit(w,req,user) err = routePanelGroupsCreateSubmit(w,req,user)
case "/panel/backups/": case "/panel/backups/":
err = common.SuperAdminOnly(w,req,user) err = common.SuperAdminOnly(w,req,user)
@ -1081,10 +1110,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(52) common.RouteViewCounter.Bump(54)
err = routePanelBackups(w,req,user,extraData) err = routePanelBackups(w,req,user,extraData)
case "/panel/logs/mod/": case "/panel/logs/mod/":
common.RouteViewCounter.Bump(53) common.RouteViewCounter.Bump(55)
err = routePanelLogsMod(w,req,user) err = routePanelLogsMod(w,req,user)
case "/panel/debug/": case "/panel/debug/":
err = common.AdminOnly(w,req,user) err = common.AdminOnly(w,req,user)
@ -1093,10 +1122,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(54) common.RouteViewCounter.Bump(56)
err = routePanelDebug(w,req,user) err = routePanelDebug(w,req,user)
default: default:
common.RouteViewCounter.Bump(55) common.RouteViewCounter.Bump(57)
err = routePanelDashboard(w,req,user) err = routePanelDashboard(w,req,user)
} }
if err != nil { if err != nil {
@ -1111,7 +1140,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(56) common.RouteViewCounter.Bump(58)
err = routes.AccountEditCritical(w,req,user) err = routes.AccountEditCritical(w,req,user)
case "/user/edit/critical/submit/": case "/user/edit/critical/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1126,7 +1155,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(57) common.RouteViewCounter.Bump(59)
err = routeAccountEditCriticalSubmit(w,req,user) err = routeAccountEditCriticalSubmit(w,req,user)
case "/user/edit/avatar/": case "/user/edit/avatar/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -1135,7 +1164,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(58) common.RouteViewCounter.Bump(60)
err = routeAccountEditAvatar(w,req,user) err = routeAccountEditAvatar(w,req,user)
case "/user/edit/avatar/submit/": case "/user/edit/avatar/submit/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -1155,7 +1184,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(59) common.RouteViewCounter.Bump(61)
err = routeAccountEditAvatarSubmit(w,req,user) err = routeAccountEditAvatarSubmit(w,req,user)
case "/user/edit/username/": case "/user/edit/username/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -1164,7 +1193,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(60) common.RouteViewCounter.Bump(62)
err = routeAccountEditUsername(w,req,user) err = routeAccountEditUsername(w,req,user)
case "/user/edit/username/submit/": case "/user/edit/username/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1179,7 +1208,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(61) common.RouteViewCounter.Bump(63)
err = routeAccountEditUsernameSubmit(w,req,user) err = routeAccountEditUsernameSubmit(w,req,user)
case "/user/edit/email/": case "/user/edit/email/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -1188,7 +1217,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(62) common.RouteViewCounter.Bump(64)
err = routeAccountEditEmail(w,req,user) err = routeAccountEditEmail(w,req,user)
case "/user/edit/token/": case "/user/edit/token/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1203,11 +1232,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(63) common.RouteViewCounter.Bump(65)
err = routeAccountEditEmailTokenSubmit(w,req,user,extraData) err = routeAccountEditEmailTokenSubmit(w,req,user,extraData)
default: default:
req.URL.Path += extraData req.URL.Path += extraData
common.RouteViewCounter.Bump(64) common.RouteViewCounter.Bump(66)
err = routeProfile(w,req,user) err = routeProfile(w,req,user)
} }
if err != nil { if err != nil {
@ -1228,7 +1257,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(65) common.RouteViewCounter.Bump(67)
err = routes.BanUserSubmit(w,req,user,extraData) err = routes.BanUserSubmit(w,req,user,extraData)
case "/users/unban/": case "/users/unban/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1243,7 +1272,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(66) common.RouteViewCounter.Bump(68)
err = routes.UnbanUser(w,req,user,extraData) err = routes.UnbanUser(w,req,user,extraData)
case "/users/activate/": case "/users/activate/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1258,7 +1287,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(67) common.RouteViewCounter.Bump(69)
err = routes.ActivateUser(w,req,user,extraData) err = routes.ActivateUser(w,req,user,extraData)
case "/users/ips/": case "/users/ips/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -1267,7 +1296,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(68) common.RouteViewCounter.Bump(70)
err = routes.IPSearch(w,req,user) err = routes.IPSearch(w,req,user)
} }
if err != nil { if err != nil {
@ -1293,7 +1322,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(69) common.RouteViewCounter.Bump(71)
err = routes.CreateTopicSubmit(w,req,user) err = routes.CreateTopicSubmit(w,req,user)
case "/topic/edit/submit/": case "/topic/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1308,7 +1337,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(70) common.RouteViewCounter.Bump(72)
err = routes.EditTopicSubmit(w,req,user,extraData) err = routes.EditTopicSubmit(w,req,user,extraData)
case "/topic/delete/submit/": case "/topic/delete/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1324,7 +1353,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
req.URL.Path += extraData req.URL.Path += extraData
common.RouteViewCounter.Bump(71) common.RouteViewCounter.Bump(73)
err = routes.DeleteTopicSubmit(w,req,user) err = routes.DeleteTopicSubmit(w,req,user)
case "/topic/stick/submit/": case "/topic/stick/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1339,7 +1368,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(72) common.RouteViewCounter.Bump(74)
err = routes.StickTopicSubmit(w,req,user,extraData) err = routes.StickTopicSubmit(w,req,user,extraData)
case "/topic/unstick/submit/": case "/topic/unstick/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1354,7 +1383,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(73) common.RouteViewCounter.Bump(75)
err = routes.UnstickTopicSubmit(w,req,user,extraData) err = routes.UnstickTopicSubmit(w,req,user,extraData)
case "/topic/lock/submit/": case "/topic/lock/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1370,7 +1399,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
req.URL.Path += extraData req.URL.Path += extraData
common.RouteViewCounter.Bump(74) common.RouteViewCounter.Bump(76)
err = routes.LockTopicSubmit(w,req,user) err = routes.LockTopicSubmit(w,req,user)
case "/topic/unlock/submit/": case "/topic/unlock/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1385,7 +1414,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(75) common.RouteViewCounter.Bump(77)
err = routes.UnlockTopicSubmit(w,req,user,extraData) err = routes.UnlockTopicSubmit(w,req,user,extraData)
case "/topic/move/submit/": case "/topic/move/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1400,7 +1429,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(76) common.RouteViewCounter.Bump(78)
err = routes.MoveTopicSubmit(w,req,user,extraData) err = routes.MoveTopicSubmit(w,req,user,extraData)
case "/topic/like/submit/": case "/topic/like/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1415,10 +1444,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(77) common.RouteViewCounter.Bump(79)
err = routeLikeTopicSubmit(w,req,user,extraData) err = routeLikeTopicSubmit(w,req,user,extraData)
default: default:
common.RouteViewCounter.Bump(78) common.RouteViewCounter.Bump(80)
err = routes.ViewTopic(w,req,user, extraData) err = routes.ViewTopic(w,req,user, extraData)
} }
if err != nil { if err != nil {
@ -1444,7 +1473,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(79) common.RouteViewCounter.Bump(81)
err = routeCreateReplySubmit(w,req,user) err = routeCreateReplySubmit(w,req,user)
case "/reply/edit/submit/": case "/reply/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1459,7 +1488,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(80) common.RouteViewCounter.Bump(82)
err = routes.ReplyEditSubmit(w,req,user,extraData) err = routes.ReplyEditSubmit(w,req,user,extraData)
case "/reply/delete/submit/": case "/reply/delete/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1474,7 +1503,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(81) common.RouteViewCounter.Bump(83)
err = routes.ReplyDeleteSubmit(w,req,user,extraData) err = routes.ReplyDeleteSubmit(w,req,user,extraData)
case "/reply/like/submit/": case "/reply/like/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1489,7 +1518,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(82) common.RouteViewCounter.Bump(84)
err = routeReplyLikeSubmit(w,req,user,extraData) err = routeReplyLikeSubmit(w,req,user,extraData)
} }
if err != nil { if err != nil {
@ -1510,7 +1539,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(83) common.RouteViewCounter.Bump(85)
err = routeProfileReplyCreateSubmit(w,req,user) err = routeProfileReplyCreateSubmit(w,req,user)
case "/profile/reply/edit/submit/": case "/profile/reply/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1525,7 +1554,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(84) common.RouteViewCounter.Bump(86)
err = routes.ProfileReplyEditSubmit(w,req,user,extraData) err = routes.ProfileReplyEditSubmit(w,req,user,extraData)
case "/profile/reply/delete/submit/": case "/profile/reply/delete/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1540,7 +1569,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(85) common.RouteViewCounter.Bump(87)
err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData) err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData)
} }
if err != nil { if err != nil {
@ -1561,10 +1590,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(86) common.RouteViewCounter.Bump(88)
err = routes.PollVote(w,req,user,extraData) err = routes.PollVote(w,req,user,extraData)
case "/poll/results/": case "/poll/results/":
common.RouteViewCounter.Bump(87) common.RouteViewCounter.Bump(89)
err = routes.PollResults(w,req,user,extraData) err = routes.PollResults(w,req,user,extraData)
} }
if err != nil { if err != nil {
@ -1573,10 +1602,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
case "/accounts": case "/accounts":
switch(req.URL.Path) { switch(req.URL.Path) {
case "/accounts/login/": case "/accounts/login/":
common.RouteViewCounter.Bump(88) common.RouteViewCounter.Bump(90)
err = routes.AccountLogin(w,req,user) err = routes.AccountLogin(w,req,user)
case "/accounts/create/": case "/accounts/create/":
common.RouteViewCounter.Bump(89) common.RouteViewCounter.Bump(91)
err = routes.AccountRegister(w,req,user) err = routes.AccountRegister(w,req,user)
case "/accounts/logout/": case "/accounts/logout/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1591,7 +1620,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(90) common.RouteViewCounter.Bump(92)
err = routeLogout(w,req,user) err = routeLogout(w,req,user)
case "/accounts/login/submit/": case "/accounts/login/submit/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
@ -1600,7 +1629,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(91) common.RouteViewCounter.Bump(93)
err = routes.AccountLoginSubmit(w,req,user) err = routes.AccountLoginSubmit(w,req,user)
case "/accounts/create/submit/": case "/accounts/create/submit/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
@ -1609,7 +1638,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(92) common.RouteViewCounter.Bump(94)
err = routes.AccountRegisterSubmit(w,req,user) err = routes.AccountRegisterSubmit(w,req,user)
} }
if err != nil { if err != nil {
@ -1626,7 +1655,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
common.NotFound(w,req) common.NotFound(w,req)
return return
} }
common.RouteViewCounter.Bump(94) common.RouteViewCounter.Bump(96)
req.URL.Path += extraData req.URL.Path += extraData
// TODO: Find a way to propagate errors up from this? // TODO: Find a way to propagate errors up from this?
router.UploadHandler(w,req) // TODO: Count these views router.UploadHandler(w,req) // TODO: Count these views
@ -1669,7 +1698,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
router.RUnlock() router.RUnlock()
if ok { if ok {
common.RouteViewCounter.Bump(93) // TODO: Be more specific about *which* dynamic route it is common.RouteViewCounter.Bump(95) // TODO: Be more specific about *which* dynamic route it is
req.URL.Path += extraData req.URL.Path += extraData
err = handle(w,req,user) err = handle(w,req,user)
if err != nil { if err != nil {
@ -1683,7 +1712,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if strings.Contains(lowerPath,"admin") || strings.Contains(lowerPath,"sql") || strings.Contains(lowerPath,"manage") || strings.Contains(lowerPath,"//") || strings.Contains(lowerPath,"\\\\") || strings.Contains(lowerPath,"wp") || strings.Contains(lowerPath,"wordpress") || strings.Contains(lowerPath,"config") || strings.Contains(lowerPath,"setup") || strings.Contains(lowerPath,"install") || strings.Contains(lowerPath,"update") || strings.Contains(lowerPath,"php") { if strings.Contains(lowerPath,"admin") || strings.Contains(lowerPath,"sql") || strings.Contains(lowerPath,"manage") || strings.Contains(lowerPath,"//") || strings.Contains(lowerPath,"\\\\") || strings.Contains(lowerPath,"wp") || strings.Contains(lowerPath,"wordpress") || strings.Contains(lowerPath,"config") || strings.Contains(lowerPath,"setup") || strings.Contains(lowerPath,"install") || strings.Contains(lowerPath,"update") || strings.Contains(lowerPath,"php") {
router.SuspiciousRequest(req) router.SuspiciousRequest(req)
} }
common.RouteViewCounter.Bump(95) common.RouteViewCounter.Bump(97)
common.NotFound(w,req) common.NotFound(w,req)
} }
} }

View File

@ -125,6 +125,10 @@ func afterDBInit() (err error) {
if err != nil { if err != nil {
return err return err
} }
common.ReferrerTracker, err = common.NewDefaultReferrerTracker()
if err != nil {
return err
}
return nil return nil
} }

View File

@ -917,6 +917,81 @@ func routePanelAnalyticsSystemViews(w http.ResponseWriter, r *http.Request, user
return panelRenderTemplate("panel_analytics_system_views", w, r, user, &pi) return panelRenderTemplate("panel_analytics_system_views", w, r, user, &pi)
} }
func routePanelAnalyticsReferrerViews(w http.ResponseWriter, r *http.Request, user common.User, domain string) common.RouteError {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil {
return ferr
}
headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css")
headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js")
timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange"))
if err != nil {
return common.LocalError(err.Error(), w, r, user)
}
var revLabelList []int64
var labelList []int64
var viewMap = make(map[int64]int64)
var currentTime = time.Now().Unix()
for i := 1; i <= timeRange.Slices; i++ {
var label = currentTime - int64(i*timeRange.SliceWidth)
revLabelList = append(revLabelList, label)
viewMap[label] = 0
}
for _, value := range revLabelList {
labelList = append(labelList, value)
}
var viewList []int64
log.Print("in routePanelAnalyticsReferrerViews")
acc := qgen.Builder.Accumulator()
// TODO: Verify the agent is valid
rows, err := acc.Select("viewchunks_referrers").Columns("count, createdAt").Where("domain = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(domain)
if err != nil && err != ErrNoRows {
return common.InternalError(err, w, r)
}
defer rows.Close()
for rows.Next() {
var count int64
var createdAt time.Time
err := rows.Scan(&count, &createdAt)
if err != nil {
return common.InternalError(err, w, r)
}
var unixCreatedAt = createdAt.Unix()
if common.Dev.SuperDebug {
log.Print("count: ", count)
log.Print("createdAt: ", createdAt)
log.Print("unixCreatedAt: ", unixCreatedAt)
}
for _, value := range labelList {
if unixCreatedAt > value {
viewMap[value] += count
break
}
}
}
err = rows.Err()
if err != nil {
return common.InternalError(err, w, r)
}
for _, value := range revLabelList {
viewList = append(viewList, viewMap[value])
}
graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
log.Printf("graph: %+v\n", graph)
pi := common.PanelAnalyticsAgentPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", html.EscapeString(domain), "", graph, timeRange.Range}
return panelRenderTemplate("panel_analytics_referrer_views", w, r, user, &pi)
}
func routePanelAnalyticsTopics(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func routePanelAnalyticsTopics(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user) headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil { if ferr != nil {
@ -1232,6 +1307,57 @@ func routePanelAnalyticsSystems(w http.ResponseWriter, r *http.Request, user com
return panelRenderTemplate("panel_analytics_systems", w, r, user, &pi) return panelRenderTemplate("panel_analytics_systems", w, r, user, &pi)
} }
func routePanelAnalyticsReferrers(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil {
return ferr
}
var refMap = make(map[string]int)
timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange"))
if err != nil {
return common.LocalError(err.Error(), w, r, user)
}
acc := qgen.Builder.Accumulator()
rows, err := acc.Select("viewchunks_referrers").Columns("count, domain").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != ErrNoRows {
return common.InternalError(err, w, r)
}
defer rows.Close()
for rows.Next() {
var count int
var domain string
err := rows.Scan(&count, &domain)
if err != nil {
return common.InternalError(err, w, r)
}
if common.Dev.SuperDebug {
log.Print("count: ", count)
log.Print("domain: ", domain)
}
refMap[domain] += count
}
err = rows.Err()
if err != nil {
return common.InternalError(err, w, r)
}
// TODO: Sort this slice
var refItems []common.PanelAnalyticsAgentsItem
for domain, count := range refMap {
refItems = append(refItems, common.PanelAnalyticsAgentsItem{
Agent: html.EscapeString(domain),
Count: count,
})
}
pi := common.PanelAnalyticsAgentsPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", refItems, timeRange.Range}
return panelRenderTemplate("panel_analytics_referrers", w, r, user, &pi)
}
func routePanelSettings(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func routePanelSettings(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user) headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil { if ferr != nil {

View File

@ -411,11 +411,11 @@ $(document).ready(function(){
event.stopPropagation(); event.stopPropagation();
}) })
$(".create_topic_link").click(function(event){ $(".create_topic_link").click((event) => {
event.preventDefault(); event.preventDefault();
$(".topic_create_form").show(); $(".topic_create_form").show();
}); });
$(".topic_create_form .close_form").click(function(){ $(".topic_create_form .close_form").click((event) => {
event.preventDefault(); event.preventDefault();
$(".topic_create_form").hide(); $(".topic_create_form").hide();
}); });
@ -504,7 +504,7 @@ $(document).ready(function(){
data: JSON.stringify(selectedTopics), data: JSON.stringify(selectedTopics),
contentType: "application/json", contentType: "application/json",
error: ajaxError, error: ajaxError,
success: function() { success: () => {
window.location.reload(); window.location.reload();
} }
}); });
@ -514,7 +514,7 @@ $(document).ready(function(){
let selectNode = this.form.querySelector(".mod_floater_options"); let selectNode = this.form.querySelector(".mod_floater_options");
let optionNode = selectNode.options[selectNode.selectedIndex]; let optionNode = selectNode.options[selectNode.selectedIndex];
let action = optionNode.getAttribute("val"); let action = optionNode.getAttribute("val");
//console.log("action",action); //console.log("action", action);
// Handle these specially // Handle these specially
switch(action) { switch(action) {
@ -598,7 +598,7 @@ $(document).ready(function(){
} }
var pollInputIndex = 1; var pollInputIndex = 1;
$("#add_poll_button").click(function(event){ $("#add_poll_button").click((event) => {
event.preventDefault(); event.preventDefault();
$(".poll_content_row").removeClass("auto_hide"); $(".poll_content_row").removeClass("auto_hide");
$("#has_poll_input").val("1"); $("#has_poll_input").val("1");

View File

@ -416,7 +416,16 @@ func createTables(adapter qgen.Adapter) error {
[]qgen.DBTableColumn{ []qgen.DBTableColumn{
qgen.DBTableColumn{"count", "int", 0, false, false, "0"}, qgen.DBTableColumn{"count", "int", 0, false, false, "0"},
qgen.DBTableColumn{"createdAt", "datetime", 0, false, false, ""}, qgen.DBTableColumn{"createdAt", "datetime", 0, false, false, ""},
qgen.DBTableColumn{"system", "varchar", 200, false, false, ""}, // windows, android, bot, etc. qgen.DBTableColumn{"system", "varchar", 200, false, false, ""}, // windows, android, unknown, etc.
},
[]qgen.DBTableKey{},
)
qgen.Install.CreateTable("viewchunks_referrers", "", "",
[]qgen.DBTableColumn{
qgen.DBTableColumn{"count", "int", 0, false, false, "0"},
qgen.DBTableColumn{"createdAt", "datetime", 0, false, false, ""},
qgen.DBTableColumn{"domain", "varchar", 200, false, false, ""},
}, },
[]qgen.DBTableKey{}, []qgen.DBTableKey{},
) )

View File

@ -546,6 +546,17 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
common.OSViewCounter.Bump(osMapEnum[os]) common.OSViewCounter.Bump(osMapEnum[os])
} }
referrer := req.Header.Get("Referer") // Check the 'referrer' header too? :P
if referrer != "" {
// ? Optimise this a little?
referrer = strings.TrimPrefix(strings.TrimPrefix(referrer,"http://"),"https://")
referrer = strings.Split(referrer,"/")[0]
portless := strings.Split(referrer,":")[0]
if portless != "localhost" && portless != "127.0.0.1" && portless != common.Site.Host {
common.ReferrerTracker.Bump(referrer)
}
}
// Deal with the session stuff, etc. // Deal with the session stuff, etc.
user, ok := common.PreRoute(w, req) user, ok := common.PreRoute(w, req)
if !ok { if !ok {

View File

@ -167,9 +167,11 @@ func buildPanelRoutes() {
View("routePanelAnalyticsRoutes", "/panel/analytics/routes/").Before("ParseForm"), View("routePanelAnalyticsRoutes", "/panel/analytics/routes/").Before("ParseForm"),
View("routePanelAnalyticsAgents", "/panel/analytics/agents/").Before("ParseForm"), View("routePanelAnalyticsAgents", "/panel/analytics/agents/").Before("ParseForm"),
View("routePanelAnalyticsSystems", "/panel/analytics/systems/").Before("ParseForm"), View("routePanelAnalyticsSystems", "/panel/analytics/systems/").Before("ParseForm"),
View("routePanelAnalyticsReferrers", "/panel/analytics/referrers/").Before("ParseForm"),
View("routePanelAnalyticsRouteViews", "/panel/analytics/route/", "extraData"), View("routePanelAnalyticsRouteViews", "/panel/analytics/route/", "extraData"),
View("routePanelAnalyticsAgentViews", "/panel/analytics/agent/", "extraData"), View("routePanelAnalyticsAgentViews", "/panel/analytics/agent/", "extraData"),
View("routePanelAnalyticsSystemViews", "/panel/analytics/system/", "extraData"), View("routePanelAnalyticsSystemViews", "/panel/analytics/system/", "extraData"),
View("routePanelAnalyticsReferrerViews", "/panel/analytics/referrer/", "extraData"),
View("routePanelAnalyticsPosts", "/panel/analytics/posts/").Before("ParseForm"), View("routePanelAnalyticsPosts", "/panel/analytics/posts/").Before("ParseForm"),
View("routePanelAnalyticsTopics", "/panel/analytics/topics/").Before("ParseForm"), View("routePanelAnalyticsTopics", "/panel/analytics/topics/").Before("ParseForm"),

View File

@ -0,0 +1,5 @@
CREATE TABLE [viewchunks_referrers] (
[count] int DEFAULT 0 not null,
[createdAt] datetime not null,
[domain] nvarchar (200) not null
);

View File

@ -0,0 +1,5 @@
CREATE TABLE `viewchunks_referrers` (
`count` int DEFAULT 0 not null,
`createdAt` datetime not null,
`domain` varchar(200) not null
);

View File

@ -0,0 +1,5 @@
CREATE TABLE `viewchunks_referrers` (
`count` int DEFAULT 0 not null,
`createdAt` timestamp not null,
`domain` varchar (200) not null
);

View File

@ -41,11 +41,14 @@
<div class="rowitem passive submenu"> <div class="rowitem passive submenu">
<a href="/panel/analytics/agents/">Agents</a> <a href="/panel/analytics/agents/">Agents</a>
</div> </div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/crawlers/">Crawlers</a>
</div>
<div class="rowitem passive submenu"> <div class="rowitem passive submenu">
<a href="/panel/analytics/systems/">Systems</a> <a href="/panel/analytics/systems/">Systems</a>
</div> </div>
<div class="rowitem passive submenu"> <div class="rowitem passive submenu">
<a href="/panel/analytics/crawlers/">Crawlers</a> <a href="/panel/analytics/referrers/">Referrers</a>
</div> </div>
{{end}} {{end}}
<div class="rowitem passive"> <div class="rowitem passive">

View File

@ -0,0 +1,51 @@
{{template "header.html" . }}
<div class="colstack panel_stack">
{{template "panel-menu.html" . }}
<main id="panel_dashboard_right" class="colstack_right">
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/referrer/{{.Agent}}" method="get">
<div class="colstack_item colstack_head">
<div class="rowitem">
<a>{{.Agent}} Views</a>
<select class="timeRangeSelector to_right" name="timeRange">
<option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option>
<option val="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>2 days</option>
<option val="one-day"{{if eq .TimeRange "one-day"}} selected{{end}}>1 day</option>
<option val="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>12 hours</option>
<option val="six-hours"{{if eq .TimeRange "six-hours"}} selected{{end}}>6 hours</option>
</select>
</div>
</div>
</form>
<div id="panel_analytics_referrers" class="colstack_graph_holder">
<div class="ct-chart"></div>
</div>
</main>
</div>
<script>
let labels = [];
let rawLabels = [{{range .PrimaryGraph.Labels}}
{{.}},{{end}}
];
for(const i in rawLabels) {
let date = new Date(rawLabels[i]*1000);
console.log("date: ", date);
let minutes = "0" + date.getMinutes();
let label = date.getHours() + ":" + minutes.substr(-2);
console.log("label:", label);
labels.push(label);
}
labels = labels.reverse()
let seriesData = [{{range .PrimaryGraph.Series}}
{{.}},{{end}}
];
seriesData = seriesData.reverse();
Chartist.Line('.ct-chart', {
labels: labels,
series: [seriesData],
}, {
height: '250px',
});
</script>
{{template "footer.html" . }}

View File

@ -0,0 +1,29 @@
{{template "header.html" . }}
<div class="colstack panel_stack">
{{template "panel-menu.html" . }}
<main id="panel_dashboard_right" class="colstack_right">
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/referrers/" method="get">
<div class="colstack_item colstack_head">
<div class="rowitem">
<a>Referrers</a>
<select class="timeRangeSelector to_right" name="timeRange">
<option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option>
<option val="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>2 days</option>
<option val="one-day"{{if eq .TimeRange "one-day"}} selected{{end}}>1 day</option>
<option val="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>12 hours</option>
<option val="six-hours"{{if eq .TimeRange "six-hours"}} selected{{end}}>6 hours</option>
</select>
</div>
</div>
</form>
<div id="panel_analytics_referrers" class="colstack_item rowlist">
{{range .ItemList}}
<div class="rowitem panel_compactrow editable_parent">
<a href="/panel/analytics/referrer/{{.Agent}}" class="panel_upshift">{{.Agent}}</a>
<span class="panel_compacttext to_right">{{.Count}} views</span>
</div>
{{end}}
</div>
</main>
</div>
{{template "footer.html" . }}