Added per-forum analytics.

Added one week as a time range option.

Moved routeForum to routes.ViewForum
Renamed form_vars to formVars.
Moved the analytics chart JS into it's own file.
Changed the header text on the requests chart from Views to Requests.
Added simple hooks to global.js
Moved some of the in funcname prints to debug mode.
De-duplicated some of the back-end analytics logic.
This commit is contained in:
Azareal 2018-02-22 02:27:17 +00:00
parent 60964868d4
commit 0879f56893
30 changed files with 706 additions and 536 deletions

View File

@ -8,7 +8,7 @@ import (
"../../query_gen/lib" "../../query_gen/lib"
) )
// TODO: The forum view counter var ForumViewCounter *DefaultForumViewCounter
// TODO: Unload forum counters without any views over the past 15 minutes, if the admin has configured the forumstore with a cap and it's been hit? // TODO: Unload forum counters without any views over the past 15 minutes, if the admin has configured the forumstore with a cap and it's been hit?
// Forums can be reloaded from the database at any time, so we want to keep the counters separate from them // Forums can be reloaded from the database at any time, so we want to keep the counters separate from them
@ -29,7 +29,7 @@ func NewDefaultForumViewCounter() (*DefaultForumViewCounter, error) {
insert: acc.Insert("viewchunks_forums").Columns("count, createdAt, forum").Fields("?,UTC_TIMESTAMP(),?").Prepare(), insert: acc.Insert("viewchunks_forums").Columns("count, createdAt, forum").Fields("?,UTC_TIMESTAMP(),?").Prepare(),
} }
common.AddScheduledFifteenMinuteTask(counter.Tick) // There could be a lot of routes, so we don't want to be running this every second common.AddScheduledFifteenMinuteTask(counter.Tick) // There could be a lot of routes, so we don't want to be running this every second
//AddScheduledSecondTask(counter.Tick) //common.AddScheduledSecondTask(counter.Tick)
common.AddShutdownTask(counter.Tick) common.AddShutdownTask(counter.Tick)
return counter, acc.FirstError() return counter, acc.FirstError()
} }
@ -83,4 +83,36 @@ func (counter *DefaultForumViewCounter) insertChunk(count int, forum int) error
return err return err
} }
func (counter *DefaultForumViewCounter) Bump(forumID int) {
// Is the ID even?
if forumID%2 == 0 {
counter.evenLock.RLock()
forum, ok := counter.evenMap[forumID]
counter.evenLock.RUnlock()
if ok {
forum.Lock()
forum.counter++
forum.Unlock()
} else {
counter.evenLock.Lock()
counter.evenMap[forumID] = &RWMutexCounterBucket{counter: 1}
counter.evenLock.Unlock()
}
return
}
counter.oddLock.RLock()
forum, ok := counter.oddMap[forumID]
counter.oddLock.RUnlock()
if ok {
forum.Lock()
forum.counter++
forum.Unlock()
} else {
counter.oddLock.Lock()
counter.oddMap[forumID] = &RWMutexCounterBucket{counter: 1}
counter.oddLock.Unlock()
}
}
// TODO: Add a forum counter backed by two maps which grow as forums are created but never shrinks // TODO: Add a forum counter backed by two maps which grow as forums are created but never shrinks

View File

