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:
parent
60964868d4
commit
0879f56893
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
358
gen_router.go
358
gen_router.go
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
4
main.go
4
main.go
|
@ -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
|
||||||
|
|
286
panel_routes.go
286
panel_routes.go
|
@ -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 {
|
||||||
|
|
|
@ -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',
|
||||||
|
});
|
||||||
|
}
|
|
@ -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");
|
||||||
});
|
});
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
96
routes.go
96
routes.go
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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" . }}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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" . }}
|
|
@ -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" . }}
|
|
@ -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" . }}
|
||||||
|
|
|
@ -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" . }}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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" . }}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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" . }}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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" . }}
|
||||||
|
|
|
@ -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" . }}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue