github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/routes/panel/analytics.go (about)

     1  package panel
     2  
     3  import (
     4  	"database/sql"
     5  	"errors"
     6  	"log"
     7  	"net/http"
     8  	"strconv"
     9  	"strings"
    10  	"time"
    11  
    12  	c "github.com/Azareal/Gosora/common"
    13  	p "github.com/Azareal/Gosora/common/phrases"
    14  	qgen "github.com/Azareal/Gosora/query_gen"
    15  )
    16  
    17  func analyticsTimeRange(rawTimeRange string) (*c.AnalyticsTimeRange, error) {
    18  	tr := &c.AnalyticsTimeRange{
    19  		Quantity:   6,
    20  		Unit:       "hour",
    21  		Slices:     12,
    22  		SliceWidth: 60 * 30,
    23  		Range:      "six-hours",
    24  	}
    25  
    26  	switch rawTimeRange {
    27  	// This might be pushing it, we might want to come up with a more efficient scheme for dealing with large timeframes like this
    28  	case "one-year":
    29  		tr.Quantity = 12
    30  		tr.Unit = "month"
    31  		tr.Slices = 12
    32  		tr.SliceWidth = 60 * 60 * 24 * 30
    33  	case "three-months":
    34  		tr.Quantity = 90
    35  		tr.Unit = "day"
    36  		tr.Slices = 30
    37  		tr.SliceWidth = 60 * 60 * 24 * 3
    38  	case "one-month":
    39  		tr.Quantity = 30
    40  		tr.Unit = "day"
    41  		tr.Slices = 30
    42  		tr.SliceWidth = 60 * 60 * 24
    43  	case "one-week":
    44  		tr.Quantity = 7
    45  		tr.Unit = "day"
    46  		tr.Slices = 14
    47  		tr.SliceWidth = 60 * 60 * 12
    48  	case "two-days": // Two days is experimental
    49  		tr.Quantity = 2
    50  		tr.Unit = "day"
    51  		tr.Slices = 24
    52  		tr.SliceWidth = 60 * 60 * 2
    53  	case "one-day":
    54  		tr.Quantity = 1
    55  		tr.Unit = "day"
    56  		tr.Slices = 24
    57  		tr.SliceWidth = 60 * 60
    58  	case "twelve-hours":
    59  		tr.Quantity = 12
    60  		tr.Slices = 24
    61  	case "six-hours", "":
    62  		return tr, nil
    63  	default:
    64  		return tr, errors.New("Unknown time range")
    65  	}
    66  	tr.Range = rawTimeRange
    67  	return tr, nil
    68  }
    69  
    70  type pAvg struct {
    71  	Avg int64
    72  	Tot int64
    73  }
    74  
    75  func analyticsRowsToAverageMap(rows *sql.Rows, labelList []int64, avgMap map[int64]int64) (map[int64]int64, error) {
    76  	defer rows.Close()
    77  	for rows.Next() {
    78  		var count int64
    79  		var createdAt time.Time
    80  		e := rows.Scan(&count, &createdAt)
    81  		if e != nil {
    82  			return avgMap, e
    83  		}
    84  		unixCreatedAt := createdAt.Unix()
    85  		// TODO: Bulk log this
    86  		if c.Dev.SuperDebug {
    87  			log.Print("count: ", count)
    88  			log.Print("createdAt: ", createdAt, " - ", unixCreatedAt)
    89  		}
    90  		pAvgMap := make(map[int64]pAvg)
    91  		for _, value := range labelList {
    92  			if unixCreatedAt > value {
    93  				prev := pAvgMap[value]
    94  				prev.Avg += count
    95  				prev.Tot++
    96  				pAvgMap[value] = prev
    97  				break
    98  			}
    99  		}
   100  		for key, pAvg := range pAvgMap {
   101  			avgMap[key] = pAvg.Avg / pAvg.Tot
   102  		}
   103  	}
   104  	return avgMap, rows.Err()
   105  }
   106  
   107  func analyticsRowsToAverageMap2(rows *sql.Rows, labelList []int64, avgMap map[int64]int64, typ int) (map[int64]int64, error) {
   108  	defer rows.Close()
   109  	for rows.Next() {
   110  		var stack, heap int64
   111  		var createdAt time.Time
   112  		e := rows.Scan(&stack, &heap, &createdAt)
   113  		if e != nil {
   114  			return avgMap, e
   115  		}
   116  		unixCreatedAt := createdAt.Unix()
   117  		// TODO: Bulk log this
   118  		if c.Dev.SuperDebug {
   119  			log.Print("stack: ", stack)
   120  			log.Print("heap: ", heap)
   121  			log.Print("createdAt: ", createdAt, " - ", unixCreatedAt)
   122  		}
   123  		if typ == 1 {
   124  			heap = 0
   125  		} else if typ == 2 {
   126  			stack = 0
   127  		}
   128  		pAvgMap := make(map[int64]pAvg)
   129  		for _, value := range labelList {
   130  			if unixCreatedAt > value {
   131  				prev := pAvgMap[value]
   132  				prev.Avg += stack + heap
   133  				prev.Tot++
   134  				pAvgMap[value] = prev
   135  				break
   136  			}
   137  		}
   138  		for key, pAvg := range pAvgMap {
   139  			avgMap[key] = pAvg.Avg / pAvg.Tot
   140  		}
   141  	}
   142  	return avgMap, rows.Err()
   143  }
   144  
   145  func analyticsRowsToAverageMap3(rows *sql.Rows, labelList []int64, avgMap map[int64]int64, typ int) (map[int64]int64, error) {
   146  	defer rows.Close()
   147  	for rows.Next() {
   148  		var low, high, avg int64
   149  		var createdAt time.Time
   150  		e := rows.Scan(&low, &high, &avg, &createdAt)
   151  		if e != nil {
   152  			return avgMap, e
   153  		}
   154  		unixCreatedAt := createdAt.Unix()
   155  		// TODO: Bulk log this
   156  		if c.Dev.SuperDebug {
   157  			log.Print("low: ", low)
   158  			log.Print("high: ", high)
   159  			log.Print("avg: ", avg)
   160  			log.Print("createdAt: ", createdAt, " - ", unixCreatedAt)
   161  		}
   162  		var dat int64
   163  		switch typ {
   164  		case 0:
   165  			dat = low
   166  		case 1:
   167  			dat = high
   168  		default:
   169  			dat = avg
   170  		}
   171  		pAvgMap := make(map[int64]pAvg)
   172  		for _, val := range labelList {
   173  			if unixCreatedAt > val {
   174  				prev := pAvgMap[val]
   175  				prev.Avg += dat
   176  				prev.Tot++
   177  				pAvgMap[val] = prev
   178  				break
   179  			}
   180  		}
   181  		for key, pAvg := range pAvgMap {
   182  			avgMap[key] = pAvg.Avg / pAvg.Tot
   183  		}
   184  	}
   185  	return avgMap, rows.Err()
   186  }
   187  
   188  func PreAnalyticsDetail(w http.ResponseWriter, r *http.Request, u *c.User) (*c.BasePanelPage, c.RouteError) {
   189  	bp, fe := buildBasePage(w, r, u, "analytics", "analytics")
   190  	if fe != nil {
   191  		return nil, fe
   192  	}
   193  	bp.AddSheet("chartist/chartist.min.css")
   194  	bp.AddScript("chartist/chartist.min.js")
   195  	bp.AddScriptAsync("analytics.js")
   196  	bp.LooseCSP = true
   197  	return bp, nil
   198  }
   199  
   200  func createTimeGraph(series [][]int64, labelList []int64, legends ...[]string) c.PanelTimeGraph {
   201  	var llegends []string
   202  	if len(legends) > 0 {
   203  		llegends = legends[0]
   204  	}
   205  	graph := c.PanelTimeGraph{Series: series, Labels: labelList, Legends: llegends}
   206  	c.DebugLogf("graph: %+v\n", graph)
   207  	return graph
   208  }
   209  
   210  func CreateViewListItems(revLabelList []int64, viewMap map[int64]int64) ([]int64, []c.PanelAnalyticsItem) {
   211  	viewList := make([]int64, len(revLabelList))
   212  	viewItems := make([]c.PanelAnalyticsItem, len(revLabelList))
   213  	for i, val := range revLabelList {
   214  		viewList[i] = viewMap[val]
   215  		viewItems[i] = c.PanelAnalyticsItem{Time: val, Count: viewMap[val]}
   216  	}
   217  	return viewList, viewItems
   218  }
   219  
   220  func AnalyticsViews(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
   221  	bp, fe := PreAnalyticsDetail(w, r, u)
   222  	if fe != nil {
   223  		return fe
   224  	}
   225  	tr, e := analyticsTimeRange(r.FormValue("timeRange"))
   226  	if e != nil {
   227  		return c.LocalError(e.Error(), w, r, u)
   228  	}
   229  	revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
   230  
   231  	c.DebugLog("in panel.AnalyticsViews")
   232  	// TODO: Add some sort of analytics store / iterator?
   233  	viewMap, e = c.Analytics.FillViewMap("viewchunks", tr, labelList, viewMap, "route", "")
   234  	if e != nil {
   235  		return c.InternalError(e, w, r)
   236  	}
   237  	viewList, viewItems := CreateViewListItems(revLabelList, viewMap)
   238  
   239  	graph := createTimeGraph([][]int64{viewList}, labelList)
   240  	var ttime string
   241  	if tr.Range == "six-hours" || tr.Range == "twelve-hours" || tr.Range == "one-day" {
   242  		ttime = "time"
   243  	}
   244  
   245  	pi := c.PanelAnalyticsStd{graph, viewItems, tr.Range, tr.Unit, ttime}
   246  	return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_views", pi})
   247  }
   248  
   249  func AnalyticsRouteViews(w http.ResponseWriter, r *http.Request, u *c.User, route string) c.RouteError {
   250  	bp, fe := PreAnalyticsDetail(w, r, u)
   251  	if fe != nil {
   252  		return fe
   253  	}
   254  	tr, e := analyticsTimeRange(r.FormValue("timeRange"))
   255  	if e != nil {
   256  		return c.LocalError(e.Error(), w, r, u)
   257  	}
   258  	revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
   259  
   260  	c.DebugLog("in panel.AnalyticsRouteViews")
   261  	// TODO: Validate the route is valid
   262  	viewMap, e = c.Analytics.FillViewMap("viewchunks", tr, labelList, viewMap, "route", route)
   263  	if e != nil {
   264  		return c.InternalError(e, w, r)
   265  	}
   266  	viewList, viewItems := CreateViewListItems(revLabelList, viewMap)
   267  	graph := createTimeGraph([][]int64{viewList}, labelList)
   268  
   269  	pi := c.PanelAnalyticsRoutePage{bp, c.SanitiseSingleLine(route), graph, viewItems, tr.Range}
   270  	return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_route_views", pi})
   271  }
   272  
   273  func AnalyticsAgentViews(w http.ResponseWriter, r *http.Request, u *c.User, agent string) c.RouteError {
   274  	bp, ferr := PreAnalyticsDetail(w, r, u)
   275  	if ferr != nil {
   276  		return ferr
   277  	}
   278  	tr, e := analyticsTimeRange(r.FormValue("timeRange"))
   279  	if e != nil {
   280  		return c.LocalError(e.Error(), w, r, u)
   281  	}
   282  	revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
   283  	// ? 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
   284  	agent = c.SanitiseSingleLine(agent)
   285  
   286  	c.DebugLog("in panel.AnalyticsAgentViews")
   287  	// TODO: Verify the agent is valid
   288  	viewMap, e = c.Analytics.FillViewMap("viewchunks_agents", tr, labelList, viewMap, "browser", agent)
   289  	if e != nil {
   290  		return c.InternalError(e, w, r)
   291  	}
   292  	viewList := CreateViewList(revLabelList, viewMap)
   293  	graph := createTimeGraph([][]int64{viewList}, labelList)
   294  
   295  	friendlyAgent, ok := p.GetUserAgentPhrase(agent)
   296  	if !ok {
   297  		friendlyAgent = agent
   298  	}
   299  
   300  	pi := c.PanelAnalyticsAgentPage{bp, agent, friendlyAgent, graph, tr.Range}
   301  	return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_agent_views", pi})
   302  }
   303  
   304  func AnalyticsForumViews(w http.ResponseWriter, r *http.Request, u *c.User, sfid string) c.RouteError {
   305  	bp, ferr := PreAnalyticsDetail(w, r, u)
   306  	if ferr != nil {
   307  		return ferr
   308  	}
   309  	tr, e := analyticsTimeRange(r.FormValue("timeRange"))
   310  	if e != nil {
   311  		return c.LocalError(e.Error(), w, r, u)
   312  	}
   313  	revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
   314  
   315  	fid, e := strconv.Atoi(sfid)
   316  	if e != nil {
   317  		return c.LocalError("Invalid integer", w, r, u)
   318  	}
   319  
   320  	c.DebugLog("in panel.AnalyticsForumViews")
   321  	// TODO: Verify the agent is valid
   322  	viewMap, e = c.Analytics.FillViewMap("viewchunks_forums", tr, labelList, viewMap, "forum", fid)
   323  	if e != nil {
   324  		return c.InternalError(e, w, r)
   325  	}
   326  	viewList := CreateViewList(revLabelList, viewMap)
   327  	graph := createTimeGraph([][]int64{viewList}, labelList)
   328  
   329  	forum, e := c.Forums.Get(fid)
   330  	if e != nil {
   331  		return c.InternalError(e, w, r)
   332  	}
   333  
   334  	pi := c.PanelAnalyticsAgentPage{bp, sfid, forum.Name, graph, tr.Range}
   335  	return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_forum_views", pi})
   336  }
   337  
   338  func AnalyticsSystemViews(w http.ResponseWriter, r *http.Request, u *c.User, system string) c.RouteError {
   339  	bp, ferr := PreAnalyticsDetail(w, r, u)
   340  	if ferr != nil {
   341  		return ferr
   342  	}
   343  	tr, e := analyticsTimeRange(r.FormValue("timeRange"))
   344  	if e != nil {
   345  		return c.LocalError(e.Error(), w, r, u)
   346  	}
   347  	revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
   348  	system = c.SanitiseSingleLine(system)
   349  
   350  	c.DebugLog("in panel.AnalyticsSystemViews")
   351  	// TODO: Verify the OS name is valid
   352  	viewMap, e = c.Analytics.FillViewMap("viewchunks_systems", tr, labelList, viewMap, "system", system)
   353  	if e != nil {
   354  		return c.InternalError(e, w, r)
   355  	}
   356  	viewList := CreateViewList(revLabelList, viewMap)
   357  	graph := createTimeGraph([][]int64{viewList}, labelList)
   358  
   359  	friendlySystem, ok := p.GetOSPhrase(system)
   360  	if !ok {
   361  		friendlySystem = system
   362  	}
   363  
   364  	pi := c.PanelAnalyticsAgentPage{bp, system, friendlySystem, graph, tr.Range}
   365  	return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_system_views", pi})
   366  }
   367  
   368  func CreateViewList(revLabelList []int64, viewMap map[int64]int64) []int64 {
   369  	viewList := make([]int64, len(revLabelList))
   370  	for i, val := range revLabelList {
   371  		viewList[i] = viewMap[val]
   372  	}
   373  	return viewList
   374  }
   375  
   376  func AnalyticsLanguageViews(w http.ResponseWriter, r *http.Request, u *c.User, lang string) c.RouteError {
   377  	bp, ferr := PreAnalyticsDetail(w, r, u)
   378  	if ferr != nil {
   379  		return ferr
   380  	}
   381  	tr, e := analyticsTimeRange(r.FormValue("timeRange"))
   382  	if e != nil {
   383  		return c.LocalError(e.Error(), w, r, u)
   384  	}
   385  	revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
   386  	lang = c.SanitiseSingleLine(lang)
   387  
   388  	c.DebugLog("in panel.AnalyticsLanguageViews")
   389  	// TODO: Verify the language code is valid
   390  	viewMap, e = c.Analytics.FillViewMap("viewchunks_langs", tr, labelList, viewMap, "lang", lang)
   391  	if e != nil {
   392  		return c.InternalError(e, w, r)
   393  	}
   394  	viewList := CreateViewList(revLabelList, viewMap)
   395  	graph := createTimeGraph([][]int64{viewList}, labelList)
   396  
   397  	friendlyLang, ok := p.GetHumanLangPhrase(lang)
   398  	if !ok {
   399  		friendlyLang = lang
   400  	}
   401  
   402  	pi := c.PanelAnalyticsAgentPage{bp, lang, friendlyLang, graph, tr.Range}
   403  	return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_lang_views", pi})
   404  }
   405  
   406  func AnalyticsReferrerViews(w http.ResponseWriter, r *http.Request, u *c.User, domain string) c.RouteError {
   407  	bp, ferr := PreAnalyticsDetail(w, r, u)
   408  	if ferr != nil {
   409  		return ferr
   410  	}
   411  	tr, e := analyticsTimeRange(r.FormValue("timeRange"))
   412  	if e != nil {
   413  		return c.LocalError(e.Error(), w, r, u)
   414  	}
   415  	revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
   416  
   417  	c.DebugLog("in panel.AnalyticsReferrerViews")
   418  	// TODO: Verify the agent is valid
   419  	viewMap, e = c.Analytics.FillViewMap("viewchunks_referrers", tr, labelList, viewMap, "domain", domain)
   420  	if e != nil {
   421  		return c.InternalError(e, w, r)
   422  	}
   423  	viewList := CreateViewList(revLabelList, viewMap)
   424  	graph := createTimeGraph([][]int64{viewList}, labelList)
   425  
   426  	pi := c.PanelAnalyticsAgentPage{bp, c.SanitiseSingleLine(domain), "", graph, tr.Range}
   427  	return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_referrer_views", pi})
   428  }
   429  
   430  func AnalyticsTopics(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
   431  	bp, ferr := PreAnalyticsDetail(w, r, u)
   432  	if ferr != nil {
   433  		return ferr
   434  	}
   435  	tr, e := analyticsTimeRange(r.FormValue("timeRange"))
   436  	if e != nil {
   437  		return c.LocalError(e.Error(), w, r, u)
   438  	}
   439  	revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
   440  
   441  	c.DebugLog("in panel.AnalyticsTopics")
   442  	viewMap, e = c.Analytics.FillViewMap("topicchunks", tr, labelList, viewMap, "")
   443  	if e != nil {
   444  		return c.InternalError(e, w, r)
   445  	}
   446  	viewList, viewItems := CreateViewListItems(revLabelList, viewMap)
   447  	graph := createTimeGraph([][]int64{viewList}, labelList)
   448  
   449  	pi := c.PanelAnalyticsStd{graph, viewItems, tr.Range, tr.Unit, "time"}
   450  	return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_topics", pi})
   451  }
   452  
   453  func AnalyticsPosts(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
   454  	bp, fe := PreAnalyticsDetail(w, r, u)
   455  	if fe != nil {
   456  		return fe
   457  	}
   458  	tr, e := analyticsTimeRange(r.FormValue("timeRange"))
   459  	if e != nil {
   460  		return c.LocalError(e.Error(), w, r, u)
   461  	}
   462  	revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
   463  
   464  	c.DebugLog("in panel.AnalyticsPosts")
   465  	viewMap, e = c.Analytics.FillViewMap("postchunks", tr, labelList, viewMap, "")
   466  	if e != nil {
   467  		return c.InternalError(e, w, r)
   468  	}
   469  	viewList, viewItems := CreateViewListItems(revLabelList, viewMap)
   470  	graph := createTimeGraph([][]int64{viewList}, labelList)
   471  
   472  	pi := c.PanelAnalyticsStd{graph, viewItems, tr.Range, tr.Unit, "time"}
   473  	return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_posts", pi})
   474  }
   475  
   476  func AnalyticsMemory(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
   477  	bp, fe := PreAnalyticsDetail(w, r, u)
   478  	if fe != nil {
   479  		return fe
   480  	}
   481  	tr, e := analyticsTimeRange(r.FormValue("timeRange"))
   482  	if e != nil {
   483  		return c.LocalError(e.Error(), w, r, u)
   484  	}
   485  	revLabelList, labelList, avgMap := c.AnalyticsTimeRangeToLabelList(tr)
   486  
   487  	c.DebugLog("in panel.AnalyticsMemory")
   488  	rows, e := qgen.NewAcc().Select("memchunks").Columns("count,createdAt").DateCutoff("createdAt", tr.Quantity, tr.Unit).Query()
   489  	if e != nil && e != sql.ErrNoRows {
   490  		return c.InternalError(e, w, r)
   491  	}
   492  	avgMap, e = analyticsRowsToAverageMap(rows, labelList, avgMap)
   493  	if e != nil {
   494  		return c.InternalError(e, w, r)
   495  	}
   496  
   497  	// TODO: Adjust for the missing chunks in week and month
   498  	avgList := make([]int64, len(revLabelList))
   499  	avgItems := make([]c.PanelAnalyticsItemUnit, len(revLabelList))
   500  	for i, value := range revLabelList {
   501  		avgList[i] = avgMap[value]
   502  		cv, cu := c.ConvertByteUnit(float64(avgMap[value]))
   503  		avgItems[i] = c.PanelAnalyticsItemUnit{Time: value, Unit: cu, Count: int64(cv)}
   504  	}
   505  	graph := createTimeGraph([][]int64{avgList}, labelList)
   506  
   507  	pi := c.PanelAnalyticsStdUnit{graph, avgItems, tr.Range, tr.Unit, "time"}
   508  	return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_memory", pi})
   509  }
   510  
   511  // TODO: Show stack and heap memory separately on the chart
   512  func AnalyticsActiveMemory(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
   513  	bp, ferr := PreAnalyticsDetail(w, r, u)
   514  	if ferr != nil {
   515  		return ferr
   516  	}
   517  	tr, e := analyticsTimeRange(r.FormValue("timeRange"))
   518  	if e != nil {
   519  		return c.LocalError(e.Error(), w, r, u)
   520  	}
   521  	revLabelList, labelList, avgMap := c.AnalyticsTimeRangeToLabelList(tr)
   522  
   523  	c.DebugLog("in panel.AnalyticsActiveMemory")
   524  	rows, e := qgen.NewAcc().Select("memchunks").Columns("stack,heap,createdAt").DateCutoff("createdAt", tr.Quantity, tr.Unit).Query()
   525  	if e != nil && e != sql.ErrNoRows {
   526  		return c.InternalError(e, w, r)
   527  	}
   528  
   529  	var typ int
   530  	switch r.FormValue("mtype") {
   531  	case "1":
   532  		typ = 1
   533  	case "2":
   534  		typ = 2
   535  	default:
   536  		typ = 0
   537  	}
   538  	avgMap, e = analyticsRowsToAverageMap2(rows, labelList, avgMap, typ)
   539  	if e != nil {
   540  		return c.InternalError(e, w, r)
   541  	}
   542  
   543  	// TODO: Adjust for the missing chunks in week and month
   544  	avgList := make([]int64, len(revLabelList))
   545  	avgItems := make([]c.PanelAnalyticsItemUnit, len(revLabelList))
   546  	for i, value := range revLabelList {
   547  		avgList[i] = avgMap[value]
   548  		cv, cu := c.ConvertByteUnit(float64(avgMap[value]))
   549  		avgItems[i] = c.PanelAnalyticsItemUnit{Time: value, Unit: cu, Count: int64(cv)}
   550  	}
   551  	graph := createTimeGraph([][]int64{avgList}, labelList)
   552  
   553  	pi := c.PanelAnalyticsActiveMemory{graph, avgItems, tr.Range, tr.Unit, "time", typ}
   554  	return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_active_memory", pi})
   555  }
   556  
   557  func AnalyticsPerf(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
   558  	bp, ferr := PreAnalyticsDetail(w, r, u)
   559  	if ferr != nil {
   560  		return ferr
   561  	}
   562  	tr, e := analyticsTimeRange(r.FormValue("timeRange"))
   563  	if e != nil {
   564  		return c.LocalError(e.Error(), w, r, u)
   565  	}
   566  	revLabelList, labelList, avgMap := c.AnalyticsTimeRangeToLabelList(tr)
   567  
   568  	c.DebugLog("in panel.AnalyticsPerf")
   569  	rows, e := qgen.NewAcc().Select("perfchunks").Columns("low,high,avg,createdAt").DateCutoff("createdAt", tr.Quantity, tr.Unit).Query()
   570  	if e != nil && e != sql.ErrNoRows {
   571  		return c.InternalError(e, w, r)
   572  	}
   573  
   574  	var typ int
   575  	switch r.FormValue("type") {
   576  	case "0":
   577  		typ = 0
   578  	case "1":
   579  		typ = 1
   580  	default:
   581  		typ = 2
   582  	}
   583  	avgMap, e = analyticsRowsToAverageMap3(rows, labelList, avgMap, typ)
   584  	if e != nil {
   585  		return c.InternalError(e, w, r)
   586  	}
   587  
   588  	// TODO: Adjust for the missing chunks in week and month
   589  	avgList := make([]int64, len(revLabelList))
   590  	avgItems := make([]c.PanelAnalyticsItemUnit, len(revLabelList))
   591  	for i, value := range revLabelList {
   592  		avgList[i] = avgMap[value]
   593  		cv, cu := c.ConvertPerfUnit(float64(avgMap[value]))
   594  		avgItems[i] = c.PanelAnalyticsItemUnit{Time: value, Unit: cu, Count: int64(cv)}
   595  	}
   596  	graph := createTimeGraph([][]int64{avgList}, labelList)
   597  
   598  	pi := c.PanelAnalyticsPerf{graph, avgItems, tr.Range, tr.Unit, "time", typ}
   599  	return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_performance", pi})
   600  }
   601  
   602  func analyticsRowsToAvgDuoMap(rows *sql.Rows, labelList []int64, avgMap map[int64]int64) (map[string]map[int64]int64, map[string]int, error) {
   603  	aMap := make(map[string]map[int64]int64)
   604  	nameMap := make(map[string]int)
   605  	defer rows.Close()
   606  	for rows.Next() {
   607  		var count int64
   608  		var name string
   609  		var createdAt time.Time
   610  		e := rows.Scan(&count, &name, &createdAt)
   611  		if e != nil {
   612  			return aMap, nameMap, e
   613  		}
   614  
   615  		// TODO: Bulk log this
   616  		unixCreatedAt := createdAt.Unix()
   617  		if c.Dev.SuperDebug {
   618  			log.Print("count: ", count)
   619  			log.Print("name: ", name)
   620  			log.Print("createdAt: ", createdAt, " - ", unixCreatedAt)
   621  		}
   622  
   623  		vvMap, ok := aMap[name]
   624  		if !ok {
   625  			vvMap = make(map[int64]int64)
   626  			for key, val := range avgMap {
   627  				vvMap[key] = val
   628  			}
   629  			aMap[name] = vvMap
   630  		}
   631  		for _, value := range labelList {
   632  			if unixCreatedAt > value {
   633  				vvMap[value] = (vvMap[value] + count) / 2
   634  				break
   635  			}
   636  		}
   637  		nameMap[name] = (nameMap[name] + int(count)) / 2
   638  	}
   639  	return aMap, nameMap, rows.Err()
   640  }
   641  
   642  func sortOVList(ovList []OVItem) []OVItem {
   643  	// Use bubble sort for now as there shouldn't be too many items
   644  	for i := 0; i < len(ovList)-1; i++ {
   645  		for j := 0; j < len(ovList)-1; j++ {
   646  			if ovList[j].count > ovList[j+1].count {
   647  				temp := ovList[j]
   648  				ovList[j] = ovList[j+1]
   649  				ovList[j+1] = temp
   650  			}
   651  		}
   652  	}
   653  
   654  	// Invert the direction
   655  	tOVList := make([]OVItem, len(ovList))
   656  	for i, ii := len(ovList)-1, 0; i >= 0; i-- {
   657  		tOVList[ii] = ovList[i]
   658  		ii++
   659  	}
   660  	return tOVList
   661  }
   662  
   663  func analyticsAMapToOVList(aMap map[string]map[int64]int64) []OVItem {
   664  	// Order the map
   665  	ovList, i := make([]OVItem, len(aMap)), 0
   666  	for name, avgMap := range aMap {
   667  		var totcount int
   668  		for _, count := range avgMap {
   669  			totcount = (totcount + int(count)) / 2
   670  		}
   671  		ovList[i] = OVItem{name, totcount, avgMap}
   672  		i++
   673  	}
   674  	return sortOVList(ovList)
   675  }
   676  
   677  func AnalyticsRoutesPerf(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
   678  	bp, ferr := PreAnalyticsDetail(w, r, u)
   679  	if ferr != nil {
   680  		return ferr
   681  	}
   682  	bp.AddScript("chartist/chartist-plugin-legend.min.js")
   683  	bp.AddSheet("chartist/chartist-plugin-legend.css")
   684  
   685  	tr, e := analyticsTimeRange(r.FormValue("timeRange"))
   686  	if e != nil {
   687  		return c.LocalError(e.Error(), w, r, u)
   688  	}
   689  	// avgMap contains timestamps but not the averages for those stamps
   690  	revLabelList, labelList, avgMap := c.AnalyticsTimeRangeToLabelList(tr)
   691  
   692  	rows, e := qgen.NewAcc().Select("viewchunks").Columns("avg,route,createdAt").Where("count!=0 AND route!=''").DateCutoff("createdAt", tr.Quantity, tr.Unit).Query()
   693  	if e != nil && e != sql.ErrNoRows {
   694  		return c.InternalError(e, w, r)
   695  	}
   696  	aMap, routeMap, e := analyticsRowsToAvgDuoMap(rows, labelList, avgMap)
   697  	if e != nil {
   698  		return c.InternalError(e, w, r)
   699  	}
   700  	//c.DebugLogf("aMap: %+v\n", aMap)
   701  	//c.DebugLogf("routeMap: %+v\n", routeMap)
   702  	ovList := analyticsAMapToOVList(aMap)
   703  	//c.DebugLogf("ovList: %+v\n", ovList)
   704  
   705  	ex := strings.Split(r.FormValue("ex"), ",")
   706  	inEx := func(name string) bool {
   707  		for _, e := range ex {
   708  			if e == name {
   709  				return true
   710  			}
   711  		}
   712  		return false
   713  	}
   714  
   715  	var vList [][]int64
   716  	var legendList []string
   717  	var i int
   718  	for _, ovitem := range ovList {
   719  		if inEx(ovitem.name) {
   720  			continue
   721  		}
   722  		if strings.HasPrefix(ovitem.name, "panel.") {
   723  			continue
   724  		}
   725  		viewList := make([]int64, len(revLabelList))
   726  		for i, val := range revLabelList {
   727  			viewList[i] = ovitem.viewMap[val]
   728  		}
   729  		vList = append(vList, viewList)
   730  		shortName := strings.Replace(ovitem.name, "routes.", "r.", -1)
   731  		legendList = append(legendList, shortName)
   732  		if i >= 7 {
   733  			break
   734  		}
   735  		i++
   736  	}
   737  	graph := createTimeGraph(vList, labelList, legendList)
   738  
   739  	// TODO: Sort this slice
   740  	var routeItems []c.PanelAnalyticsRoutesPerfItem
   741  	for route, count := range routeMap {
   742  		if inEx(route) {
   743  			continue
   744  		}
   745  		cv, cu := c.ConvertPerfUnit(float64(count))
   746  		routeItems = append(routeItems, c.PanelAnalyticsRoutesPerfItem{
   747  			Route: route,
   748  			Unit:  cu,
   749  			Count: int(cv),
   750  		})
   751  	}
   752  
   753  	pi := c.PanelAnalyticsRoutesPerfPage{bp, routeItems, graph, tr.Range}
   754  	return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_routes_perf", pi})
   755  }
   756  
   757  func analyticsRowsToRefMap(rows *sql.Rows) (map[string]int, error) {
   758  	nameMap := make(map[string]int)
   759  	defer rows.Close()
   760  	c.DebugDetail("name - count")
   761  	for rows.Next() {
   762  		var count int
   763  		var name string
   764  		e := rows.Scan(&count, &name)
   765  		if e != nil {
   766  			return nameMap, e
   767  		}
   768  		// TODO: Bulk log this
   769  		if c.Dev.SuperDebug {
   770  			log.Print(name, " - ", count)
   771  		}
   772  		nameMap[name] += count
   773  	}
   774  	return nameMap, rows.Err()
   775  }
   776  
   777  func analyticsRowsToDuoMap(rows *sql.Rows, labelList []int64, viewMap map[int64]int64) (map[string]map[int64]int64, map[string]int, error) {
   778  	vMap := make(map[string]map[int64]int64)
   779  	nameMap := make(map[string]int)
   780  	defer rows.Close()
   781  	for rows.Next() {
   782  		var count int64
   783  		var name string
   784  		var createdAt time.Time
   785  		e := rows.Scan(&count, &name, &createdAt)
   786  		if e != nil {
   787  			return vMap, nameMap, e
   788  		}
   789  
   790  		// TODO: Bulk log this
   791  		unixCreatedAt := createdAt.Unix()
   792  		if c.Dev.SuperDebug {
   793  			log.Print("count: ", count)
   794  			log.Print("name: ", name)
   795  			log.Print("createdAt: ", createdAt, " - ", unixCreatedAt)
   796  		}
   797  
   798  		vvMap, ok := vMap[name]
   799  		if !ok {
   800  			vvMap = make(map[int64]int64)
   801  			for key, val := range viewMap {
   802  				vvMap[key] = val
   803  			}
   804  			vMap[name] = vvMap
   805  		}
   806  		for _, value := range labelList {
   807  			if unixCreatedAt > value {
   808  				vvMap[value] += count
   809  				break
   810  			}
   811  		}
   812  		nameMap[name] += int(count)
   813  	}
   814  	return vMap, nameMap, rows.Err()
   815  }
   816  
   817  type OVItem struct {
   818  	name    string
   819  	count   int
   820  	viewMap map[int64]int64
   821  }
   822  
   823  func analyticsVMapToOVList(vMap map[string]map[int64]int64) (ovList []OVItem) {
   824  	// Order the map
   825  	ovList, i := make([]OVItem, len(vMap)), 0
   826  	for name, viewMap := range vMap {
   827  		var totcount int
   828  		for _, count := range viewMap {
   829  			totcount += int(count)
   830  		}
   831  		ovList[i] = OVItem{name, totcount, viewMap}
   832  		i++
   833  	}
   834  	return sortOVList(ovList)
   835  }
   836  
   837  func AnalyticsForums(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
   838  	bp, ferr := PreAnalyticsDetail(w, r, u)
   839  	if ferr != nil {
   840  		return ferr
   841  	}
   842  	bp.AddScript("chartist/chartist-plugin-legend.min.js")
   843  	bp.AddSheet("chartist/chartist-plugin-legend.css")
   844  
   845  	tr, e := analyticsTimeRange(r.FormValue("timeRange"))
   846  	if e != nil {
   847  		return c.LocalError(e.Error(), w, r, u)
   848  	}
   849  	revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
   850  
   851  	rows, e := qgen.NewAcc().Select("viewchunks_forums").Columns("count,forum,createdAt").Where("forum!=''").DateCutoff("createdAt", tr.Quantity, tr.Unit).Query()
   852  	if e != nil && e != sql.ErrNoRows {
   853  		return c.InternalError(e, w, r)
   854  	}
   855  	vMap, forumMap, e := analyticsRowsToDuoMap(rows, labelList, viewMap)
   856  	if e != nil {
   857  		return c.InternalError(e, w, r)
   858  	}
   859  	ovList := analyticsVMapToOVList(vMap)
   860  
   861  	var vList [][]int64
   862  	var legendList []string
   863  	var i int
   864  	for _, ovitem := range ovList {
   865  		viewList := make([]int64, len(revLabelList))
   866  		for i, val := range revLabelList {
   867  			viewList[i] = ovitem.viewMap[val]
   868  		}
   869  		vList = append(vList, viewList)
   870  		fid, e := strconv.Atoi(ovitem.name)
   871  		if e != nil {
   872  			return c.InternalError(e, w, r)
   873  		}
   874  		var lName string
   875  		forum, e := c.Forums.Get(fid)
   876  		if e == sql.ErrNoRows {
   877  			lName = "Deleted Forum" // TODO: Localise this
   878  		} else if e != nil {
   879  			return c.InternalError(e, w, r)
   880  		} else {
   881  			lName = forum.Name
   882  		}
   883  		legendList = append(legendList, lName)
   884  		if i >= 6 {
   885  			break
   886  		}
   887  		i++
   888  	}
   889  	graph := createTimeGraph(vList, labelList, legendList)
   890  
   891  	// TODO: Sort this slice
   892  	forumItems, i := make([]c.PanelAnalyticsAgentsItem, len(forumMap)), 0
   893  	for sfid, count := range forumMap {
   894  		fid, e := strconv.Atoi(sfid)
   895  		if e != nil {
   896  			return c.InternalError(e, w, r)
   897  		}
   898  		var lName string
   899  		forum, e := c.Forums.Get(fid)
   900  		if e == sql.ErrNoRows {
   901  			// TODO: Localise this
   902  			lName = "Deleted Forum"
   903  		} else if e != nil {
   904  			return c.InternalError(e, w, r)
   905  		} else {
   906  			lName = forum.Name
   907  		}
   908  		forumItems[i] = c.PanelAnalyticsAgentsItem{
   909  			Agent:         sfid,
   910  			FriendlyAgent: lName,
   911  			Count:         count,
   912  		}
   913  		i++
   914  	}
   915  
   916  	pi := c.PanelAnalyticsDuoPage{bp, forumItems, graph, tr.Range}
   917  	return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_forums", pi})
   918  }
   919  
   920  func AnalyticsRoutes(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
   921  	bp, ferr := PreAnalyticsDetail(w, r, u)
   922  	if ferr != nil {
   923  		return ferr
   924  	}
   925  	bp.AddScript("chartist/chartist-plugin-legend.min.js")
   926  	bp.AddSheet("chartist/chartist-plugin-legend.css")
   927  
   928  	tr, e := analyticsTimeRange(r.FormValue("timeRange"))
   929  	if e != nil {
   930  		return c.LocalError(e.Error(), w, r, u)
   931  	}
   932  	revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
   933  
   934  	rows, e := qgen.NewAcc().Select("viewchunks").Columns("count,route,createdAt").Where("route!=''").DateCutoff("createdAt", tr.Quantity, tr.Unit).Query()
   935  	if e != nil && e != sql.ErrNoRows {
   936  		return c.InternalError(e, w, r)
   937  	}
   938  	vMap, routeMap, e := analyticsRowsToDuoMap(rows, labelList, viewMap)
   939  	if e != nil {
   940  		return c.InternalError(e, w, r)
   941  	}
   942  	//c.DebugLogf("vMap: %+v\n", vMap)
   943  	//c.DebugLogf("routeMap: %+v\n", routeMap)
   944  	ovList := analyticsVMapToOVList(vMap)
   945  	//c.DebugLogf("ovList: %+v\n", ovList)
   946  
   947  	ex := strings.Split(r.FormValue("ex"), ",")
   948  	inEx := func(name string) bool {
   949  		for _, e := range ex {
   950  			if e == name {
   951  				return true
   952  			}
   953  		}
   954  		return false
   955  	}
   956  
   957  	var vList [][]int64
   958  	var legendList []string
   959  	var i int
   960  	for _, ovitem := range ovList {
   961  		if inEx(ovitem.name) {
   962  			continue
   963  		}
   964  		viewList := make([]int64, len(revLabelList))
   965  		for i, val := range revLabelList {
   966  			viewList[i] = ovitem.viewMap[val]
   967  		}
   968  		vList = append(vList, viewList)
   969  		shortName := strings.Replace(ovitem.name, "routes.", "r.", -1)
   970  		legendList = append(legendList, shortName)
   971  		if i >= 7 {
   972  			break
   973  		}
   974  		i++
   975  	}
   976  	graph := createTimeGraph(vList, labelList, legendList)
   977  
   978  	// TODO: Sort this slice
   979  	var routeItems []c.PanelAnalyticsRoutesItem
   980  	for route, count := range routeMap {
   981  		if inEx(route) {
   982  			continue
   983  		}
   984  		routeItems = append(routeItems, c.PanelAnalyticsRoutesItem{
   985  			Route: route,
   986  			Count: count,
   987  		})
   988  	}
   989  
   990  	pi := c.PanelAnalyticsRoutesPage{bp, routeItems, graph, tr.Range}
   991  	return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_routes", pi})
   992  }
   993  
   994  // Trialling multi-series charts
   995  func AnalyticsAgents(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
   996  	bp, ferr := PreAnalyticsDetail(w, r, u)
   997  	if ferr != nil {
   998  		return ferr
   999  	}
  1000  	bp.AddScript("chartist/chartist-plugin-legend.min.js")
  1001  	bp.AddSheet("chartist/chartist-plugin-legend.css")
  1002  
  1003  	tr, e := analyticsTimeRange(r.FormValue("timeRange"))
  1004  	if e != nil {
  1005  		return c.LocalError(e.Error(), w, r, u)
  1006  	}
  1007  	revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
  1008  
  1009  	rows, e := qgen.NewAcc().Select("viewchunks_agents").Columns("count,browser,createdAt").DateCutoff("createdAt", tr.Quantity, tr.Unit).Query()
  1010  	if e != nil && e != sql.ErrNoRows {
  1011  		return c.InternalError(e, w, r)
  1012  	}
  1013  	vMap, agentMap, e := analyticsRowsToDuoMap(rows, labelList, viewMap)
  1014  	if e != nil {
  1015  		return c.InternalError(e, w, r)
  1016  	}
  1017  	ovList := analyticsVMapToOVList(vMap)
  1018  
  1019  	ex := strings.Split(r.FormValue("ex"), ",")
  1020  	inEx := func(name string) bool {
  1021  		for _, e := range ex {
  1022  			if e == name {
  1023  				return true
  1024  			}
  1025  		}
  1026  		return false
  1027  	}
  1028  
  1029  	var vList [][]int64
  1030  	var legendList []string
  1031  	var i int
  1032  	for _, ovitem := range ovList {
  1033  		if inEx(ovitem.name) {
  1034  			continue
  1035  		}
  1036  		lName, ok := p.GetUserAgentPhrase(ovitem.name)
  1037  		if !ok {
  1038  			lName = ovitem.name
  1039  		}
  1040  		if inEx(lName) {
  1041  			continue
  1042  		}
  1043  		viewList := make([]int64, len(revLabelList))
  1044  		for i, val := range revLabelList {
  1045  			viewList[i] = ovitem.viewMap[val]
  1046  		}
  1047  		vList = append(vList, viewList)
  1048  		legendList = append(legendList, lName)
  1049  		if i >= 7 {
  1050  			break
  1051  		}
  1052  		i++
  1053  	}
  1054  	graph := createTimeGraph(vList, labelList, legendList)
  1055  
  1056  	// TODO: Sort this slice
  1057  	var agentItems []c.PanelAnalyticsAgentsItem
  1058  	for agent, count := range agentMap {
  1059  		if inEx(agent) {
  1060  			continue
  1061  		}
  1062  		aAgent, ok := p.GetUserAgentPhrase(agent)
  1063  		if !ok {
  1064  			aAgent = agent
  1065  		}
  1066  		if inEx(aAgent) {
  1067  			continue
  1068  		}
  1069  		agentItems = append(agentItems, c.PanelAnalyticsAgentsItem{
  1070  			Agent:         agent,
  1071  			FriendlyAgent: aAgent,
  1072  			Count:         count,
  1073  		})
  1074  	}
  1075  
  1076  	pi := c.PanelAnalyticsDuoPage{bp, agentItems, graph, tr.Range}
  1077  	return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_agents", pi})
  1078  }
  1079  
  1080  func AnalyticsSystems(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
  1081  	bp, ferr := PreAnalyticsDetail(w, r, u)
  1082  	if ferr != nil {
  1083  		return ferr
  1084  	}
  1085  	bp.AddScript("chartist/chartist-plugin-legend.min.js")
  1086  	bp.AddSheet("chartist/chartist-plugin-legend.css")
  1087  
  1088  	tr, e := analyticsTimeRange(r.FormValue("timeRange"))
  1089  	if e != nil {
  1090  		return c.LocalError(e.Error(), w, r, u)
  1091  	}
  1092  	revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
  1093  
  1094  	rows, e := qgen.NewAcc().Select("viewchunks_systems").Columns("count,system,createdAt").DateCutoff("createdAt", tr.Quantity, tr.Unit).Query()
  1095  	if e != nil && e != sql.ErrNoRows {
  1096  		return c.InternalError(e, w, r)
  1097  	}
  1098  	vMap, osMap, e := analyticsRowsToDuoMap(rows, labelList, viewMap)
  1099  	if e != nil {
  1100  		return c.InternalError(e, w, r)
  1101  	}
  1102  	ovList := analyticsVMapToOVList(vMap)
  1103  
  1104  	var vList [][]int64
  1105  	var legendList []string
  1106  	var i int
  1107  	for _, ovitem := range ovList {
  1108  		viewList := make([]int64, len(revLabelList))
  1109  		for ii, val := range revLabelList {
  1110  			viewList[ii] = ovitem.viewMap[val]
  1111  		}
  1112  		vList = append(vList, viewList)
  1113  		lName, ok := p.GetOSPhrase(ovitem.name)
  1114  		if !ok {
  1115  			lName = ovitem.name
  1116  		}
  1117  		legendList = append(legendList, lName)
  1118  		if i >= 6 {
  1119  			break
  1120  		}
  1121  		i++
  1122  	}
  1123  	graph := createTimeGraph(vList, labelList, legendList)
  1124  
  1125  	// TODO: Sort this slice
  1126  	systemItems, i := make([]c.PanelAnalyticsAgentsItem, len(osMap)), 0
  1127  	for system, count := range osMap {
  1128  		sSystem, ok := p.GetOSPhrase(system)
  1129  		if !ok {
  1130  			sSystem = system
  1131  		}
  1132  		systemItems[i] = c.PanelAnalyticsAgentsItem{
  1133  			Agent:         system,
  1134  			FriendlyAgent: sSystem,
  1135  			Count:         count,
  1136  		}
  1137  		i++
  1138  	}
  1139  
  1140  	pi := c.PanelAnalyticsDuoPage{bp, systemItems, graph, tr.Range}
  1141  	return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_systems", pi})
  1142  }
  1143  
  1144  func AnalyticsLanguages(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
  1145  	bp, ferr := PreAnalyticsDetail(w, r, u)
  1146  	if ferr != nil {
  1147  		return ferr
  1148  	}
  1149  	bp.AddScript("chartist/chartist-plugin-legend.min.js")
  1150  	bp.AddSheet("chartist/chartist-plugin-legend.css")
  1151  
  1152  	tr, e := analyticsTimeRange(r.FormValue("timeRange"))
  1153  	if e != nil {
  1154  		return c.LocalError(e.Error(), w, r, u)
  1155  	}
  1156  	revLabelList, labelList, viewMap := c.AnalyticsTimeRangeToLabelList(tr)
  1157  
  1158  	rows, e := qgen.NewAcc().Select("viewchunks_langs").Columns("count,lang,createdAt").DateCutoff("createdAt", tr.Quantity, tr.Unit).Query()
  1159  	if e != nil && e != sql.ErrNoRows {
  1160  		return c.InternalError(e, w, r)
  1161  	}
  1162  	vMap, langMap, e := analyticsRowsToDuoMap(rows, labelList, viewMap)
  1163  	if e != nil {
  1164  		return c.InternalError(e, w, r)
  1165  	}
  1166  	ovList := analyticsVMapToOVList(vMap)
  1167  
  1168  	ex := strings.Split(r.FormValue("ex"), ",")
  1169  	inEx := func(name string) bool {
  1170  		for _, e := range ex {
  1171  			if e == name {
  1172  				return true
  1173  			}
  1174  		}
  1175  		return false
  1176  	}
  1177  
  1178  	var vList [][]int64
  1179  	var legendList []string
  1180  	var i int
  1181  	for _, ovitem := range ovList {
  1182  		if inEx(ovitem.name) {
  1183  			continue
  1184  		}
  1185  		lName, ok := p.GetHumanLangPhrase(ovitem.name)
  1186  		if !ok {
  1187  			lName = ovitem.name
  1188  		}
  1189  		if inEx(lName) {
  1190  			continue
  1191  		}
  1192  
  1193  		viewList := make([]int64, len(revLabelList))
  1194  		for _, val := range revLabelList {
  1195  			viewList[i] = ovitem.viewMap[val]
  1196  		}
  1197  		vList = append(vList, viewList)
  1198  		legendList = append(legendList, lName)
  1199  		if i >= 6 {
  1200  			break
  1201  		}
  1202  		i++
  1203  	}
  1204  	graph := createTimeGraph(vList, labelList, legendList)
  1205  
  1206  	// TODO: Can we de-duplicate these analytics functions further?
  1207  	// TODO: Sort this slice
  1208  	var langItems []c.PanelAnalyticsAgentsItem
  1209  	for lang, count := range langMap {
  1210  		if inEx(lang) {
  1211  			continue
  1212  		}
  1213  		lLang, ok := p.GetHumanLangPhrase(lang)
  1214  		if !ok {
  1215  			lLang = lang
  1216  		}
  1217  		if inEx(lLang) {
  1218  			continue
  1219  		}
  1220  		langItems = append(langItems, c.PanelAnalyticsAgentsItem{
  1221  			Agent:         lang,
  1222  			FriendlyAgent: lLang,
  1223  			Count:         count,
  1224  		})
  1225  	}
  1226  
  1227  	pi := c.PanelAnalyticsDuoPage{bp, langItems, graph, tr.Range}
  1228  	return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_langs", pi})
  1229  }
  1230  
  1231  func AnalyticsReferrers(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
  1232  	bp, ferr := buildBasePage(w, r, u, "analytics", "analytics")
  1233  	if ferr != nil {
  1234  		return ferr
  1235  	}
  1236  	tr, e := analyticsTimeRange(r.FormValue("timeRange"))
  1237  	if e != nil {
  1238  		return c.LocalError(e.Error(), w, r, u)
  1239  	}
  1240  
  1241  	rows, e := qgen.NewAcc().Select("viewchunks_referrers").Columns("count,domain").DateCutoff("createdAt", tr.Quantity, tr.Unit).Query()
  1242  	if e != nil && e != sql.ErrNoRows {
  1243  		return c.InternalError(e, w, r)
  1244  	}
  1245  	refMap, e := analyticsRowsToRefMap(rows)
  1246  	if e != nil {
  1247  		return c.InternalError(e, w, r)
  1248  	}
  1249  	showSpam := r.FormValue("spam") == "1"
  1250  
  1251  	isSpammy := func(domain string) bool {
  1252  		for _, substr := range c.SpammyDomainBits {
  1253  			if strings.Contains(domain, substr) {
  1254  				return true
  1255  			}
  1256  		}
  1257  		return false
  1258  	}
  1259  
  1260  	// TODO: Sort this slice
  1261  	var refItems []c.PanelAnalyticsAgentsItem
  1262  	for domain, count := range refMap {
  1263  		sdomain := c.SanitiseSingleLine(domain)
  1264  		if !showSpam && isSpammy(sdomain) {
  1265  			continue
  1266  		}
  1267  		refItems = append(refItems, c.PanelAnalyticsAgentsItem{
  1268  			Agent: sdomain,
  1269  			Count: count,
  1270  		})
  1271  	}
  1272  
  1273  	pi := c.PanelAnalyticsReferrersPage{bp, refItems, tr.Range, showSpam}
  1274  	return renderTemplate("panel", w, r, bp.Header, c.Panel{bp, "panel_analytics_right", "analytics", "panel_analytics_referrers", pi})
  1275  }