@ -23,7 +23,6 @@ type Stmts struct {
getActivityEntry *sql.Stmt getActivityEntry *sql.Stmt
forumEntryExists *sql.Stmt forumEntryExists *sql.Stmt
groupEntryExists *sql.Stmt groupEntryExists *sql.Stmt
getForumTopicsOffset *sql.Stmt
getAttachment *sql.Stmt getAttachment *sql.Stmt
getForumTopics *sql.Stmt getForumTopics *sql.Stmt
getProfileReplies *sql.Stmt getProfileReplies *sql.Stmt
@ -164,13 +163,6 @@ func _gen_mssql() (err error) {
return err return err
} }
log.Print("Preparing getForumTopicsOffset statement.")
stmts.getForumTopicsOffset, err = db.Prepare("SELECT [tid],[title],[content],[createdBy],[is_closed],[sticky],[createdAt],[lastReplyAt],[lastReplyBy],[parentID],[postCount],[likeCount] FROM [topics] WHERE [parentID] = ?1 ORDER BY sticky DESC,lastReplyAt DESC,createdBy DESC OFFSET ?2 ROWS FETCH NEXT ?3 ROWS ONLY")
if err != nil {
log.Print("Bad Query: ","SELECT [tid],[title],[content],[createdBy],[is_closed],[sticky],[createdAt],[lastReplyAt],[lastReplyBy],[parentID],[postCount],[likeCount] FROM [topics] WHERE [parentID] = ?1 ORDER BY sticky DESC,lastReplyAt DESC,createdBy DESC OFFSET ?2 ROWS FETCH NEXT ?3 ROWS ONLY")
return err
}
log.Print("Preparing getAttachment statement.") log.Print("Preparing getAttachment statement.")
stmts.getAttachment, err = db.Prepare("SELECT [sectionID],[sectionTable],[originID],[originTable],[uploadedBy],[path] FROM [attachments] WHERE [path] = ?1 AND [sectionID] = ?2 AND [sectionTable] = ?3") stmts.getAttachment, err = db.Prepare("SELECT [sectionID],[sectionTable],[originID],[originTable],[uploadedBy],[path] FROM [attachments] WHERE [path] = ?1 AND [sectionID] = ?2 AND [sectionTable] = ?3")
if err != nil { if err != nil {

View File

@ -25,7 +25,6 @@ type Stmts struct {
getActivityEntry *sql.Stmt getActivityEntry *sql.Stmt
forumEntryExists *sql.Stmt forumEntryExists *sql.Stmt
groupEntryExists *sql.Stmt groupEntryExists *sql.Stmt
getForumTopicsOffset *sql.Stmt
getAttachment *sql.Stmt getAttachment *sql.Stmt
getForumTopics *sql.Stmt getForumTopics *sql.Stmt
getProfileReplies *sql.Stmt getProfileReplies *sql.Stmt
@ -152,12 +151,6 @@ func _gen_mysql() (err error) {
return err return err
} }
log.Print("Preparing getForumTopicsOffset statement.")
stmts.getForumTopicsOffset, err = db.Prepare("SELECT `tid`,`title`,`content`,`createdBy`,`is_closed`,`sticky`,`createdAt`,`lastReplyAt`,`lastReplyBy`,`parentID`,`postCount`,`likeCount` FROM `topics` WHERE `parentID` = ? ORDER BY sticky DESC,lastReplyAt DESC,createdBy DESC LIMIT ?,?")
if err != nil {
return err
}
log.Print("Preparing getAttachment statement.") log.Print("Preparing getAttachment statement.")
stmts.getAttachment, err = db.Prepare("SELECT `sectionID`,`sectionTable`,`originID`,`originTable`,`uploadedBy`,`path` FROM `attachments` WHERE `path` = ? AND `sectionID` = ? AND `sectionTable` = ?") stmts.getAttachment, err = db.Prepare("SELECT `sectionID`,`sectionTable`,`originID`,`originTable`,`uploadedBy`,`path` FROM `attachments` WHERE `path` = ? AND `sectionID` = ? AND `sectionTable` = ?")
if err != nil { if err != nil {

View File

@ -21,7 +21,7 @@ var RouteMap = map[string]interface{}{
"routes.Overview": routes.Overview, "routes.Overview": routes.Overview,
"routes.CustomPage": routes.CustomPage, "routes.CustomPage": routes.CustomPage,
"routeForums": routeForums, "routeForums": routeForums,
"routeForum": routeForum, "routes.ViewForum": routes.ViewForum,
"routeChangeTheme": routeChangeTheme, "routeChangeTheme": routeChangeTheme,
"routeShowAttachment": routeShowAttachment, "routeShowAttachment": routeShowAttachment,
"routeWebsockets": routeWebsockets, "routeWebsockets": routeWebsockets,
@ -61,10 +61,12 @@ var RouteMap = map[string]interface{}{
"routePanelAnalyticsReferrers": routePanelAnalyticsReferrers, "routePanelAnalyticsReferrers": routePanelAnalyticsReferrers,
"routePanelAnalyticsRouteViews": routePanelAnalyticsRouteViews, "routePanelAnalyticsRouteViews": routePanelAnalyticsRouteViews,
"routePanelAnalyticsAgentViews": routePanelAnalyticsAgentViews, "routePanelAnalyticsAgentViews": routePanelAnalyticsAgentViews,
"routePanelAnalyticsForumViews": routePanelAnalyticsForumViews,
"routePanelAnalyticsSystemViews": routePanelAnalyticsSystemViews, "routePanelAnalyticsSystemViews": routePanelAnalyticsSystemViews,
"routePanelAnalyticsReferrerViews": routePanelAnalyticsReferrerViews, "routePanelAnalyticsReferrerViews": routePanelAnalyticsReferrerViews,
"routePanelAnalyticsPosts": routePanelAnalyticsPosts, "routePanelAnalyticsPosts": routePanelAnalyticsPosts,
"routePanelAnalyticsTopics": routePanelAnalyticsTopics, "routePanelAnalyticsTopics": routePanelAnalyticsTopics,
"routePanelAnalyticsForums": routePanelAnalyticsForums,
"routePanelGroups": routePanelGroups, "routePanelGroups": routePanelGroups,
"routePanelGroupsEdit": routePanelGroupsEdit, "routePanelGroupsEdit": routePanelGroupsEdit,
"routePanelGroupsEditPerms": routePanelGroupsEditPerms, "routePanelGroupsEditPerms": routePanelGroupsEditPerms,
@ -124,7 +126,7 @@ var routeMapEnum = map[string]int{
"routes.Overview": 1, "routes.Overview": 1,
"routes.CustomPage": 2, "routes.CustomPage": 2,
"routeForums": 3, "routeForums": 3,
"routeForum": 4, "routes.ViewForum": 4,
"routeChangeTheme": 5, "routeChangeTheme": 5,
"routeShowAttachment": 6, "routeShowAttachment": 6,
"routeWebsockets": 7, "routeWebsockets": 7,
@ -164,68 +166,70 @@ var routeMapEnum = map[string]int{
"routePanelAnalyticsReferrers": 41, "routePanelAnalyticsReferrers": 41,
"routePanelAnalyticsRouteViews": 42, "routePanelAnalyticsRouteViews": 42,
"routePanelAnalyticsAgentViews": 43, "routePanelAnalyticsAgentViews": 43,
"routePanelAnalyticsSystemViews": 44, "routePanelAnalyticsForumViews": 44,
"routePanelAnalyticsReferrerViews": 45, "routePanelAnalyticsSystemViews": 45,
"routePanelAnalyticsPosts": 46, "routePanelAnalyticsReferrerViews": 46,
"routePanelAnalyticsTopics": 47, "routePanelAnalyticsPosts": 47,
"routePanelGroups": 48, "routePanelAnalyticsTopics": 48,
"routePanelGroupsEdit": 49, "routePanelAnalyticsForums": 49,
"routePanelGroupsEditPerms": 50, "routePanelGroups": 50,
"routePanelGroupsEditSubmit": 51, "routePanelGroupsEdit": 51,
"routePanelGroupsEditPermsSubmit": 52, "routePanelGroupsEditPerms": 52,
"routePanelGroupsCreateSubmit": 53, "routePanelGroupsEditSubmit": 53,
"routePanelBackups": 54, "routePanelGroupsEditPermsSubmit": 54,
"routePanelLogsMod": 55, "routePanelGroupsCreateSubmit": 55,
"routePanelDebug": 56, "routePanelBackups": 56,
"routePanelDashboard": 57, "routePanelLogsMod": 57,
"routes.AccountEditCritical": 58, "routePanelDebug": 58,
"routeAccountEditCriticalSubmit": 59, "routePanelDashboard": 59,
"routeAccountEditAvatar": 60, "routes.AccountEditCritical": 60,
"routeAccountEditAvatarSubmit": 61, "routeAccountEditCriticalSubmit": 61,
"routeAccountEditUsername": 62, "routeAccountEditAvatar": 62,
"routeAccountEditUsernameSubmit": 63, "routeAccountEditAvatarSubmit": 63,
"routeAccountEditEmail": 64, "routeAccountEditUsername": 64,
"routeAccountEditEmailTokenSubmit": 65, "routeAccountEditUsernameSubmit": 65,
"routeProfile": 66, "routeAccountEditEmail": 66,
"routes.BanUserSubmit": 67, "routeAccountEditEmailTokenSubmit": 67,
"routes.UnbanUser": 68, "routeProfile": 68,
"routes.ActivateUser": 69, "routes.BanUserSubmit": 69,
"routes.IPSearch": 70, "routes.UnbanUser": 70,
"routes.CreateTopicSubmit": 71, "routes.ActivateUser": 71,
"routes.EditTopicSubmit": 72, "routes.IPSearch": 72,
"routes.DeleteTopicSubmit": 73, "routes.CreateTopicSubmit": 73,
"routes.StickTopicSubmit": 74, "routes.EditTopicSubmit": 74,
"routes.UnstickTopicSubmit": 75, "routes.DeleteTopicSubmit": 75,
"routes.LockTopicSubmit": 76, "routes.StickTopicSubmit": 76,
"routes.UnlockTopicSubmit": 77, "routes.UnstickTopicSubmit": 77,
"routes.MoveTopicSubmit": 78, "routes.LockTopicSubmit": 78,
"routeLikeTopicSubmit": 79, "routes.UnlockTopicSubmit": 79,
"routes.ViewTopic": 80, "routes.MoveTopicSubmit": 80,
"routeCreateReplySubmit": 81, "routeLikeTopicSubmit": 81,
"routes.ReplyEditSubmit": 82, "routes.ViewTopic": 82,
"routes.ReplyDeleteSubmit": 83, "routeCreateReplySubmit": 83,
"routeReplyLikeSubmit": 84, "routes.ReplyEditSubmit": 84,
"routeProfileReplyCreateSubmit": 85, "routes.ReplyDeleteSubmit": 85,
"routes.ProfileReplyEditSubmit": 86, "routeReplyLikeSubmit": 86,
"routes.ProfileReplyDeleteSubmit": 87, "routeProfileReplyCreateSubmit": 87,
"routes.PollVote": 88, "routes.ProfileReplyEditSubmit": 88,
"routes.PollResults": 89, "routes.ProfileReplyDeleteSubmit": 89,
"routes.AccountLogin": 90, "routes.PollVote": 90,
"routes.AccountRegister": 91, "routes.PollResults": 91,
"routeLogout": 92, "routes.AccountLogin": 92,
"routes.AccountLoginSubmit": 93, "routes.AccountRegister": 93,
"routes.AccountRegisterSubmit": 94, "routeLogout": 94,
"routeDynamic": 95, "routes.AccountLoginSubmit": 95,
"routeUploads": 96, "routes.AccountRegisterSubmit": 96,
"routes.StaticFile": 97, "routeDynamic": 97,
"BadRoute": 98, "routeUploads": 98,
"routes.StaticFile": 99,
"BadRoute": 100,
} }
var reverseRouteMapEnum = map[int]string{ var reverseRouteMapEnum = map[int]string{
0: "routeAPI", 0: "routeAPI",
1: "routes.Overview", 1: "routes.Overview",
2: "routes.CustomPage", 2: "routes.CustomPage",
3: "routeForums", 3: "routeForums",
4: "routeForum", 4: "routes.ViewForum",
5: "routeChangeTheme", 5: "routeChangeTheme",
6: "routeShowAttachment", 6: "routeShowAttachment",
7: "routeWebsockets", 7: "routeWebsockets",
@ -265,61 +269,63 @@ var reverseRouteMapEnum = map[int]string{
41: "routePanelAnalyticsReferrers", 41: "routePanelAnalyticsReferrers",
42: "routePanelAnalyticsRouteViews", 42: "routePanelAnalyticsRouteViews",
43: "routePanelAnalyticsAgentViews", 43: "routePanelAnalyticsAgentViews",
44: "routePanelAnalyticsSystemViews", 44: "routePanelAnalyticsForumViews",
45: "routePanelAnalyticsReferrerViews", 45: "routePanelAnalyticsSystemViews",
46: "routePanelAnalyticsPosts", 46: "routePanelAnalyticsReferrerViews",
47: "routePanelAnalyticsTopics", 47: "routePanelAnalyticsPosts",
48: "routePanelGroups", 48: "routePanelAnalyticsTopics",
49: "routePanelGroupsEdit", 49: "routePanelAnalyticsForums",
50: "routePanelGroupsEditPerms", 50: "routePanelGroups",
51: "routePanelGroupsEditSubmit", 51: "routePanelGroupsEdit",
52: "routePanelGroupsEditPermsSubmit", 52: "routePanelGroupsEditPerms",
53: "routePanelGroupsCreateSubmit", 53: "routePanelGroupsEditSubmit",
54: "routePanelBackups", 54: "routePanelGroupsEditPermsSubmit",
55: "routePanelLogsMod", 55: "routePanelGroupsCreateSubmit",
56: "routePanelDebug", 56: "routePanelBackups",
57: "routePanelDashboard", 57: "routePanelLogsMod",
58: "routes.AccountEditCritical", 58: "routePanelDebug",
59: "routeAccountEditCriticalSubmit", 59: "routePanelDashboard",
60: "routeAccountEditAvatar", 60: "routes.AccountEditCritical",
61: "routeAccountEditAvatarSubmit", 61: "routeAccountEditCriticalSubmit",
62: "routeAccountEditUsername", 62: "routeAccountEditAvatar",
63: "routeAccountEditUsernameSubmit", 63: "routeAccountEditAvatarSubmit",
64: "routeAccountEditEmail", 64: "routeAccountEditUsername",
65: "routeAccountEditEmailTokenSubmit", 65: "routeAccountEditUsernameSubmit",
66: "routeProfile", 66: "routeAccountEditEmail",
67: "routes.BanUserSubmit", 67: "routeAccountEditEmailTokenSubmit",
68: "routes.UnbanUser", 68: "routeProfile",
69: "routes.ActivateUser", 69: "routes.BanUserSubmit",
70: "routes.IPSearch", 70: "routes.UnbanUser",
71: "routes.CreateTopicSubmit", 71: "routes.ActivateUser",
72: "routes.EditTopicSubmit", 72: "routes.IPSearch",
73: "routes.DeleteTopicSubmit", 73: "routes.CreateTopicSubmit",
74: "routes.StickTopicSubmit", 74: "routes.EditTopicSubmit",
75: "routes.UnstickTopicSubmit", 75: "routes.DeleteTopicSubmit",
76: "routes.LockTopicSubmit", 76: "routes.StickTopicSubmit",
77: "routes.UnlockTopicSubmit", 77: "routes.UnstickTopicSubmit",
78: "routes.MoveTopicSubmit", 78: "routes.LockTopicSubmit",
79: "routeLikeTopicSubmit", 79: "routes.UnlockTopicSubmit",
80: "routes.ViewTopic", 80: "routes.MoveTopicSubmit",
81: "routeCreateReplySubmit", 81: "routeLikeTopicSubmit",
82: "routes.ReplyEditSubmit", 82: "routes.ViewTopic",
83: "routes.ReplyDeleteSubmit", 83: "routeCreateReplySubmit",
84: "routeReplyLikeSubmit", 84: "routes.ReplyEditSubmit",
85: "routeProfileReplyCreateSubmit", 85: "routes.ReplyDeleteSubmit",
86: "routes.ProfileReplyEditSubmit", 86: "routeReplyLikeSubmit",
87: "routes.ProfileReplyDeleteSubmit", 87: "routeProfileReplyCreateSubmit",
88: "routes.PollVote", 88: "routes.ProfileReplyEditSubmit",
89: "routes.PollResults", 89: "routes.ProfileReplyDeleteSubmit",
90: "routes.AccountLogin", 90: "routes.PollVote",
91: "routes.AccountRegister", 91: "routes.PollResults",
92: "routeLogout", 92: "routes.AccountLogin",
93: "routes.AccountLoginSubmit", 93: "routes.AccountRegister",
94: "routes.AccountRegisterSubmit", 94: "routeLogout",
95: "routeDynamic", 95: "routes.AccountLoginSubmit",
96: "routeUploads", 96: "routes.AccountRegisterSubmit",
97: "routes.StaticFile", 97: "routeDynamic",
98: "BadRoute", 98: "routeUploads",
99: "routes.StaticFile",
100: "BadRoute",
} }
var osMapEnum = map[string]int{ var osMapEnum = map[string]int{
"unknown": 0, "unknown": 0,
@ -571,7 +577,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
counters.GlobalViewCounter.Bump() counters.GlobalViewCounter.Bump()
if prefix == "/static" { if prefix == "/static" {
counters.RouteViewCounter.Bump(97) counters.RouteViewCounter.Bump(99)
req.URL.Path += extraData req.URL.Path += extraData
routes.StaticFile(w, req) routes.StaticFile(w, req)
return return
@ -745,7 +751,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
case "/forum": case "/forum":
counters.RouteViewCounter.Bump(4) counters.RouteViewCounter.Bump(4)
err = routeForum(w,req,user,extraData) err = routes.ViewForum(w,req,user,extraData)
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)
} }
@ -1052,11 +1058,14 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
case "/panel/analytics/agent/": case "/panel/analytics/agent/":
counters.RouteViewCounter.Bump(43) counters.RouteViewCounter.Bump(43)
err = routePanelAnalyticsAgentViews(w,req,user,extraData) err = routePanelAnalyticsAgentViews(w,req,user,extraData)
case "/panel/analytics/system/": case "/panel/analytics/forum/":
counters.RouteViewCounter.Bump(44) counters.RouteViewCounter.Bump(44)
err = routePanelAnalyticsForumViews(w,req,user,extraData)
case "/panel/analytics/system/":
counters.RouteViewCounter.Bump(45)
err = routePanelAnalyticsSystemViews(w,req,user,extraData) err = routePanelAnalyticsSystemViews(w,req,user,extraData)
case "/panel/analytics/referrer/": case "/panel/analytics/referrer/":
counters.RouteViewCounter.Bump(45) counters.RouteViewCounter.Bump(46)
err = routePanelAnalyticsReferrerViews(w,req,user,extraData) 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)
@ -1065,7 +1074,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(46) counters.RouteViewCounter.Bump(47)
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)
@ -1074,16 +1083,25 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(47)
err = routePanelAnalyticsTopics(w,req,user)
case "/panel/groups/":
counters.RouteViewCounter.Bump(48) counters.RouteViewCounter.Bump(48)
err = routePanelAnalyticsTopics(w,req,user)
case "/panel/analytics/forums/":
err = common.ParseForm(w,req,user)
if err != nil {
router.handleError(err,w,req,user)
return
}
counters.RouteViewCounter.Bump(49)
err = routePanelAnalyticsForums(w,req,user)
case "/panel/groups/":
counters.RouteViewCounter.Bump(50)
err = routePanelGroups(w,req,user) err = routePanelGroups(w,req,user)
case "/panel/groups/edit/": case "/panel/groups/edit/":
counters.RouteViewCounter.Bump(49) counters.RouteViewCounter.Bump(51)
err = routePanelGroupsEdit(w,req,user,extraData) err = routePanelGroupsEdit(w,req,user,extraData)
case "/panel/groups/edit/perms/": case "/panel/groups/edit/perms/":
counters.RouteViewCounter.Bump(50) counters.RouteViewCounter.Bump(52)
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)
@ -1092,7 +1110,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(51) counters.RouteViewCounter.Bump(53)
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)
@ -1101,7 +1119,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(52) counters.RouteViewCounter.Bump(54)
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)
@ -1110,7 +1128,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(53) counters.RouteViewCounter.Bump(55)
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)
@ -1119,10 +1137,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(54) counters.RouteViewCounter.Bump(56)
err = routePanelBackups(w,req,user,extraData) err = routePanelBackups(w,req,user,extraData)
case "/panel/logs/mod/": case "/panel/logs/mod/":
counters.RouteViewCounter.Bump(55) counters.RouteViewCounter.Bump(57)
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)
@ -1131,10 +1149,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(56) counters.RouteViewCounter.Bump(58)
err = routePanelDebug(w,req,user) err = routePanelDebug(w,req,user)
default: default:
counters.RouteViewCounter.Bump(57) counters.RouteViewCounter.Bump(59)
err = routePanelDashboard(w,req,user) err = routePanelDashboard(w,req,user)
} }
if err != nil { if err != nil {
@ -1149,7 +1167,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(58) counters.RouteViewCounter.Bump(60)
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)
@ -1164,7 +1182,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(59) counters.RouteViewCounter.Bump(61)
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)
@ -1173,7 +1191,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(60) counters.RouteViewCounter.Bump(62)
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)
@ -1193,7 +1211,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(61) counters.RouteViewCounter.Bump(63)
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)
@ -1202,7 +1220,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(62) counters.RouteViewCounter.Bump(64)
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)
@ -1217,7 +1235,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(63) counters.RouteViewCounter.Bump(65)
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)
@ -1226,7 +1244,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(64) counters.RouteViewCounter.Bump(66)
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)
@ -1241,11 +1259,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(65) counters.RouteViewCounter.Bump(67)
err = routeAccountEditEmailTokenSubmit(w,req,user,extraData) err = routeAccountEditEmailTokenSubmit(w,req,user,extraData)
default: default:
req.URL.Path += extraData req.URL.Path += extraData
counters.RouteViewCounter.Bump(66) counters.RouteViewCounter.Bump(68)
err = routeProfile(w,req,user) err = routeProfile(w,req,user)
} }
if err != nil { if err != nil {
@ -1266,7 +1284,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(67) counters.RouteViewCounter.Bump(69)
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)
@ -1281,7 +1299,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(68) counters.RouteViewCounter.Bump(70)
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)
@ -1296,7 +1314,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(69) counters.RouteViewCounter.Bump(71)
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)
@ -1305,7 +1323,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(70) counters.RouteViewCounter.Bump(72)
err = routes.IPSearch(w,req,user) err = routes.IPSearch(w,req,user)
} }
if err != nil { if err != nil {
@ -1331,7 +1349,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(71) counters.RouteViewCounter.Bump(73)
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)
@ -1346,7 +1364,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(72) counters.RouteViewCounter.Bump(74)
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)
@ -1362,7 +1380,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
req.URL.Path += extraData req.URL.Path += extraData
counters.RouteViewCounter.Bump(73) counters.RouteViewCounter.Bump(75)
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)
@ -1377,7 +1395,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(74) counters.RouteViewCounter.Bump(76)
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)
@ -1392,7 +1410,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(75) counters.RouteViewCounter.Bump(77)
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)
@ -1408,7 +1426,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
req.URL.Path += extraData req.URL.Path += extraData
counters.RouteViewCounter.Bump(76) counters.RouteViewCounter.Bump(78)
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)
@ -1423,7 +1441,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(77) counters.RouteViewCounter.Bump(79)
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)
@ -1438,7 +1456,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(78) counters.RouteViewCounter.Bump(80)
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)
@ -1453,10 +1471,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(79) counters.RouteViewCounter.Bump(81)
err = routeLikeTopicSubmit(w,req,user,extraData) err = routeLikeTopicSubmit(w,req,user,extraData)
default: default:
counters.RouteViewCounter.Bump(80) counters.RouteViewCounter.Bump(82)
err = routes.ViewTopic(w,req,user, extraData) err = routes.ViewTopic(w,req,user, extraData)
} }
if err != nil { if err != nil {
@ -1482,7 +1500,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(81) counters.RouteViewCounter.Bump(83)
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)
@ -1497,7 +1515,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(82) counters.RouteViewCounter.Bump(84)
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)
@ -1512,7 +1530,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(83) counters.RouteViewCounter.Bump(85)
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)
@ -1527,7 +1545,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(84) counters.RouteViewCounter.Bump(86)
err = routeReplyLikeSubmit(w,req,user,extraData) err = routeReplyLikeSubmit(w,req,user,extraData)
} }
if err != nil { if err != nil {
@ -1548,7 +1566,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(85) counters.RouteViewCounter.Bump(87)
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)
@ -1563,7 +1581,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(86) counters.RouteViewCounter.Bump(88)
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)
@ -1578,7 +1596,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(87) counters.RouteViewCounter.Bump(89)
err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData) err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData)
} }
if err != nil { if err != nil {
@ -1599,10 +1617,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(88) counters.RouteViewCounter.Bump(90)
err = routes.PollVote(w,req,user,extraData) err = routes.PollVote(w,req,user,extraData)
case "/poll/results/": case "/poll/results/":
counters.RouteViewCounter.Bump(89) counters.RouteViewCounter.Bump(91)
err = routes.PollResults(w,req,user,extraData) err = routes.PollResults(w,req,user,extraData)
} }
if err != nil { if err != nil {
@ -1611,10 +1629,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/":
counters.RouteViewCounter.Bump(90) counters.RouteViewCounter.Bump(92)
err = routes.AccountLogin(w,req,user) err = routes.AccountLogin(w,req,user)
case "/accounts/create/": case "/accounts/create/":
counters.RouteViewCounter.Bump(91) counters.RouteViewCounter.Bump(93)
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)
@ -1629,7 +1647,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(92) counters.RouteViewCounter.Bump(94)
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)
@ -1638,7 +1656,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(93) counters.RouteViewCounter.Bump(95)
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)
@ -1647,7 +1665,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(94) counters.RouteViewCounter.Bump(96)
err = routes.AccountRegisterSubmit(w,req,user) err = routes.AccountRegisterSubmit(w,req,user)
} }
if err != nil { if err != nil {
@ -1664,7 +1682,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
common.NotFound(w,req,nil) common.NotFound(w,req,nil)
return return
} }
counters.RouteViewCounter.Bump(96) counters.RouteViewCounter.Bump(98)
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
@ -1707,7 +1725,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
router.RUnlock() router.RUnlock()
if ok { if ok {
counters.RouteViewCounter.Bump(95) // TODO: Be more specific about *which* dynamic route it is counters.RouteViewCounter.Bump(97) // 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 {
@ -1721,7 +1739,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)
} }
counters.RouteViewCounter.Bump(98) counters.RouteViewCounter.Bump(100)
common.NotFound(w,req,nil) common.NotFound(w,req,nil)
} }
} }

View File

@ -130,6 +130,10 @@ func afterDBInit() (err error) {
if err != nil { if err != nil {
return err return err
} }
counters.ForumViewCounter, err = counters.NewDefaultForumViewCounter()
if err != nil {
return err
}
counters.ReferrerTracker, err = counters.NewDefaultReferrerTracker() counters.ReferrerTracker, err = counters.NewDefaultReferrerTracker()
if err != nil { if err != nil {
return err return err

View File

@ -575,6 +575,12 @@ func panelAnalyticsTimeRange(rawTimeRange string) (timeRange AnalyticsTimeRange,
timeRange.Slices = 30 timeRange.Slices = 30
timeRange.SliceWidth = 60 * 60 * 24 timeRange.SliceWidth = 60 * 60 * 24
timeRange.Range = "one-month" timeRange.Range = "one-month"
case "one-week":
timeRange.Quantity = 7
timeRange.Unit = "day"
timeRange.Slices = 14
timeRange.SliceWidth = 60 * 60 * 12
timeRange.Range = "one-week"
case "two-days": // Two days is experimental case "two-days": // Two days is experimental
timeRange.Quantity = 2 timeRange.Quantity = 2
timeRange.Unit = "day" timeRange.Unit = "day"
@ -599,24 +605,9 @@ func panelAnalyticsTimeRange(rawTimeRange string) (timeRange AnalyticsTimeRange,
return timeRange, nil return timeRange, nil
} }
func routePanelAnalyticsViews(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func panelAnalyticsTimeRangeToLabelList(timeRange AnalyticsTimeRange) (revLabelList []int64, labelList []int64, viewMap map[int64]int64) {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user) viewMap = make(map[int64]int64)
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() var currentTime = time.Now().Unix()
for i := 1; i <= timeRange.Slices; i++ { for i := 1; i <= timeRange.Slices; i++ {
var label = currentTime - int64(i*timeRange.SliceWidth) var label = currentTime - int64(i*timeRange.SliceWidth)
revLabelList = append(revLabelList, label) revLabelList = append(revLabelList, label)
@ -625,9 +616,26 @@ func routePanelAnalyticsViews(w http.ResponseWriter, r *http.Request, user commo
for _, value := range revLabelList { for _, value := range revLabelList {
labelList = append(labelList, value) labelList = append(labelList, value)
} }
return revLabelList, labelList, viewMap
}
func routePanelAnalyticsViews(w http.ResponseWriter, r *http.Request, user common.User) 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")
headerVars.Scripts = append(headerVars.Scripts, "analytics.js")
timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange"))
if err != nil {
return common.LocalError(err.Error(), w, r, user)
}
revLabelList, labelList, viewMap := panelAnalyticsTimeRangeToLabelList(timeRange)
var viewList []int64 var viewList []int64
log.Print("in routePanelAnalyticsViews") common.DebugLog("in routePanelAnalyticsViews")
acc := qgen.Builder.Accumulator() acc := qgen.Builder.Accumulator()
rows, err := acc.Select("viewchunks").Columns("count, createdAt").Where("route = ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() rows, err := acc.Select("viewchunks").Columns("count, createdAt").Where("route = ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
@ -679,28 +687,16 @@ func routePanelAnalyticsRouteViews(w http.ResponseWriter, r *http.Request, user
} }
headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css") headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css")
headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js") headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js")
headerVars.Scripts = append(headerVars.Scripts, "analytics.js")
timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange")) timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange"))
if err != nil { if err != nil {
return common.LocalError(err.Error(), w, r, user) return common.LocalError(err.Error(), w, r, user)
} }
revLabelList, labelList, viewMap := panelAnalyticsTimeRangeToLabelList(timeRange)
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 var viewList []int64
log.Print("in routePanelAnalyticsRouteViews") common.DebugLog("in routePanelAnalyticsRouteViews")
acc := qgen.Builder.Accumulator() acc := qgen.Builder.Accumulator()
// TODO: Validate the route is valid // TODO: Validate the route is valid
@ -753,28 +749,16 @@ func routePanelAnalyticsAgentViews(w http.ResponseWriter, r *http.Request, user
} }
headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css") headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css")
headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js") headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js")
headerVars.Scripts = append(headerVars.Scripts, "analytics.js")
timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange")) timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange"))
if err != nil { if err != nil {
return common.LocalError(err.Error(), w, r, user) return common.LocalError(err.Error(), w, r, user)
} }
revLabelList, labelList, viewMap := panelAnalyticsTimeRangeToLabelList(timeRange)
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 var viewList []int64
log.Print("in routePanelAnalyticsAgentViews") common.DebugLog("in routePanelAnalyticsAgentViews")
acc := qgen.Builder.Accumulator() acc := qgen.Builder.Accumulator()
// TODO: Verify the agent is valid // TODO: Verify the agent is valid
@ -829,6 +813,79 @@ func routePanelAnalyticsAgentViews(w http.ResponseWriter, r *http.Request, user
return panelRenderTemplate("panel_analytics_agent_views", w, r, user, &pi) return panelRenderTemplate("panel_analytics_agent_views", w, r, user, &pi)
} }
func routePanelAnalyticsForumViews(w http.ResponseWriter, r *http.Request, user common.User, sfid 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")
headerVars.Scripts = append(headerVars.Scripts, "analytics.js")
timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange"))
if err != nil {
return common.LocalError(err.Error(), w, r, user)
}
revLabelList, labelList, viewMap := panelAnalyticsTimeRangeToLabelList(timeRange)
var viewList []int64
common.DebugLog("in routePanelAnalyticsForumViews")
acc := qgen.Builder.Accumulator()
// TODO: Verify the agent is valid
rows, err := acc.Select("viewchunks_forums").Columns("count, createdAt").Where("forum = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(sfid)
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()
// TODO: Bulk log this
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)
fid, err := strconv.Atoi(sfid)
if err != nil {
return common.InternalError(err, w, r)
}
forum, err := common.Forums.Get(fid)
if err != nil {
return common.InternalError(err, w, r)
}
pi := common.PanelAnalyticsAgentPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", sfid, forum.Name, graph, timeRange.Range}
return panelRenderTemplate("panel_analytics_forum_views", w, r, user, &pi)
}
func routePanelAnalyticsSystemViews(w http.ResponseWriter, r *http.Request, user common.User, system string) common.RouteError { func routePanelAnalyticsSystemViews(w http.ResponseWriter, r *http.Request, user common.User, system string) common.RouteError {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user) headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil { if ferr != nil {
@ -836,28 +893,16 @@ func routePanelAnalyticsSystemViews(w http.ResponseWriter, r *http.Request, user
} }
headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css") headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css")
headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js") headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js")
headerVars.Scripts = append(headerVars.Scripts, "analytics.js")
timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange")) timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange"))
if err != nil { if err != nil {
return common.LocalError(err.Error(), w, r, user) return common.LocalError(err.Error(), w, r, user)
} }
revLabelList, labelList, viewMap := panelAnalyticsTimeRangeToLabelList(timeRange)
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 var viewList []int64
log.Print("in routePanelAnalyticsSystemViews") common.DebugLog("in routePanelAnalyticsSystemViews")
acc := qgen.Builder.Accumulator() acc := qgen.Builder.Accumulator()
// TODO: Verify the agent is valid // TODO: Verify the agent is valid
@ -918,28 +963,16 @@ func routePanelAnalyticsReferrerViews(w http.ResponseWriter, r *http.Request, us
} }
headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css") headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css")
headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js") headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js")
headerVars.Scripts = append(headerVars.Scripts, "analytics.js")
timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange")) timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange"))
if err != nil { if err != nil {
return common.LocalError(err.Error(), w, r, user) return common.LocalError(err.Error(), w, r, user)
} }
revLabelList, labelList, viewMap := panelAnalyticsTimeRangeToLabelList(timeRange)
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 var viewList []int64
log.Print("in routePanelAnalyticsReferrerViews") common.DebugLog("in routePanelAnalyticsReferrerViews")
acc := qgen.Builder.Accumulator() acc := qgen.Builder.Accumulator()
// TODO: Verify the agent is valid // TODO: Verify the agent is valid
@ -994,28 +1027,16 @@ func routePanelAnalyticsTopics(w http.ResponseWriter, r *http.Request, user comm
} }
headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css") headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css")
headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js") headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js")
headerVars.Scripts = append(headerVars.Scripts, "analytics.js")
timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange")) timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange"))
if err != nil { if err != nil {
return common.LocalError(err.Error(), w, r, user) return common.LocalError(err.Error(), w, r, user)
} }
revLabelList, labelList, viewMap := panelAnalyticsTimeRangeToLabelList(timeRange)
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 var viewList []int64
log.Print("in routePanelAnalyticsTopics") common.DebugLog("in routePanelAnalyticsTopics")
acc := qgen.Builder.Accumulator() acc := qgen.Builder.Accumulator()
rows, err := acc.Select("topicchunks").Columns("count, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() rows, err := acc.Select("topicchunks").Columns("count, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
@ -1071,28 +1092,16 @@ func routePanelAnalyticsPosts(w http.ResponseWriter, r *http.Request, user commo
} }
headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css") headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css")
headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js") headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js")
headerVars.Scripts = append(headerVars.Scripts, "analytics.js")
timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange")) timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange"))
if err != nil { if err != nil {
return common.LocalError(err.Error(), w, r, user) return common.LocalError(err.Error(), w, r, user)
} }
revLabelList, labelList, viewMap := panelAnalyticsTimeRangeToLabelList(timeRange)
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 var viewList []int64
log.Print("in routePanelAnalyticsPosts") common.DebugLog("in routePanelAnalyticsPosts")
acc := qgen.Builder.Accumulator() acc := qgen.Builder.Accumulator()
rows, err := acc.Select("postchunks").Columns("count, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() rows, err := acc.Select("postchunks").Columns("count, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
@ -1140,6 +1149,67 @@ func routePanelAnalyticsPosts(w http.ResponseWriter, r *http.Request, user commo
return panelRenderTemplate("panel_analytics_posts", w, r, user, &pi) return panelRenderTemplate("panel_analytics_posts", w, r, user, &pi)
} }
func routePanelAnalyticsForums(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 forumMap = 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_forums").Columns("count, forum").Where("forum != ''").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 forum string
err := rows.Scan(&count, &forum)
if err != nil {
return common.InternalError(err, w, r)
}
// TODO: Bulk log this
if common.Dev.SuperDebug {
log.Print("count: ", count)
log.Print("forum: ", forum)
}
forumMap[forum] += count
}
err = rows.Err()
if err != nil {
return common.InternalError(err, w, r)
}
// TODO: Sort this slice
var forumItems []common.PanelAnalyticsAgentsItem
for sfid, count := range forumMap {
fid, err := strconv.Atoi(sfid)
if err != nil {
return common.InternalError(err, w, r)
}
forum, err := common.Forums.Get(fid)
if err != nil {
return common.InternalError(err, w, r)
}
forumItems = append(forumItems, common.PanelAnalyticsAgentsItem{
Agent: sfid,
FriendlyAgent: forum.Name,
Count: count,
})
}
pi := common.PanelAnalyticsAgentsPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", forumItems, timeRange.Range}
return panelRenderTemplate("panel_analytics_forums", w, r, user, &pi)
}
func routePanelAnalyticsRoutes(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func routePanelAnalyticsRoutes(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 {

39
public/analytics.js Normal file
View File

@ -0,0 +1,39 @@
/*addHook(() => {
})*/
function buildStatsChart(rawLabels, seriesData, timeRange) {
let labels = [];
if(timeRange=="one-month") {
labels = ["today","01 days"];
for(let i = 2; i < 30; i++) {
let label = "0" + i + " days";
if(label.length > "01 days".length) label = label.substr(1);
labels.push(label);
}
} else if(timeRange=="one-week") {
labels = ["today"];
for(let i = 2; i < 14; i++) {
if (i%2==0) labels.push("");
else labels.push(Math.floor(i/2) + " days");
}
} else {
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()
seriesData = seriesData.reverse();
Chartist.Line('.ct_chart', {
labels: labels,
series: [seriesData],
}, {
height: '250px',
});
}

View File

@ -1,14 +1,27 @@
'use strict'; 'use strict';
var form_vars = {}; var formVars = {};
var alertList = []; var alertList = [];
var alertCount = 0; var alertCount = 0;
var conn; var conn;
var selectedTopics = []; var selectedTopics = [];
var attachItemCallback = function(){} var attachItemCallback = function(){}
var hooks = {
"start_init": [],
"end_init": [],
};
// Topic move // Topic move
var forumToMoveTo = 0; var forumToMoveTo = 0;
function runHook(name, ...args) {
if(!(name in hooks)) return;
let hook = hooks[name];
for (const callback in hook) {
callback(...args);
}
}
// TODO: Write a friendlier error handler which uses a .notice or something, we could have a specialised one for alerts // TODO: Write a friendlier error handler which uses a .notice or something, we could have a specialised one for alerts
function ajaxError(xhr,status,errstr) { function ajaxError(xhr,status,errstr) {
console.log("The AJAX request failed"); console.log("The AJAX request failed");
@ -203,6 +216,7 @@ function runWebSockets() {
} }
$(document).ready(function(){ $(document).ready(function(){
runHook("start_init");
if(window["WebSocket"]) runWebSockets(); if(window["WebSocket"]) runWebSockets();
else conn = false; else conn = false;
@ -311,7 +325,7 @@ $(document).ready(function(){
var fieldType = this.getAttribute("data-type"); var fieldType = this.getAttribute("data-type");
if(fieldType=="list") { if(fieldType=="list") {
var fieldValue = this.getAttribute("data-value"); var fieldValue = this.getAttribute("data-value");
if(fieldName in form_vars) var it = form_vars[fieldName]; if(fieldName in formVars) var it = formVars[fieldName];
else var it = ['No','Yes']; else var it = ['No','Yes'];
var itLen = it.length; var itLen = it.length;
var out = ""; var out = "";
@ -624,4 +638,6 @@ $(document).ready(function(){
}); });
}) })
}); });
runHook("end_init");
}); });

View File

@ -247,8 +247,6 @@ func writeSelects(adapter qgen.Adapter) error {
build.Select("groupEntryExists").Table("users_groups").Columns("gid").Where("name = ''").Orderby("gid ASC").Limit("0,1").Parse() build.Select("groupEntryExists").Table("users_groups").Columns("gid").Where("name = ''").Orderby("gid ASC").Limit("0,1").Parse()
build.Select("getForumTopicsOffset").Table("topics").Columns("tid, title, content, createdBy, is_closed, sticky, createdAt, lastReplyAt, lastReplyBy, parentID, postCount, likeCount").Where("parentID = ?").Orderby("sticky DESC, lastReplyAt DESC, createdBy DESC").Limit("?,?").Parse()
build.Select("getAttachment").Table("attachments").Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path").Where("path = ? AND sectionID = ? AND sectionTable = ?").Parse() build.Select("getAttachment").Table("attachments").Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path").Where("path = ? AND sectionID = ? AND sectionTable = ?").Parse()
return nil return nil

View File

@ -6,7 +6,7 @@ func routes() {
addRoute(View("routes.Overview", "/overview/")) addRoute(View("routes.Overview", "/overview/"))
addRoute(View("routes.CustomPage", "/pages/", "extraData")) addRoute(View("routes.CustomPage", "/pages/", "extraData"))
addRoute(View("routeForums", "/forums/" /*,"&forums"*/)) addRoute(View("routeForums", "/forums/" /*,"&forums"*/))
addRoute(View("routeForum", "/forum/", "extraData")) addRoute(View("routes.ViewForum", "/forum/", "extraData"))
addRoute(AnonAction("routeChangeTheme", "/theme/")) addRoute(AnonAction("routeChangeTheme", "/theme/"))
addRoute( addRoute(
View("routeShowAttachment", "/attachs/", "extraData").Before("ParseForm"), View("routeShowAttachment", "/attachs/", "extraData").Before("ParseForm"),
@ -170,10 +170,12 @@ func buildPanelRoutes() {
View("routePanelAnalyticsReferrers", "/panel/analytics/referrers/").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("routePanelAnalyticsForumViews", "/panel/analytics/forum/", "extraData"),
View("routePanelAnalyticsSystemViews", "/panel/analytics/system/", "extraData"), View("routePanelAnalyticsSystemViews", "/panel/analytics/system/", "extraData"),
View("routePanelAnalyticsReferrerViews", "/panel/analytics/referrer/", "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("routePanelAnalyticsForums", "/panel/analytics/forums/").Before("ParseForm"),
View("routePanelGroups", "/panel/groups/"), View("routePanelGroups", "/panel/groups/"),
View("routePanelGroupsEdit", "/panel/groups/edit/", "extraData"), View("routePanelGroupsEdit", "/panel/groups/edit/", "extraData"),

View File

@ -44,102 +44,6 @@ func routeUploads() {
func BadRoute() { func BadRoute() {
} }
func routeForum(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError {
page, _ := strconv.Atoi(r.FormValue("page"))
// SEO URLs...
halves := strings.Split(sfid, ".")
if len(halves) < 2 {
halves = append(halves, halves[0])
}
fid, err := strconv.Atoi(halves[1])
if err != nil {
return common.PreError("The provided ForumID is not a valid number.", w, r)
}
headerVars, ferr := common.ForumUserCheck(w, r, &user, fid)
if ferr != nil {
return ferr
}
if !user.Perms.ViewTopic {
return common.NoPermissions(w, r, user)
}
headerVars.Zone = "view_forum"
// TODO: Fix this double-check
forum, err := common.Forums.Get(fid)
if err == ErrNoRows {
return common.NotFound(w, r, headerVars)
} else if err != nil {
return common.InternalError(err, w, r)
}
// TODO: Does forum.TopicCount take the deleted items into consideration for guests? We don't have soft-delete yet, only hard-delete
offset, page, lastPage := common.PageOffset(forum.TopicCount, page, common.Config.ItemsPerPage)
// TODO: Move this to *Forum
rows, err := stmts.getForumTopicsOffset.Query(fid, offset, common.Config.ItemsPerPage)
if err != nil {
return common.InternalError(err, w, r)
}
defer rows.Close()
// TODO: Use something other than TopicsRow as we don't need to store the forum name and link on each and every topic item?
var topicList []*common.TopicsRow
var reqUserList = make(map[int]bool)
for rows.Next() {
var topicItem = common.TopicsRow{ID: 0}
err := rows.Scan(&topicItem.ID, &topicItem.Title, &topicItem.Content, &topicItem.CreatedBy, &topicItem.IsClosed, &topicItem.Sticky, &topicItem.CreatedAt, &topicItem.LastReplyAt, &topicItem.LastReplyBy, &topicItem.ParentID, &topicItem.PostCount, &topicItem.LikeCount)
if err != nil {
return common.InternalError(err, w, r)
}
topicItem.Link = common.BuildTopicURL(common.NameToSlug(topicItem.Title), topicItem.ID)
topicItem.RelativeLastReplyAt = common.RelativeTime(topicItem.LastReplyAt)
common.RunVhook("forum_trow_assign", &topicItem, &forum)
topicList = append(topicList, &topicItem)
reqUserList[topicItem.CreatedBy] = true
reqUserList[topicItem.LastReplyBy] = true
}
err = rows.Err()
if err != nil {
return common.InternalError(err, w, r)
}
// Convert the user ID map to a slice, then bulk load the users
var idSlice = make([]int, len(reqUserList))
var i int
for userID := range reqUserList {
idSlice[i] = userID
i++
}
// TODO: What if a user is deleted via the Control Panel?
userList, err := common.Users.BulkGetMap(idSlice)
if err != nil {
return common.InternalError(err, w, r)
}
// Second pass to the add the user data
// TODO: Use a pointer to TopicsRow instead of TopicsRow itself?
for _, topicItem := range topicList {
topicItem.Creator = userList[topicItem.CreatedBy]
topicItem.LastUser = userList[topicItem.LastReplyBy]
}
pageList := common.Paginate(forum.TopicCount, common.Config.ItemsPerPage, 5)
pi := common.ForumPage{forum.Name, user, headerVars, topicList, forum, pageList, page, lastPage}
if common.RunPreRenderHook("pre_render_forum", w, r, &user, &pi) {
return nil
}
err = common.RunThemeTemplate(headerVars.Theme.Name, "forum", pi, w)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
}
func routeForums(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func routeForums(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
headerVars, ferr := common.UserCheck(w, r, &user) headerVars, ferr := common.UserCheck(w, r, &user)
if ferr != nil { if ferr != nil {

125
routes/forum.go Normal file
View File

@ -0,0 +1,125 @@
package routes
import (
"database/sql"
"net/http"
"strconv"
"strings"
"../common"
"../common/counters"
"../query_gen/lib"
)
type ForumStmts struct {
getTopics *sql.Stmt
}
var forumStmts ForumStmts
// TODO: Move these DbInits into *Forum as Topics()
func init() {
common.DbInits.Add(func(acc *qgen.Accumulator) error {
forumStmts = ForumStmts{
getTopics: acc.Select("topics").Columns("tid, title, content, createdBy, is_closed, sticky, createdAt, lastReplyAt, lastReplyBy, parentID, postCount, likeCount").Where("parentID = ?").Orderby("sticky DESC, lastReplyAt DESC, createdBy DESC").Limit("?,?").Prepare(),
}
return acc.FirstError()
})
}
func ViewForum(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError {
page, _ := strconv.Atoi(r.FormValue("page"))
// SEO URLs...
halves := strings.Split(sfid, ".")
if len(halves) < 2 {
halves = append(halves, halves[0])
}
fid, err := strconv.Atoi(halves[1])
if err != nil {
return common.PreError("The provided ForumID is not a valid number.", w, r)
}
headerVars, ferr := common.ForumUserCheck(w, r, &user, fid)
if ferr != nil {
return ferr
}
if !user.Perms.ViewTopic {
return common.NoPermissions(w, r, user)
}
headerVars.Zone = "view_forum"
// TODO: Fix this double-check
forum, err := common.Forums.Get(fid)
if err == sql.ErrNoRows {
return common.NotFound(w, r, headerVars)
} else if err != nil {
return common.InternalError(err, w, r)
}
// TODO: Does forum.TopicCount take the deleted items into consideration for guests? We don't have soft-delete yet, only hard-delete
offset, page, lastPage := common.PageOffset(forum.TopicCount, page, common.Config.ItemsPerPage)
// TODO: Move this to *Forum
rows, err := forumStmts.getTopics.Query(fid, offset, common.Config.ItemsPerPage)
if err != nil {
return common.InternalError(err, w, r)
}
defer rows.Close()
// TODO: Use something other than TopicsRow as we don't need to store the forum name and link on each and every topic item?
var topicList []*common.TopicsRow
var reqUserList = make(map[int]bool)
for rows.Next() {
var topicItem = common.TopicsRow{ID: 0}
err := rows.Scan(&topicItem.ID, &topicItem.Title, &topicItem.Content, &topicItem.CreatedBy, &topicItem.IsClosed, &topicItem.Sticky, &topicItem.CreatedAt, &topicItem.LastReplyAt, &topicItem.LastReplyBy, &topicItem.ParentID, &topicItem.PostCount, &topicItem.LikeCount)
if err != nil {
return common.InternalError(err, w, r)
}
topicItem.Link = common.BuildTopicURL(common.NameToSlug(topicItem.Title), topicItem.ID)
topicItem.RelativeLastReplyAt = common.RelativeTime(topicItem.LastReplyAt)
common.RunVhook("forum_trow_assign", &topicItem, &forum)
topicList = append(topicList, &topicItem)
reqUserList[topicItem.CreatedBy] = true
reqUserList[topicItem.LastReplyBy] = true
}
err = rows.Err()
if err != nil {
return common.InternalError(err, w, r)
}
// Convert the user ID map to a slice, then bulk load the users
var idSlice = make([]int, len(reqUserList))
var i int
for userID := range reqUserList {
idSlice[i] = userID
i++
}
// TODO: What if a user is deleted via the Control Panel?
userList, err := common.Users.BulkGetMap(idSlice)
if err != nil {
return common.InternalError(err, w, r)
}
// Second pass to the add the user data
// TODO: Use a pointer to TopicsRow instead of TopicsRow itself?
for _, topicItem := range topicList {
topicItem.Creator = userList[topicItem.CreatedBy]
topicItem.LastUser = userList[topicItem.LastReplyBy]
}
pageList := common.Paginate(forum.TopicCount, common.Config.ItemsPerPage, 5)
pi := common.ForumPage{forum.Name, user, headerVars, topicList, forum, pageList, page, lastPage}
if common.RunPreRenderHook("pre_render_forum", w, r, &user, &pi) {
return nil
}
err = common.RunThemeTemplate(headerVars.Theme.Name, "forum", pi, w)
if err != nil {
return common.InternalError(err, w, r)
}
counters.ForumViewCounter.Bump(forum.ID)
return nil
}

View File

@ -191,6 +191,7 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, urlBit
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
counters.TopicViewCounter.Bump(topic.ID) // TODO: Move this into the router? counters.TopicViewCounter.Bump(topic.ID) // TODO: Move this into the router?
counters.ForumViewCounter.Bump(topic.ParentID)
return nil return nil
} }

View File

@ -3,7 +3,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<script> <script>
var form_vars = {'perm_preset': ['can_moderate','can_post','read_only','no_access','default','custom']}; var formVars = {'perm_preset': ['can_moderate','can_post','read_only','no_access','default','custom']};
</script> </script>
<main class="colstack_right"> <main class="colstack_right">

View File

@ -35,6 +35,9 @@
<div class="rowitem passive submenu"> <div class="rowitem passive submenu">
<a href="/panel/analytics/topics/">Topics</a> <a href="/panel/analytics/topics/">Topics</a>
</div> </div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/forums/">Forums</a>
</div>
<div class="rowitem passive submenu"> <div class="rowitem passive submenu">
<a href="/panel/analytics/routes/">Routes</a> <a href="/panel/analytics/routes/">Routes</a>
</div> </div>

View File

@ -8,6 +8,7 @@
<a>{{.FriendlyAgent}} Views</a> <a>{{.FriendlyAgent}} Views</a>
<select class="timeRangeSelector to_right" name="timeRange"> <select class="timeRangeSelector to_right" name="timeRange">
<option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option> <option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option>
<option val="one-week"{{if eq .TimeRange "one-week"}} selected{{end}}>1 week</option>
<option val="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>2 days</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="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="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>12 hours</option>
@ -17,35 +18,17 @@
</div> </div>
</form> </form>
<div id="panel_analytics_views" class="colstack_graph_holder"> <div id="panel_analytics_views" class="colstack_graph_holder">
<div class="ct-chart"></div> <div class="ct_chart"></div>
</div> </div>
</main> </main>
</div> </div>
<script> <script>
let labels = [];
let rawLabels = [{{range .PrimaryGraph.Labels}} let rawLabels = [{{range .PrimaryGraph.Labels}}
{{.}},{{end}} {{.}},{{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}} let seriesData = [{{range .PrimaryGraph.Series}}
{{.}},{{end}} {{.}},{{end}}
]; ];
seriesData = seriesData.reverse(); buildStatsChart(rawLabels, seriesData, "{{.TimeRange}}");
Chartist.Line('.ct-chart', {
labels: labels,
series: [seriesData],
}, {
height: '250px',
});
</script> </script>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -8,6 +8,7 @@
<a>User Agents</a> <a>User Agents</a>
<select class="timeRangeSelector to_right" name="timeRange"> <select class="timeRangeSelector to_right" name="timeRange">
<option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option> <option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option>
<option val="one-week"{{if eq .TimeRange "one-week"}} selected{{end}}>1 week</option>
<option val="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>2 days</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="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="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>12 hours</option>

View File

@ -0,0 +1,34 @@
{{template "header.html" . }}
<div class="colstack panel_stack">
{{template "panel-menu.html" . }}
<main id="panel_analytics_right" class="colstack_right">
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/forum/{{.Agent}}" method="get">
<div class="colstack_item colstack_head">
<div class="rowitem">
<a>{{.FriendlyAgent}} 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="one-week"{{if eq .TimeRange "one-week"}} selected{{end}}>1 week</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_views" class="colstack_graph_holder">
<div class="ct_chart"></div>
</div>
</main>
</div>
<script>
let rawLabels = [{{range .PrimaryGraph.Labels}}
{{.}},{{end}}
];
let seriesData = [{{range .PrimaryGraph.Series}}
{{.}},{{end}}
];
buildStatsChart(rawLabels, seriesData, "{{.TimeRange}}");
</script>
{{template "footer.html" . }}

View File

@ -0,0 +1,30 @@
{{template "header.html" . }}
<div class="colstack panel_stack">
{{template "panel-menu.html" . }}
<main id="panel_analytics_right" class="colstack_right">
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/forums/" method="get">
<div class="colstack_item colstack_head">
<div class="rowitem">
<a>Forums</a>
<select class="timeRangeSelector to_right" name="timeRange">
<option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option>
<option val="one-week"{{if eq .TimeRange "one-week"}} selected{{end}}>1 week</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_routes" class="colstack_item rowlist">
{{range .ItemList}}
<div class="rowitem panel_compactrow editable_parent">
<a href="/panel/analytics/forum/{{.Agent}}" class="panel_upshift">{{.FriendlyAgent}}</a>
<span class="panel_compacttext to_right">{{.Count}} views</span>
</div>
{{else}}<div class="rowitem passive rowmsg">No forum view counts could be found in the selected time range</div>{{end}}
</div>
</main>
</div>
{{template "footer.html" . }}

View File

@ -8,6 +8,7 @@
<a>Post Counts</a> <a>Post Counts</a>
<select class="timeRangeSelector to_right" name="timeRange"> <select class="timeRangeSelector to_right" name="timeRange">
<option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option> <option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option>
<option val="one-week"{{if eq .TimeRange "one-week"}} selected{{end}}>1 week</option>
<option val="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>2 days</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="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="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>12 hours</option>
@ -33,30 +34,12 @@
</main> </main>
</div> </div>
<script> <script>
let labels = [];
let rawLabels = [{{range .PrimaryGraph.Labels}} let rawLabels = [{{range .PrimaryGraph.Labels}}
{{.}},{{end}} {{.}},{{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}} let seriesData = [{{range .PrimaryGraph.Series}}
{{.}},{{end}} {{.}},{{end}}
]; ];
seriesData = seriesData.reverse(); buildStatsChart(rawLabels, seriesData, "{{.TimeRange}}");
Chartist.Line('.ct_chart', {
labels: labels,
series: [seriesData],
}, {
height: '250px',
});
</script> </script>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -8,6 +8,7 @@
<a>{{.Agent}} Views</a> <a>{{.Agent}} Views</a>
<select class="timeRangeSelector to_right" name="timeRange"> <select class="timeRangeSelector to_right" name="timeRange">
<option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option> <option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option>
<option val="one-week"{{if eq .TimeRange "one-week"}} selected{{end}}>1 week</option>
<option val="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>2 days</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="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="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>12 hours</option>
@ -17,35 +18,17 @@
</div> </div>
</form> </form>
<div id="panel_analytics_referrers" class="colstack_graph_holder"> <div id="panel_analytics_referrers" class="colstack_graph_holder">
<div class="ct-chart"></div> <div class="ct_chart"></div>
</div> </div>
</main> </main>
</div> </div>
<script> <script>
let labels = [];
let rawLabels = [{{range .PrimaryGraph.Labels}} let rawLabels = [{{range .PrimaryGraph.Labels}}
{{.}},{{end}} {{.}},{{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}} let seriesData = [{{range .PrimaryGraph.Series}}
{{.}},{{end}} {{.}},{{end}}
]; ];
seriesData = seriesData.reverse(); buildStatsChart(rawLabels, seriesData, "{{.TimeRange}}");
Chartist.Line('.ct-chart', {
labels: labels,
series: [seriesData],
}, {
height: '250px',
});
</script> </script>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -8,6 +8,7 @@
<a>Referrers</a> <a>Referrers</a>
<select class="timeRangeSelector to_right" name="timeRange"> <select class="timeRangeSelector to_right" name="timeRange">
<option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option> <option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option>
<option val="one-week"{{if eq .TimeRange "one-week"}} selected{{end}}>1 week</option>
<option val="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>2 days</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="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="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>12 hours</option>

View File

@ -8,6 +8,7 @@
<a>{{.Route}} Views</a> <a>{{.Route}} Views</a>
<select class="timeRangeSelector to_right" name="timeRange"> <select class="timeRangeSelector to_right" name="timeRange">
<option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option> <option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option>
<option val="one-week"{{if eq .TimeRange "one-week"}} selected{{end}}>1 week</option>
<option val="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>2 days</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="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="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>12 hours</option>
@ -17,7 +18,7 @@
</div> </div>
</form> </form>
<div id="panel_analytics_views" class="colstack_graph_holder"> <div id="panel_analytics_views" class="colstack_graph_holder">
<div class="ct-chart"></div> <div class="ct_chart"></div>
</div> </div>
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><a>Details</a></div> <div class="rowitem"><a>Details</a></div>
@ -33,30 +34,12 @@
</main> </main>
</div> </div>
<script> <script>
let labels = [];
let rawLabels = [{{range .PrimaryGraph.Labels}} let rawLabels = [{{range .PrimaryGraph.Labels}}
{{.}},{{end}} {{.}},{{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}} let seriesData = [{{range .PrimaryGraph.Series}}
{{.}},{{end}} {{.}},{{end}}
]; ];
seriesData = seriesData.reverse(); buildStatsChart(rawLabels, seriesData, "{{.TimeRange}}");
Chartist.Line('.ct-chart', {
labels: labels,
series: [seriesData],
}, {
height: '250px',
});
</script> </script>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -8,6 +8,7 @@
<a>Routes</a> <a>Routes</a>
<select class="timeRangeSelector to_right" name="timeRange"> <select class="timeRangeSelector to_right" name="timeRange">
<option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option> <option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option>
<option val="one-week"{{if eq .TimeRange "one-week"}} selected{{end}}>1 week</option>
<option val="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>2 days</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="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="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>12 hours</option>

View File

@ -8,6 +8,7 @@
<a>{{.FriendlyAgent}} Views</a> <a>{{.FriendlyAgent}} Views</a>
<select class="timeRangeSelector to_right" name="timeRange"> <select class="timeRangeSelector to_right" name="timeRange">
<option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option> <option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option>
<option val="one-week"{{if eq .TimeRange "one-week"}} selected{{end}}>1 week</option>
<option val="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>2 days</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="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="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>12 hours</option>
@ -17,35 +18,17 @@
</div> </div>
</form> </form>
<div id="panel_analytics_systems" class="colstack_graph_holder"> <div id="panel_analytics_systems" class="colstack_graph_holder">
<div class="ct-chart"></div> <div class="ct_chart"></div>
</div> </div>
</main> </main>
</div> </div>
<script> <script>
let labels = [];
let rawLabels = [{{range .PrimaryGraph.Labels}} let rawLabels = [{{range .PrimaryGraph.Labels}}
{{.}},{{end}} {{.}},{{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}} let seriesData = [{{range .PrimaryGraph.Series}}
{{.}},{{end}} {{.}},{{end}}
]; ];
seriesData = seriesData.reverse(); buildStatsChart(rawLabels, seriesData, "{{.TimeRange}}");
Chartist.Line('.ct-chart', {
labels: labels,
series: [seriesData],
}, {
height: '250px',
});
</script> </script>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -8,6 +8,7 @@
<a>Operating Systems</a> <a>Operating Systems</a>
<select class="timeRangeSelector to_right" name="timeRange"> <select class="timeRangeSelector to_right" name="timeRange">
<option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option> <option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option>
<option val="one-week"{{if eq .TimeRange "one-week"}} selected{{end}}>1 week</option>
<option val="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>2 days</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="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="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>12 hours</option>

View File

@ -8,6 +8,7 @@
<a>Topic Counts</a> <a>Topic Counts</a>
<select class="timeRangeSelector to_right" name="timeRange"> <select class="timeRangeSelector to_right" name="timeRange">
<option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option> <option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option>
<option val="one-week"{{if eq .TimeRange "one-week"}} selected{{end}}>1 week</option>
<option val="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>2 days</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="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="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>12 hours</option>
@ -33,30 +34,12 @@
</main> </main>
</div> </div>
<script> <script>
let labels = [];
let rawLabels = [{{range .PrimaryGraph.Labels}} let rawLabels = [{{range .PrimaryGraph.Labels}}
{{.}},{{end}} {{.}},{{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}} let seriesData = [{{range .PrimaryGraph.Series}}
{{.}},{{end}} {{.}},{{end}}
]; ];
seriesData = seriesData.reverse(); buildStatsChart(rawLabels, seriesData, "{{.TimeRange}}");
Chartist.Line('.ct_chart', {
labels: labels,
series: [seriesData],
}, {
height: '250px',
});
</script> </script>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -5,9 +5,10 @@
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/views/" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/views/" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">
<a>Views</a> <a>Requests</a>
<select class="timeRangeSelector to_right" name="timeRange"> <select class="timeRangeSelector to_right" name="timeRange">
<option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option> <option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option>
<option val="one-week"{{if eq .TimeRange "one-week"}} selected{{end}}>1 week</option>
<option val="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>2 days</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="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="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>12 hours</option>
@ -33,30 +34,12 @@
</main> </main>
</div> </div>
<script> <script>
let labels = [];
let rawLabels = [{{range .PrimaryGraph.Labels}} let rawLabels = [{{range .PrimaryGraph.Labels}}
{{.}},{{end}} {{.}},{{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}} let seriesData = [{{range .PrimaryGraph.Series}}
{{.}},{{end}} {{.}},{{end}}
]; ];
seriesData = seriesData.reverse(); buildStatsChart(rawLabels, seriesData, "{{.TimeRange}}");
Chartist.Line('.ct_chart', {
labels: labels,
series: [seriesData],
}, {
height: '250px',
});
</script> </script>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -2,7 +2,7 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<script>var form_vars = { <script>var formVars = {
'forum_active': ['Hide','Show'], 'forum_active': ['Hide','Show'],
'forum_preset': ['all','announce','members','staff','admins','archive','custom']}; 'forum_preset': ['all','announce','members','staff','admins','archive','custom']};
</script> </script>

View File

@ -196,6 +196,30 @@
.ct-point:hover { .ct-point:hover {
stroke: hsl(359,98%,30%) !important; stroke: hsl(359,98%,30%) !important;
} }
/*.ct-chart-line {
height: 300px !important;
}*/
/*.ct-label.ct-horizontal {
position: fixed;
justify-content: flex-end;
text-align: right;
transform-origin: 100% 0;
transform: translate(-100%) rotate(-90deg);
white-space: nowrap;
/*padding-right: 48px;/
top: 8px;
}*/
/*.ct-label.ct-horizontal {
white-space: nowrap;
}*/
/*.ct-label.ct-horizontal {
position: fixed;
justify-content: flex-end;
text-align: right;
transform-origin: 100% 0;
transform: translate(-100%) rotate(-45deg);
white-space: nowrap;
}*/
.timeRangeSelector { .timeRangeSelector {
margin-top: -5px; margin-top: -5px;
} }