1276 lines
36 KiB
Go
1276 lines
36 KiB
Go
package panel
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"log"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
c "git.tuxpa.in/a/gosora/common"
|
|
p "git.tuxpa.in/a/gosora/common/phrases"
|
|
qgen "git.tuxpa.in/a/gosora/query_gen"
|
|
)
|
|
|
|
func analyticsTimeRange(rawTimeRange string) (*c.AnalyticsTimeRange, error) {
|
|
tr := &c.AnalyticsTimeRange{
|
|
Quantity: 6,
|
|
Unit: "hour",
|
|
Slices: 12,
|
|
SliceWidth: 60 * 30,
|
|
Range: "six-hours",
|
|
}
|
|
|
|
switch rawTimeRange {
|
|
// This might be pushing it, we might want to come up with a more efficient scheme for dealing with large timeframes like this
|
|
case "one-year":
|
|
tr.Quantity = 12
|
|
tr.Unit = "month"
|
|
tr.Slices = 12
|
|
tr.SliceWidth = 60 * 60 * 24 * 30
|
|
case "three-months":
|
|
tr.Quantity = 90
|
|
tr.Unit = "day"
|
|
tr.Slices = 30
|
|
tr.SliceWidth = 60 * 60 * 24 * 3
|
|
case "one-month":
|
|
tr.Quantity = 30
|
|
tr.Unit = "day"
|
|
tr.Slices = 30
|
|
tr.SliceWidth = 60 * 60 * 24
|
|
case "one-week":
|
|
tr.Quantity = 7
|
|
tr.Unit = "day"
|
|
tr.Slices = 14
|
|
tr.SliceWidth = 60 * 60 * 12
|
|
case "two-days": // Two days is experimental
|
|
tr.Quantity = 2
|
|
tr.Unit = "day"
|
|
tr.Slices = 24
|
|
tr.SliceWidth = 60 * 60 * 2
|
|
case "one-day":
|
|
tr.Quantity = 1
|
|
tr.Unit = "day"
|
|
tr.Slices = 24
|
|
tr.SliceWidth = 60 * 60
|
|
case "twelve-hours":
|
|
tr.Quantity = 12
|
|
tr.Slices = 24
|
|
case "six-hours", "":
|
|
return tr, nil
|
|
default:
|
|
return tr, errors.New("Unknown time range")
|
|
}
|
|
tr.Range = rawTimeRange
|
|
return tr, nil
|
|
}
|
|
|
|
type pAvg struct {
|
|
Avg int64
|
|
Tot int64
|
|
}
|
|
|
|
func analyticsRowsToAverageMap(rows *sql.Rows, labelList []int64, avgMap map[int64]int64) (map[int64]int64, error) {
|
|
defer rows.Close()
|
|
for rows.Next() {
|
|
var count int64
|
|
var createdAt time.Time
|
|
e := rows.Scan(&count, &createdAt)
|
|
if e != nil {
|
|
return avgMap, e
|
|
}
|
|
unixCreatedAt := createdAt.Unix()
|
|
// TODO: Bulk log this
|
|
if c.Dev.SuperDebug {
|
|
log.Print("count: ", count)
|
|
log.Print("createdAt: ", createdAt, " - ", unixCreatedAt)
|
|
}
|
|
pAvgMap := make(map[int64]pAvg)
|
|
for _, value := range labelList {
|
|
if unixCreatedAt > value {
|
|
prev := pAvgMap[value]
|
|
prev.Avg += count
|
|
prev.Tot++
|
|
pAvgMap[value] = prev
|
|
break
|
|
}
|
|
}
|
|
for key, pAvg := range pAvgMap {
|
|
avgMap[key] = pAvg.Avg / pAvg.Tot
|
|
}
|
|
}
|
|
return avgMap, rows.Err()
|
|
}
|
|
|
|
func analyticsRowsToAverageMap2(rows *sql.Rows, labelList []int64, avgMap map[int64]int64, typ int) (map[int64]int64, error) {
|
|
defer rows.Close()
|
|
for rows.Next() {
|
|
var stack, heap int64
|
|
var createdAt time.Time
|
|
e := rows.Scan(&stack, &heap, &createdAt)
|
|
if e != nil {
|
|
return avgMap, e
|
|
}
|
|
unixCreatedAt := createdAt.Unix()
|
|
// TODO: Bulk log this
|
|
if c.Dev.SuperDebug {
|
|
log.Print("stack: ", stack)
|
|
log.Print("heap: ", heap)
|
|
log.Print("createdAt: ", createdAt, " - ", unixCreatedAt)
|
|
}
|
|
if typ == 1 {
|
|
heap = 0
|
|
} else if typ == 2 {
|
|
stack = 0
|
|
}
|
|
pAvgMap := make(map[int64]pAvg)
|
|
for _, value := range labelList {
|
|
if unixCreatedAt > value {
|
|
prev := pAvgMap[value]
|
|
prev.Avg += stack + heap
|
|
prev.Tot++
|
|
pAvgMap[value] = prev
|
|
break
|
|
}
|
|
}
|
|
for key, pAvg := range pAvgMap {
|
|
avgMap[key] = pAvg.Avg / pAvg.Tot
|
|
}
|
|
}
|
|
return avgMap, rows.Err()
|
|
}
|
|
|
|
func analyticsRowsToAverageMap3(rows *sql.Rows, labelList []int64, avgMap map[int64]int64, typ int) (map[int64]int64, error) {
|
|
defer rows.Close()
|
|
for rows.Next() {
|
|
var low, high, avg int64
|
|
var createdAt time.Time
|
|
e := rows.Scan(&low, &high, &avg, &createdAt)
|
|
if e != nil {
|
|
return avgMap, e
|
|
}
|
|
unixCreatedAt := createdAt.Unix()
|
|
// TODO: Bulk log this
|
|
if c.Dev.SuperDebug {
|
|
log.Print("low: ", low)
|
|
log.Print("high: ", high)
|
|
log.Print("avg: ", avg)
|
|
log.Print("createdAt: ", createdAt, " - ", unixCreatedAt)
|
|
}
|
|
var dat int64
|
|
switch typ {
|
|
case 0:
|
|
dat = low
|
|
case 1:
|
|
dat = high
|
|
default:
|
|
dat = avg
|
|
}
|
|
pAvgMap := make(map[int64]pAvg)
|
|
for _, val := range labelList {
|
|
if unixCreatedAt > val {
|
|
prev := pAvgMap[val]
|
|
prev.Avg += dat
|
|
prev.Tot++
|
|
pAvgMap[val] = prev
|
|
break
|
|
}
|
|
}
|
|
for key, pAvg := range pAvgMap {
|
|
avgMap[key] = pAvg.Avg / pAvg.Tot
|
|
}
|
|
}
|
|
return avgMap, rows.Err()
|
|
}
|
|
|
|
func PreAnalyticsDetail(w http.ResponseWriter, r *http.Request, u *c.User) (*c.BasePanelPage, c.RouteError) {
|
|
bp, fe := buildBasePage(w, r, u, "analytics", "analytics")
|
|
if fe != nil {
|
|
return nil, fe
|
|
}
|
|
bp.AddSheet("chartist/chartist.min.css")
|
|
bp.AddScript("chartist/chartist.min.js")
|
|
bp.AddScriptAsync("analytics.js")
|
|
bp.LooseCSP = true
|
|
return bp, nil
|
|
}
|
|
|
|
func createTimeGraph(series [][]int64, labelList []int64, legends ...[]string) c.PanelTimeGraph {
|
|
var llegends []string
|
|
if len(legends) > 0 {
|
|
llegends = legends[0]
|
|
}
|
|
graph := c.PanelTimeGraph{Series: series, Labels: labelList, Legends: llegends}
|
|
c.DebugLogf("graph: %+v\n", graph)
|
|
return graph
|
|
}
|
|
|
|
func CreateViewListItems(revLabelList []int64, viewMap map[int64]int64) ([]int64, []c.PanelAnalyticsItem) {
|
|
viewList := make([]int64, len(revLabelList))
|
|
viewItems := make([]c.PanelAnalyticsItem, len(revLabelList))
|
|
for i, val := range revLabelList {
|
|
viewList[i] = viewMap[val]
|
|
viewItems[i] = c.PanelAnalyticsItem{Time: val, Count: viewMap[val]}
|
|
}
|
|
return viewList, viewItems
|
|
}
|
|
|
|
func AnalyticsViews(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
|
|
bp, fe := PreAnalyticsDetail(w, r, u)
|
|
if fe != nil {
|
|
return fe
|
|
}
|
|
tr, e := analyticsTimeRange(r.FormValue("timeRange"))
|
|
if e != nil {
|
|
return c.LocalError(e.Error(), w, r, u)
|
|
}
|
|
revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
|
|
|
|
c.DebugLog("in panel.AnalyticsViews")
|
|
// TODO: Add some sort of analytics store / iterator?
|
|
viewMap, e = c.Analytics.FillViewMap("viewchunks", tr, labelList, viewMap, "route", "")
|
|
if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
viewList, viewItems := CreateViewListItems(revLabelList, viewMap)
|
|
|
|
graph := createTimeGraph([][]int64{viewList}, labelList)
|
|
var ttime string
|
|
if tr.Range == "six-hours" || tr.Range == "twelve-hours" || tr.Range == "one-day" {
|
|
ttime = "time"
|
|
}
|
|
|
|
pi := c.PanelAnalyticsStd{graph, viewItems, tr.Range, tr.Unit, ttime}
|
|
return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_views", pi})
|
|
}
|
|
|
|
func AnalyticsRouteViews(w http.ResponseWriter, r *http.Request, u *c.User, route string) c.RouteError {
|
|
bp, fe := PreAnalyticsDetail(w, r, u)
|
|
if fe != nil {
|
|
return fe
|
|
}
|
|
tr, e := analyticsTimeRange(r.FormValue("timeRange"))
|
|
if e != nil {
|
|
return c.LocalError(e.Error(), w, r, u)
|
|
}
|
|
revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
|
|
|
|
c.DebugLog("in panel.AnalyticsRouteViews")
|
|
// TODO: Validate the route is valid
|
|
viewMap, e = c.Analytics.FillViewMap("viewchunks", tr, labelList, viewMap, "route", route)
|
|
if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
viewList, viewItems := CreateViewListItems(revLabelList, viewMap)
|
|
graph := createTimeGraph([][]int64{viewList}, labelList)
|
|
|
|
pi := c.PanelAnalyticsRoutePage{bp, c.SanitiseSingleLine(route), graph, viewItems, tr.Range}
|
|
return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_route_views", pi})
|
|
}
|
|
|
|
func AnalyticsAgentViews(w http.ResponseWriter, r *http.Request, u *c.User, agent string) c.RouteError {
|
|
bp, ferr := PreAnalyticsDetail(w, r, u)
|
|
if ferr != nil {
|
|
return ferr
|
|
}
|
|
tr, e := analyticsTimeRange(r.FormValue("timeRange"))
|
|
if e != nil {
|
|
return c.LocalError(e.Error(), w, r, u)
|
|
}
|
|
revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
|
|
// ? Only allow valid agents? The problem with this is that agents wind up getting renamed and it would take a migration to get them all up to snuff
|
|
agent = c.SanitiseSingleLine(agent)
|
|
|
|
c.DebugLog("in panel.AnalyticsAgentViews")
|
|
// TODO: Verify the agent is valid
|
|
viewMap, e = c.Analytics.FillViewMap("viewchunks_agents", tr, labelList, viewMap, "browser", agent)
|
|
if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
viewList := CreateViewList(revLabelList, viewMap)
|
|
graph := createTimeGraph([][]int64{viewList}, labelList)
|
|
|
|
friendlyAgent, ok := p.GetUserAgentPhrase(agent)
|
|
if !ok {
|
|
friendlyAgent = agent
|
|
}
|
|
|
|
pi := c.PanelAnalyticsAgentPage{bp, agent, friendlyAgent, graph, tr.Range}
|
|
return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_agent_views", pi})
|
|
}
|
|
|
|
func AnalyticsForumViews(w http.ResponseWriter, r *http.Request, u *c.User, sfid string) c.RouteError {
|
|
bp, ferr := PreAnalyticsDetail(w, r, u)
|
|
if ferr != nil {
|
|
return ferr
|
|
}
|
|
tr, e := analyticsTimeRange(r.FormValue("timeRange"))
|
|
if e != nil {
|
|
return c.LocalError(e.Error(), w, r, u)
|
|
}
|
|
revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
|
|
|
|
fid, e := strconv.Atoi(sfid)
|
|
if e != nil {
|
|
return c.LocalError("Invalid integer", w, r, u)
|
|
}
|
|
|
|
c.DebugLog("in panel.AnalyticsForumViews")
|
|
// TODO: Verify the agent is valid
|
|
viewMap, e = c.Analytics.FillViewMap("viewchunks_forums", tr, labelList, viewMap, "forum", fid)
|
|
if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
viewList := CreateViewList(revLabelList, viewMap)
|
|
graph := createTimeGraph([][]int64{viewList}, labelList)
|
|
|
|
forum, e := c.Forums.Get(fid)
|
|
if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
|
|
pi := c.PanelAnalyticsAgentPage{bp, sfid, forum.Name, graph, tr.Range}
|
|
return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_forum_views", pi})
|
|
}
|
|
|
|
func AnalyticsSystemViews(w http.ResponseWriter, r *http.Request, u *c.User, system string) c.RouteError {
|
|
bp, ferr := PreAnalyticsDetail(w, r, u)
|
|
if ferr != nil {
|
|
return ferr
|
|
}
|
|
tr, e := analyticsTimeRange(r.FormValue("timeRange"))
|
|
if e != nil {
|
|
return c.LocalError(e.Error(), w, r, u)
|
|
}
|
|
revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
|
|
system = c.SanitiseSingleLine(system)
|
|
|
|
c.DebugLog("in panel.AnalyticsSystemViews")
|
|
// TODO: Verify the OS name is valid
|
|
viewMap, e = c.Analytics.FillViewMap("viewchunks_systems", tr, labelList, viewMap, "system", system)
|
|
if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
viewList := CreateViewList(revLabelList, viewMap)
|
|
graph := createTimeGraph([][]int64{viewList}, labelList)
|
|
|
|
friendlySystem, ok := p.GetOSPhrase(system)
|
|
if !ok {
|
|
friendlySystem = system
|
|
}
|
|
|
|
pi := c.PanelAnalyticsAgentPage{bp, system, friendlySystem, graph, tr.Range}
|
|
return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_system_views", pi})
|
|
}
|
|
|
|
func CreateViewList(revLabelList []int64, viewMap map[int64]int64) []int64 {
|
|
viewList := make([]int64, len(revLabelList))
|
|
for i, val := range revLabelList {
|
|
viewList[i] = viewMap[val]
|
|
}
|
|
return viewList
|
|
}
|
|
|
|
func AnalyticsLanguageViews(w http.ResponseWriter, r *http.Request, u *c.User, lang string) c.RouteError {
|
|
bp, ferr := PreAnalyticsDetail(w, r, u)
|
|
if ferr != nil {
|
|
return ferr
|
|
}
|
|
tr, e := analyticsTimeRange(r.FormValue("timeRange"))
|
|
if e != nil {
|
|
return c.LocalError(e.Error(), w, r, u)
|
|
}
|
|
revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
|
|
lang = c.SanitiseSingleLine(lang)
|
|
|
|
c.DebugLog("in panel.AnalyticsLanguageViews")
|
|
// TODO: Verify the language code is valid
|
|
viewMap, e = c.Analytics.FillViewMap("viewchunks_langs", tr, labelList, viewMap, "lang", lang)
|
|
if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
viewList := CreateViewList(revLabelList, viewMap)
|
|
graph := createTimeGraph([][]int64{viewList}, labelList)
|
|
|
|
friendlyLang, ok := p.GetHumanLangPhrase(lang)
|
|
if !ok {
|
|
friendlyLang = lang
|
|
}
|
|
|
|
pi := c.PanelAnalyticsAgentPage{bp, lang, friendlyLang, graph, tr.Range}
|
|
return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_lang_views", pi})
|
|
}
|
|
|
|
func AnalyticsReferrerViews(w http.ResponseWriter, r *http.Request, u *c.User, domain string) c.RouteError {
|
|
bp, ferr := PreAnalyticsDetail(w, r, u)
|
|
if ferr != nil {
|
|
return ferr
|
|
}
|
|
tr, e := analyticsTimeRange(r.FormValue("timeRange"))
|
|
if e != nil {
|
|
return c.LocalError(e.Error(), w, r, u)
|
|
}
|
|
revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
|
|
|
|
c.DebugLog("in panel.AnalyticsReferrerViews")
|
|
// TODO: Verify the agent is valid
|
|
viewMap, e = c.Analytics.FillViewMap("viewchunks_referrers", tr, labelList, viewMap, "domain", domain)
|
|
if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
viewList := CreateViewList(revLabelList, viewMap)
|
|
graph := createTimeGraph([][]int64{viewList}, labelList)
|
|
|
|
pi := c.PanelAnalyticsAgentPage{bp, c.SanitiseSingleLine(domain), "", graph, tr.Range}
|
|
return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_referrer_views", pi})
|
|
}
|
|
|
|
func AnalyticsTopics(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
|
|
bp, ferr := PreAnalyticsDetail(w, r, u)
|
|
if ferr != nil {
|
|
return ferr
|
|
}
|
|
tr, e := analyticsTimeRange(r.FormValue("timeRange"))
|
|
if e != nil {
|
|
return c.LocalError(e.Error(), w, r, u)
|
|
}
|
|
revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
|
|
|
|
c.DebugLog("in panel.AnalyticsTopics")
|
|
viewMap, e = c.Analytics.FillViewMap("topicchunks", tr, labelList, viewMap, "")
|
|
if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
viewList, viewItems := CreateViewListItems(revLabelList, viewMap)
|
|
graph := createTimeGraph([][]int64{viewList}, labelList)
|
|
|
|
pi := c.PanelAnalyticsStd{graph, viewItems, tr.Range, tr.Unit, "time"}
|
|
return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_topics", pi})
|
|
}
|
|
|
|
func AnalyticsPosts(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
|
|
bp, fe := PreAnalyticsDetail(w, r, u)
|
|
if fe != nil {
|
|
return fe
|
|
}
|
|
tr, e := analyticsTimeRange(r.FormValue("timeRange"))
|
|
if e != nil {
|
|
return c.LocalError(e.Error(), w, r, u)
|
|
}
|
|
revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
|
|
|
|
c.DebugLog("in panel.AnalyticsPosts")
|
|
viewMap, e = c.Analytics.FillViewMap("postchunks", tr, labelList, viewMap, "")
|
|
if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
viewList, viewItems := CreateViewListItems(revLabelList, viewMap)
|
|
graph := createTimeGraph([][]int64{viewList}, labelList)
|
|
|
|
pi := c.PanelAnalyticsStd{graph, viewItems, tr.Range, tr.Unit, "time"}
|
|
return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_posts", pi})
|
|
}
|
|
|
|
func AnalyticsMemory(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
|
|
bp, fe := PreAnalyticsDetail(w, r, u)
|
|
if fe != nil {
|
|
return fe
|
|
}
|
|
tr, e := analyticsTimeRange(r.FormValue("timeRange"))
|
|
if e != nil {
|
|
return c.LocalError(e.Error(), w, r, u)
|
|
}
|
|
revLabelList, labelList, avgMap := c.AnalyticsTimeRangeToLabelList(tr)
|
|
|
|
c.DebugLog("in panel.AnalyticsMemory")
|
|
rows, e := qgen.NewAcc().Select("memchunks").Columns("count,createdAt").DateCutoff("createdAt", tr.Quantity, tr.Unit).Query()
|
|
if e != nil && e != sql.ErrNoRows {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
avgMap, e = analyticsRowsToAverageMap(rows, labelList, avgMap)
|
|
if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
|
|
// TODO: Adjust for the missing chunks in week and month
|
|
avgList := make([]int64, len(revLabelList))
|
|
avgItems := make([]c.PanelAnalyticsItemUnit, len(revLabelList))
|
|
for i, value := range revLabelList {
|
|
avgList[i] = avgMap[value]
|
|
cv, cu := c.ConvertByteUnit(float64(avgMap[value]))
|
|
avgItems[i] = c.PanelAnalyticsItemUnit{Time: value, Unit: cu, Count: int64(cv)}
|
|
}
|
|
graph := createTimeGraph([][]int64{avgList}, labelList)
|
|
|
|
pi := c.PanelAnalyticsStdUnit{graph, avgItems, tr.Range, tr.Unit, "time"}
|
|
return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_memory", pi})
|
|
}
|
|
|
|
// TODO: Show stack and heap memory separately on the chart
|
|
func AnalyticsActiveMemory(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
|
|
bp, ferr := PreAnalyticsDetail(w, r, u)
|
|
if ferr != nil {
|
|
return ferr
|
|
}
|
|
tr, e := analyticsTimeRange(r.FormValue("timeRange"))
|
|
if e != nil {
|
|
return c.LocalError(e.Error(), w, r, u)
|
|
}
|
|
revLabelList, labelList, avgMap := c.AnalyticsTimeRangeToLabelList(tr)
|
|
|
|
c.DebugLog("in panel.AnalyticsActiveMemory")
|
|
rows, e := qgen.NewAcc().Select("memchunks").Columns("stack,heap,createdAt").DateCutoff("createdAt", tr.Quantity, tr.Unit).Query()
|
|
if e != nil && e != sql.ErrNoRows {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
|
|
var typ int
|
|
switch r.FormValue("mtype") {
|
|
case "1":
|
|
typ = 1
|
|
case "2":
|
|
typ = 2
|
|
default:
|
|
typ = 0
|
|
}
|
|
avgMap, e = analyticsRowsToAverageMap2(rows, labelList, avgMap, typ)
|
|
if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
|
|
// TODO: Adjust for the missing chunks in week and month
|
|
avgList := make([]int64, len(revLabelList))
|
|
avgItems := make([]c.PanelAnalyticsItemUnit, len(revLabelList))
|
|
for i, value := range revLabelList {
|
|
avgList[i] = avgMap[value]
|
|
cv, cu := c.ConvertByteUnit(float64(avgMap[value]))
|
|
avgItems[i] = c.PanelAnalyticsItemUnit{Time: value, Unit: cu, Count: int64(cv)}
|
|
}
|
|
graph := createTimeGraph([][]int64{avgList}, labelList)
|
|
|
|
pi := c.PanelAnalyticsActiveMemory{graph, avgItems, tr.Range, tr.Unit, "time", typ}
|
|
return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_active_memory", pi})
|
|
}
|
|
|
|
func AnalyticsPerf(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
|
|
bp, ferr := PreAnalyticsDetail(w, r, u)
|
|
if ferr != nil {
|
|
return ferr
|
|
}
|
|
tr, e := analyticsTimeRange(r.FormValue("timeRange"))
|
|
if e != nil {
|
|
return c.LocalError(e.Error(), w, r, u)
|
|
}
|
|
revLabelList, labelList, avgMap := c.AnalyticsTimeRangeToLabelList(tr)
|
|
|
|
c.DebugLog("in panel.AnalyticsPerf")
|
|
rows, e := qgen.NewAcc().Select("perfchunks").Columns("low,high,avg,createdAt").DateCutoff("createdAt", tr.Quantity, tr.Unit).Query()
|
|
if e != nil && e != sql.ErrNoRows {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
|
|
var typ int
|
|
switch r.FormValue("type") {
|
|
case "0":
|
|
typ = 0
|
|
case "1":
|
|
typ = 1
|
|
default:
|
|
typ = 2
|
|
}
|
|
avgMap, e = analyticsRowsToAverageMap3(rows, labelList, avgMap, typ)
|
|
if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
|
|
// TODO: Adjust for the missing chunks in week and month
|
|
avgList := make([]int64, len(revLabelList))
|
|
avgItems := make([]c.PanelAnalyticsItemUnit, len(revLabelList))
|
|
for i, value := range revLabelList {
|
|
avgList[i] = avgMap[value]
|
|
cv, cu := c.ConvertPerfUnit(float64(avgMap[value]))
|
|
avgItems[i] = c.PanelAnalyticsItemUnit{Time: value, Unit: cu, Count: int64(cv)}
|
|
}
|
|
graph := createTimeGraph([][]int64{avgList}, labelList)
|
|
|
|
pi := c.PanelAnalyticsPerf{graph, avgItems, tr.Range, tr.Unit, "time", typ}
|
|
return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_performance", pi})
|
|
}
|
|
|
|
func analyticsRowsToAvgDuoMap(rows *sql.Rows, labelList []int64, avgMap map[int64]int64) (map[string]map[int64]int64, map[string]int, error) {
|
|
aMap := make(map[string]map[int64]int64)
|
|
nameMap := make(map[string]int)
|
|
defer rows.Close()
|
|
for rows.Next() {
|
|
var count int64
|
|
var name string
|
|
var createdAt time.Time
|
|
e := rows.Scan(&count, &name, &createdAt)
|
|
if e != nil {
|
|
return aMap, nameMap, e
|
|
}
|
|
|
|
// TODO: Bulk log this
|
|
unixCreatedAt := createdAt.Unix()
|
|
if c.Dev.SuperDebug {
|
|
log.Print("count: ", count)
|
|
log.Print("name: ", name)
|
|
log.Print("createdAt: ", createdAt, " - ", unixCreatedAt)
|
|
}
|
|
|
|
vvMap, ok := aMap[name]
|
|
if !ok {
|
|
vvMap = make(map[int64]int64)
|
|
for key, val := range avgMap {
|
|
vvMap[key] = val
|
|
}
|
|
aMap[name] = vvMap
|
|
}
|
|
for _, value := range labelList {
|
|
if unixCreatedAt > value {
|
|
vvMap[value] = (vvMap[value] + count) / 2
|
|
break
|
|
}
|
|
}
|
|
nameMap[name] = (nameMap[name] + int(count)) / 2
|
|
}
|
|
return aMap, nameMap, rows.Err()
|
|
}
|
|
|
|
func sortOVList(ovList []OVItem) []OVItem {
|
|
// Use bubble sort for now as there shouldn't be too many items
|
|
for i := 0; i < len(ovList)-1; i++ {
|
|
for j := 0; j < len(ovList)-1; j++ {
|
|
if ovList[j].count > ovList[j+1].count {
|
|
temp := ovList[j]
|
|
ovList[j] = ovList[j+1]
|
|
ovList[j+1] = temp
|
|
}
|
|
}
|
|
}
|
|
|
|
// Invert the direction
|
|
tOVList := make([]OVItem, len(ovList))
|
|
for i, ii := len(ovList)-1, 0; i >= 0; i-- {
|
|
tOVList[ii] = ovList[i]
|
|
ii++
|
|
}
|
|
return tOVList
|
|
}
|
|
|
|
func analyticsAMapToOVList(aMap map[string]map[int64]int64) []OVItem {
|
|
// Order the map
|
|
ovList, i := make([]OVItem, len(aMap)), 0
|
|
for name, avgMap := range aMap {
|
|
var totcount int
|
|
for _, count := range avgMap {
|
|
totcount = (totcount + int(count)) / 2
|
|
}
|
|
ovList[i] = OVItem{name, totcount, avgMap}
|
|
i++
|
|
}
|
|
return sortOVList(ovList)
|
|
}
|
|
|
|
func AnalyticsRoutesPerf(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
|
|
bp, ferr := PreAnalyticsDetail(w, r, u)
|
|
if ferr != nil {
|
|
return ferr
|
|
}
|
|
bp.AddScript("chartist/chartist-plugin-legend.min.js")
|
|
bp.AddSheet("chartist/chartist-plugin-legend.css")
|
|
|
|
tr, e := analyticsTimeRange(r.FormValue("timeRange"))
|
|
if e != nil {
|
|
return c.LocalError(e.Error(), w, r, u)
|
|
}
|
|
// avgMap contains timestamps but not the averages for those stamps
|
|
revLabelList, labelList, avgMap := c.AnalyticsTimeRangeToLabelList(tr)
|
|
|
|
rows, e := qgen.NewAcc().Select("viewchunks").Columns("avg,route,createdAt").Where("count!=0 AND route!=''").DateCutoff("createdAt", tr.Quantity, tr.Unit).Query()
|
|
if e != nil && e != sql.ErrNoRows {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
aMap, routeMap, e := analyticsRowsToAvgDuoMap(rows, labelList, avgMap)
|
|
if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
//c.DebugLogf("aMap: %+v\n", aMap)
|
|
//c.DebugLogf("routeMap: %+v\n", routeMap)
|
|
ovList := analyticsAMapToOVList(aMap)
|
|
//c.DebugLogf("ovList: %+v\n", ovList)
|
|
|
|
ex := strings.Split(r.FormValue("ex"), ",")
|
|
inEx := func(name string) bool {
|
|
for _, e := range ex {
|
|
if e == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
var vList [][]int64
|
|
var legendList []string
|
|
var i int
|
|
for _, ovitem := range ovList {
|
|
if inEx(ovitem.name) {
|
|
continue
|
|
}
|
|
if strings.HasPrefix(ovitem.name, "panel.") {
|
|
continue
|
|
}
|
|
viewList := make([]int64, len(revLabelList))
|
|
for i, val := range revLabelList {
|
|
viewList[i] = ovitem.viewMap[val]
|
|
}
|
|
vList = append(vList, viewList)
|
|
shortName := strings.Replace(ovitem.name, "routes.", "r.", -1)
|
|
legendList = append(legendList, shortName)
|
|
if i >= 7 {
|
|
break
|
|
}
|
|
i++
|
|
}
|
|
graph := createTimeGraph(vList, labelList, legendList)
|
|
|
|
// TODO: Sort this slice
|
|
var routeItems []c.PanelAnalyticsRoutesPerfItem
|
|
for route, count := range routeMap {
|
|
if inEx(route) {
|
|
continue
|
|
}
|
|
cv, cu := c.ConvertPerfUnit(float64(count))
|
|
routeItems = append(routeItems, c.PanelAnalyticsRoutesPerfItem{
|
|
Route: route,
|
|
Unit: cu,
|
|
Count: int(cv),
|
|
})
|
|
}
|
|
|
|
pi := c.PanelAnalyticsRoutesPerfPage{bp, routeItems, graph, tr.Range}
|
|
return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_routes_perf", pi})
|
|
}
|
|
|
|
func analyticsRowsToRefMap(rows *sql.Rows) (map[string]int, error) {
|
|
nameMap := make(map[string]int)
|
|
defer rows.Close()
|
|
c.DebugDetail("name - count")
|
|
for rows.Next() {
|
|
var count int
|
|
var name string
|
|
e := rows.Scan(&count, &name)
|
|
if e != nil {
|
|
return nameMap, e
|
|
}
|
|
// TODO: Bulk log this
|
|
if c.Dev.SuperDebug {
|
|
log.Print(name, " - ", count)
|
|
}
|
|
nameMap[name] += count
|
|
}
|
|
return nameMap, rows.Err()
|
|
}
|
|
|
|
func analyticsRowsToDuoMap(rows *sql.Rows, labelList []int64, viewMap map[int64]int64) (map[string]map[int64]int64, map[string]int, error) {
|
|
vMap := make(map[string]map[int64]int64)
|
|
nameMap := make(map[string]int)
|
|
defer rows.Close()
|
|
for rows.Next() {
|
|
var count int64
|
|
var name string
|
|
var createdAt time.Time
|
|
e := rows.Scan(&count, &name, &createdAt)
|
|
if e != nil {
|
|
return vMap, nameMap, e
|
|
}
|
|
|
|
// TODO: Bulk log this
|
|
unixCreatedAt := createdAt.Unix()
|
|
if c.Dev.SuperDebug {
|
|
log.Print("count: ", count)
|
|
log.Print("name: ", name)
|
|
log.Print("createdAt: ", createdAt, " - ", unixCreatedAt)
|
|
}
|
|
|
|
vvMap, ok := vMap[name]
|
|
if !ok {
|
|
vvMap = make(map[int64]int64)
|
|
for key, val := range viewMap {
|
|
vvMap[key] = val
|
|
}
|
|
vMap[name] = vvMap
|
|
}
|
|
for _, value := range labelList {
|
|
if unixCreatedAt > value {
|
|
vvMap[value] += count
|
|
break
|
|
}
|
|
}
|
|
nameMap[name] += int(count)
|
|
}
|
|
return vMap, nameMap, rows.Err()
|
|
}
|
|
|
|
type OVItem struct {
|
|
name string
|
|
count int
|
|
viewMap map[int64]int64
|
|
}
|
|
|
|
func analyticsVMapToOVList(vMap map[string]map[int64]int64) (ovList []OVItem) {
|
|
// Order the map
|
|
ovList, i := make([]OVItem, len(vMap)), 0
|
|
for name, viewMap := range vMap {
|
|
var totcount int
|
|
for _, count := range viewMap {
|
|
totcount += int(count)
|
|
}
|
|
ovList[i] = OVItem{name, totcount, viewMap}
|
|
i++
|
|
}
|
|
return sortOVList(ovList)
|
|
}
|
|
|
|
func AnalyticsForums(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
|
|
bp, ferr := PreAnalyticsDetail(w, r, u)
|
|
if ferr != nil {
|
|
return ferr
|
|
}
|
|
bp.AddScript("chartist/chartist-plugin-legend.min.js")
|
|
bp.AddSheet("chartist/chartist-plugin-legend.css")
|
|
|
|
tr, e := analyticsTimeRange(r.FormValue("timeRange"))
|
|
if e != nil {
|
|
return c.LocalError(e.Error(), w, r, u)
|
|
}
|
|
revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
|
|
|
|
rows, e := qgen.NewAcc().Select("viewchunks_forums").Columns("count,forum,createdAt").Where("forum!=''").DateCutoff("createdAt", tr.Quantity, tr.Unit).Query()
|
|
if e != nil && e != sql.ErrNoRows {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
vMap, forumMap, e := analyticsRowsToDuoMap(rows, labelList, viewMap)
|
|
if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
ovList := analyticsVMapToOVList(vMap)
|
|
|
|
var vList [][]int64
|
|
var legendList []string
|
|
var i int
|
|
for _, ovitem := range ovList {
|
|
viewList := make([]int64, len(revLabelList))
|
|
for i, val := range revLabelList {
|
|
viewList[i] = ovitem.viewMap[val]
|
|
}
|
|
vList = append(vList, viewList)
|
|
fid, e := strconv.Atoi(ovitem.name)
|
|
if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
var lName string
|
|
forum, e := c.Forums.Get(fid)
|
|
if e == sql.ErrNoRows {
|
|
lName = "Deleted Forum" // TODO: Localise this
|
|
} else if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
} else {
|
|
lName = forum.Name
|
|
}
|
|
legendList = append(legendList, lName)
|
|
if i >= 6 {
|
|
break
|
|
}
|
|
i++
|
|
}
|
|
graph := createTimeGraph(vList, labelList, legendList)
|
|
|
|
// TODO: Sort this slice
|
|
forumItems, i := make([]c.PanelAnalyticsAgentsItem, len(forumMap)), 0
|
|
for sfid, count := range forumMap {
|
|
fid, e := strconv.Atoi(sfid)
|
|
if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
var lName string
|
|
forum, e := c.Forums.Get(fid)
|
|
if e == sql.ErrNoRows {
|
|
// TODO: Localise this
|
|
lName = "Deleted Forum"
|
|
} else if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
} else {
|
|
lName = forum.Name
|
|
}
|
|
forumItems[i] = c.PanelAnalyticsAgentsItem{
|
|
Agent: sfid,
|
|
FriendlyAgent: lName,
|
|
Count: count,
|
|
}
|
|
i++
|
|
}
|
|
|
|
pi := c.PanelAnalyticsDuoPage{bp, forumItems, graph, tr.Range}
|
|
return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_forums", pi})
|
|
}
|
|
|
|
func AnalyticsRoutes(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
|
|
bp, ferr := PreAnalyticsDetail(w, r, u)
|
|
if ferr != nil {
|
|
return ferr
|
|
}
|
|
bp.AddScript("chartist/chartist-plugin-legend.min.js")
|
|
bp.AddSheet("chartist/chartist-plugin-legend.css")
|
|
|
|
tr, e := analyticsTimeRange(r.FormValue("timeRange"))
|
|
if e != nil {
|
|
return c.LocalError(e.Error(), w, r, u)
|
|
}
|
|
revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
|
|
|
|
rows, e := qgen.NewAcc().Select("viewchunks").Columns("count,route,createdAt").Where("route!=''").DateCutoff("createdAt", tr.Quantity, tr.Unit).Query()
|
|
if e != nil && e != sql.ErrNoRows {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
vMap, routeMap, e := analyticsRowsToDuoMap(rows, labelList, viewMap)
|
|
if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
//c.DebugLogf("vMap: %+v\n", vMap)
|
|
//c.DebugLogf("routeMap: %+v\n", routeMap)
|
|
ovList := analyticsVMapToOVList(vMap)
|
|
//c.DebugLogf("ovList: %+v\n", ovList)
|
|
|
|
ex := strings.Split(r.FormValue("ex"), ",")
|
|
inEx := func(name string) bool {
|
|
for _, e := range ex {
|
|
if e == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
var vList [][]int64
|
|
var legendList []string
|
|
var i int
|
|
for _, ovitem := range ovList {
|
|
if inEx(ovitem.name) {
|
|
continue
|
|
}
|
|
viewList := make([]int64, len(revLabelList))
|
|
for i, val := range revLabelList {
|
|
viewList[i] = ovitem.viewMap[val]
|
|
}
|
|
vList = append(vList, viewList)
|
|
shortName := strings.Replace(ovitem.name, "routes.", "r.", -1)
|
|
legendList = append(legendList, shortName)
|
|
if i >= 7 {
|
|
break
|
|
}
|
|
i++
|
|
}
|
|
graph := createTimeGraph(vList, labelList, legendList)
|
|
|
|
// TODO: Sort this slice
|
|
var routeItems []c.PanelAnalyticsRoutesItem
|
|
for route, count := range routeMap {
|
|
if inEx(route) {
|
|
continue
|
|
}
|
|
routeItems = append(routeItems, c.PanelAnalyticsRoutesItem{
|
|
Route: route,
|
|
Count: count,
|
|
})
|
|
}
|
|
|
|
pi := c.PanelAnalyticsRoutesPage{bp, routeItems, graph, tr.Range}
|
|
return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_routes", pi})
|
|
}
|
|
|
|
// Trialling multi-series charts
|
|
func AnalyticsAgents(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
|
|
bp, ferr := PreAnalyticsDetail(w, r, u)
|
|
if ferr != nil {
|
|
return ferr
|
|
}
|
|
bp.AddScript("chartist/chartist-plugin-legend.min.js")
|
|
bp.AddSheet("chartist/chartist-plugin-legend.css")
|
|
|
|
tr, e := analyticsTimeRange(r.FormValue("timeRange"))
|
|
if e != nil {
|
|
return c.LocalError(e.Error(), w, r, u)
|
|
}
|
|
revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
|
|
|
|
rows, e := qgen.NewAcc().Select("viewchunks_agents").Columns("count,browser,createdAt").DateCutoff("createdAt", tr.Quantity, tr.Unit).Query()
|
|
if e != nil && e != sql.ErrNoRows {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
vMap, agentMap, e := analyticsRowsToDuoMap(rows, labelList, viewMap)
|
|
if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
ovList := analyticsVMapToOVList(vMap)
|
|
|
|
ex := strings.Split(r.FormValue("ex"), ",")
|
|
inEx := func(name string) bool {
|
|
for _, e := range ex {
|
|
if e == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
var vList [][]int64
|
|
var legendList []string
|
|
var i int
|
|
for _, ovitem := range ovList {
|
|
if inEx(ovitem.name) {
|
|
continue
|
|
}
|
|
lName, ok := p.GetUserAgentPhrase(ovitem.name)
|
|
if !ok {
|
|
lName = ovitem.name
|
|
}
|
|
if inEx(lName) {
|
|
continue
|
|
}
|
|
viewList := make([]int64, len(revLabelList))
|
|
for i, val := range revLabelList {
|
|
viewList[i] = ovitem.viewMap[val]
|
|
}
|
|
vList = append(vList, viewList)
|
|
legendList = append(legendList, lName)
|
|
if i >= 7 {
|
|
break
|
|
}
|
|
i++
|
|
}
|
|
graph := createTimeGraph(vList, labelList, legendList)
|
|
|
|
// TODO: Sort this slice
|
|
var agentItems []c.PanelAnalyticsAgentsItem
|
|
for agent, count := range agentMap {
|
|
if inEx(agent) {
|
|
continue
|
|
}
|
|
aAgent, ok := p.GetUserAgentPhrase(agent)
|
|
if !ok {
|
|
aAgent = agent
|
|
}
|
|
if inEx(aAgent) {
|
|
continue
|
|
}
|
|
agentItems = append(agentItems, c.PanelAnalyticsAgentsItem{
|
|
Agent: agent,
|
|
FriendlyAgent: aAgent,
|
|
Count: count,
|
|
})
|
|
}
|
|
|
|
pi := c.PanelAnalyticsDuoPage{bp, agentItems, graph, tr.Range}
|
|
return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_agents", pi})
|
|
}
|
|
|
|
func AnalyticsSystems(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
|
|
bp, ferr := PreAnalyticsDetail(w, r, u)
|
|
if ferr != nil {
|
|
return ferr
|
|
}
|
|
bp.AddScript("chartist/chartist-plugin-legend.min.js")
|
|
bp.AddSheet("chartist/chartist-plugin-legend.css")
|
|
|
|
tr, e := analyticsTimeRange(r.FormValue("timeRange"))
|
|
if e != nil {
|
|
return c.LocalError(e.Error(), w, r, u)
|
|
}
|
|
revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
|
|
|
|
rows, e := qgen.NewAcc().Select("viewchunks_systems").Columns("count,system,createdAt").DateCutoff("createdAt", tr.Quantity, tr.Unit).Query()
|
|
if e != nil && e != sql.ErrNoRows {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
vMap, osMap, e := analyticsRowsToDuoMap(rows, labelList, viewMap)
|
|
if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
ovList := analyticsVMapToOVList(vMap)
|
|
|
|
var vList [][]int64
|
|
var legendList []string
|
|
var i int
|
|
for _, ovitem := range ovList {
|
|
viewList := make([]int64, len(revLabelList))
|
|
for ii, val := range revLabelList {
|
|
viewList[ii] = ovitem.viewMap[val]
|
|
}
|
|
vList = append(vList, viewList)
|
|
lName, ok := p.GetOSPhrase(ovitem.name)
|
|
if !ok {
|
|
lName = ovitem.name
|
|
}
|
|
legendList = append(legendList, lName)
|
|
if i >= 6 {
|
|
break
|
|
}
|
|
i++
|
|
}
|
|
graph := createTimeGraph(vList, labelList, legendList)
|
|
|
|
// TODO: Sort this slice
|
|
systemItems, i := make([]c.PanelAnalyticsAgentsItem, len(osMap)), 0
|
|
for system, count := range osMap {
|
|
sSystem, ok := p.GetOSPhrase(system)
|
|
if !ok {
|
|
sSystem = system
|
|
}
|
|
systemItems[i] = c.PanelAnalyticsAgentsItem{
|
|
Agent: system,
|
|
FriendlyAgent: sSystem,
|
|
Count: count,
|
|
}
|
|
i++
|
|
}
|
|
|
|
pi := c.PanelAnalyticsDuoPage{bp, systemItems, graph, tr.Range}
|
|
return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_systems", pi})
|
|
}
|
|
|
|
func AnalyticsLanguages(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
|
|
bp, ferr := PreAnalyticsDetail(w, r, u)
|
|
if ferr != nil {
|
|
return ferr
|
|
}
|
|
bp.AddScript("chartist/chartist-plugin-legend.min.js")
|
|
bp.AddSheet("chartist/chartist-plugin-legend.css")
|
|
|
|
tr, e := analyticsTimeRange(r.FormValue("timeRange"))
|
|
if e != nil {
|
|
return c.LocalError(e.Error(), w, r, u)
|
|
}
|
|
revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
|
|
|
|
rows, e := qgen.NewAcc().Select("viewchunks_langs").Columns("count,lang,createdAt").DateCutoff("createdAt", tr.Quantity, tr.Unit).Query()
|
|
if e != nil && e != sql.ErrNoRows {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
vMap, langMap, e := analyticsRowsToDuoMap(rows, labelList, viewMap)
|
|
if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
ovList := analyticsVMapToOVList(vMap)
|
|
|
|
ex := strings.Split(r.FormValue("ex"), ",")
|
|
inEx := func(name string) bool {
|
|
for _, e := range ex {
|
|
if e == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
var vList [][]int64
|
|
var legendList []string
|
|
var i int
|
|
for _, ovitem := range ovList {
|
|
if inEx(ovitem.name) {
|
|
continue
|
|
}
|
|
lName, ok := p.GetHumanLangPhrase(ovitem.name)
|
|
if !ok {
|
|
lName = ovitem.name
|
|
}
|
|
if inEx(lName) {
|
|
continue
|
|
}
|
|
|
|
viewList := make([]int64, len(revLabelList))
|
|
for _, val := range revLabelList {
|
|
viewList[i] = ovitem.viewMap[val]
|
|
}
|
|
vList = append(vList, viewList)
|
|
legendList = append(legendList, lName)
|
|
if i >= 6 {
|
|
break
|
|
}
|
|
i++
|
|
}
|
|
graph := createTimeGraph(vList, labelList, legendList)
|
|
|
|
// TODO: Can we de-duplicate these analytics functions further?
|
|
// TODO: Sort this slice
|
|
var langItems []c.PanelAnalyticsAgentsItem
|
|
for lang, count := range langMap {
|
|
if inEx(lang) {
|
|
continue
|
|
}
|
|
lLang, ok := p.GetHumanLangPhrase(lang)
|
|
if !ok {
|
|
lLang = lang
|
|
}
|
|
if inEx(lLang) {
|
|
continue
|
|
}
|
|
langItems = append(langItems, c.PanelAnalyticsAgentsItem{
|
|
Agent: lang,
|
|
FriendlyAgent: lLang,
|
|
Count: count,
|
|
})
|
|
}
|
|
|
|
pi := c.PanelAnalyticsDuoPage{bp, langItems, graph, tr.Range}
|
|
return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_langs", pi})
|
|
}
|
|
|
|
func AnalyticsReferrers(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
|
|
bp, ferr := buildBasePage(w, r, u, "analytics", "analytics")
|
|
if ferr != nil {
|
|
return ferr
|
|
}
|
|
tr, e := analyticsTimeRange(r.FormValue("timeRange"))
|
|
if e != nil {
|
|
return c.LocalError(e.Error(), w, r, u)
|
|
}
|
|
|
|
rows, e := qgen.NewAcc().Select("viewchunks_referrers").Columns("count,domain").DateCutoff("createdAt", tr.Quantity, tr.Unit).Query()
|
|
if e != nil && e != sql.ErrNoRows {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
refMap, e := analyticsRowsToRefMap(rows)
|
|
if e != nil {
|
|
return c.InternalError(e, w, r)
|
|
}
|
|
showSpam := r.FormValue("spam") == "1"
|
|
|
|
isSpammy := func(domain string) bool {
|
|
for _, substr := range c.SpammyDomainBits {
|
|
if strings.Contains(domain, substr) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// TODO: Sort this slice
|
|
var refItems []c.PanelAnalyticsAgentsItem
|
|
for domain, count := range refMap {
|
|
sdomain := c.SanitiseSingleLine(domain)
|
|
if !showSpam && isSpammy(sdomain) {
|
|
continue
|
|
}
|
|
refItems = append(refItems, c.PanelAnalyticsAgentsItem{
|
|
Agent: sdomain,
|
|
Count: count,
|
|
})
|
|
}
|
|
|
|
pi := c.PanelAnalyticsReferrersPage{bp, refItems, tr.Range, showSpam}
|
|
return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_referrers", pi})
|
|
}
|