Deployed multi-series charts across the entirety of the analytics panel.
Added the one year time range to the analytics panes. Dates are now shown on detail panes for Request, Topic and Post analytics instead of times for higher time ranges. The labels should now show up properly for the three month time range charts. The paginator should now work properly for login logs. Pushed a potential fix for subsequent pages with only one item not showing. up. Executing a search query should now change the title. Fixed a bug where the user agent parser choked on : characters. Fixed the ordering of items in the multi-series charts which caused the most important items to get booted out rather then the least important ones. Tweaked the padding on the User Manager items for Nox so they won't break onto multiple lines so readily. Fixed a potential issue with topic list titles. Fixed a potential crash bug in the Forum Analytics for deleted forums. Added the Count method to LoginLogStore. Continued work on the ElasticSearch mapping setup utility. Added the topic_list.search_head phrase. Added the panel_statistics_time_range_one_year phrase.
This commit is contained in:
parent
633c9ef2ec
commit
1fb497adf8
|
@ -175,32 +175,92 @@ type ESReply struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupData(client *elastic.Client) error {
|
func setupData(client *elastic.Client) error {
|
||||||
err := qgen.NewAcc().Select("topics").Cols("tid, title, content, createdBy, ipaddress").Each(func(rows *sql.Rows) error {
|
tcount := 4
|
||||||
var tid, createdBy int
|
errs := make(chan error)
|
||||||
var title, content, ip string
|
|
||||||
err := rows.Scan(&tid, &title, &content, &createdBy, &ip)
|
go func() {
|
||||||
if err != nil {
|
tin := make([]chan ESTopic, tcount)
|
||||||
return err
|
tf := func(tin chan ESTopic) {
|
||||||
|
for {
|
||||||
|
topic, more := <-tin
|
||||||
|
if !more {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_, err := client.Index().Index("topics").Type("_doc").Id(strconv.Itoa(topic.ID)).BodyJson(topic).Do(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
errs <- err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
go tf(tin[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
topic := ESTopic{tid, title, content, createdBy, ip}
|
oi := 0
|
||||||
_, err = client.Index().Index("topics").Type("_doc").Id(strconv.Itoa(tid)).BodyJson(topic).Do(context.Background())
|
err := qgen.NewAcc().Select("topics").Cols("tid, title, content, createdBy, ipaddress").Each(func(rows *sql.Rows) error {
|
||||||
return err
|
topic := ESTopic{}
|
||||||
})
|
err := rows.Scan(&topic.ID, &topic.Title, &topic.Content, &topic.CreatedBy, &topic.IPAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
tin[oi] <- topic
|
||||||
|
if oi < 3 {
|
||||||
|
oi++
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
close(tin[i])
|
||||||
|
}
|
||||||
|
errs <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
rin := make([]chan ESReply, tcount)
|
||||||
|
rf := func(rin chan ESReply) {
|
||||||
|
for {
|
||||||
|
reply, more := <-rin
|
||||||
|
if !more {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_, err := client.Index().Index("replies").Type("_doc").Id(strconv.Itoa(reply.ID)).BodyJson(reply).Do(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
errs <- err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
rf(rin[i])
|
||||||
|
}
|
||||||
|
oi := 0
|
||||||
|
err := qgen.NewAcc().Select("replies").Cols("rid, tid, content, createdBy, ipaddress").Each(func(rows *sql.Rows) error {
|
||||||
|
reply := ESReply{}
|
||||||
|
err := rows.Scan(&reply.ID, &reply.TID, &reply.Content, &reply.CreatedBy, &reply.IPAddress)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rin[oi] <- reply
|
||||||
|
if oi < 3 {
|
||||||
|
oi++
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
close(rin[i])
|
||||||
|
}
|
||||||
|
errs <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
fin := 0
|
||||||
|
for {
|
||||||
|
err := <-errs
|
||||||
|
if err == nil {
|
||||||
|
fin++
|
||||||
|
if fin == 2 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return qgen.NewAcc().Select("replies").Cols("rid, tid, content, createdBy, ipaddress").Each(func(rows *sql.Rows) error {
|
|
||||||
var rid, tid, createdBy int
|
|
||||||
var content, ip string
|
|
||||||
err := rows.Scan(&rid, &tid, &content, &createdBy, &ip)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
reply := ESReply{rid, tid, content, createdBy, ip}
|
|
||||||
_, err = client.Index().Index("replies").Type("_doc").Id(strconv.Itoa(rid)).BodyJson(reply).Do(context.Background())
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,17 +143,20 @@ func (log *LoginLogItem) Create() (id int, err error) {
|
||||||
|
|
||||||
type LoginLogStore interface {
|
type LoginLogStore interface {
|
||||||
GlobalCount() (logCount int)
|
GlobalCount() (logCount int)
|
||||||
|
Count(uid int) (logCount int)
|
||||||
GetOffset(uid int, offset int, perPage int) (logs []LoginLogItem, err error)
|
GetOffset(uid int, offset int, perPage int) (logs []LoginLogItem, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SQLLoginLogStore struct {
|
type SQLLoginLogStore struct {
|
||||||
count *sql.Stmt
|
count *sql.Stmt
|
||||||
|
countForUser *sql.Stmt
|
||||||
getOffsetByUser *sql.Stmt
|
getOffsetByUser *sql.Stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLoginLogStore(acc *qgen.Accumulator) (*SQLLoginLogStore, error) {
|
func NewLoginLogStore(acc *qgen.Accumulator) (*SQLLoginLogStore, error) {
|
||||||
return &SQLLoginLogStore{
|
return &SQLLoginLogStore{
|
||||||
count: acc.Count("login_logs").Prepare(),
|
count: acc.Count("login_logs").Prepare(),
|
||||||
|
countForUser: acc.Count("login_logs").Where("uid = ?").Prepare(),
|
||||||
getOffsetByUser: acc.Select("login_logs").Columns("lid, success, ipaddress, doneAt").Where("uid = ?").Orderby("doneAt DESC").Limit("?,?").Prepare(),
|
getOffsetByUser: acc.Select("login_logs").Columns("lid, success, ipaddress, doneAt").Where("uid = ?").Orderby("doneAt DESC").Limit("?,?").Prepare(),
|
||||||
}, acc.FirstError()
|
}, acc.FirstError()
|
||||||
}
|
}
|
||||||
|
@ -166,6 +169,14 @@ func (store *SQLLoginLogStore) GlobalCount() (logCount int) {
|
||||||
return logCount
|
return logCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (store *SQLLoginLogStore) Count(uid int) (logCount int) {
|
||||||
|
err := store.countForUser.QueryRow(uid).Scan(&logCount)
|
||||||
|
if err != nil {
|
||||||
|
LogError(err)
|
||||||
|
}
|
||||||
|
return logCount
|
||||||
|
}
|
||||||
|
|
||||||
func (store *SQLLoginLogStore) GetOffset(uid int, offset int, perPage int) (logs []LoginLogItem, err error) {
|
func (store *SQLLoginLogStore) GetOffset(uid int, offset int, perPage int) (logs []LoginLogItem, err error) {
|
||||||
rows, err := store.getOffsetByUser.Query(uid, offset, perPage)
|
rows, err := store.getOffsetByUser.Query(uid, offset, perPage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -277,6 +277,8 @@ type PanelAnalyticsPage struct {
|
||||||
Graph PanelTimeGraph
|
Graph PanelTimeGraph
|
||||||
ViewItems []PanelAnalyticsItem
|
ViewItems []PanelAnalyticsItem
|
||||||
TimeRange string
|
TimeRange string
|
||||||
|
Unit string
|
||||||
|
TimeType string
|
||||||
}
|
}
|
||||||
|
|
||||||
type PanelAnalyticsRoutesItem struct {
|
type PanelAnalyticsRoutesItem struct {
|
||||||
|
@ -287,9 +289,11 @@ type PanelAnalyticsRoutesItem struct {
|
||||||
type PanelAnalyticsRoutesPage struct {
|
type PanelAnalyticsRoutesPage struct {
|
||||||
*BasePanelPage
|
*BasePanelPage
|
||||||
ItemList []PanelAnalyticsRoutesItem
|
ItemList []PanelAnalyticsRoutesItem
|
||||||
|
Graph PanelTimeGraph
|
||||||
TimeRange string
|
TimeRange string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Rename the fields as this structure is being used in a generic way now
|
||||||
type PanelAnalyticsAgentsItem struct {
|
type PanelAnalyticsAgentsItem struct {
|
||||||
Agent string
|
Agent string
|
||||||
FriendlyAgent string
|
FriendlyAgent string
|
||||||
|
|
|
@ -880,10 +880,11 @@ func PageOffset(count int, page int, perPage int) (int, int, int) {
|
||||||
page = 1
|
page = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ? - This has been commented out as it created a bug in the user manager where the first user on a page wouldn't be accessible
|
||||||
// We don't want the offset to overflow the slices, if everything's in memory
|
// We don't want the offset to overflow the slices, if everything's in memory
|
||||||
if offset >= (count - 1) {
|
/*if offset >= (count - 1) {
|
||||||
offset = 0
|
offset = 0
|
||||||
}
|
}*/
|
||||||
return offset, page, lastPage
|
return offset, page, lastPage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -768,7 +768,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
for _, item := range StringToBytes(ua) {
|
for _, item := range StringToBytes(ua) {
|
||||||
if (item > 64 && item < 91) || (item > 96 && item < 123) {
|
if (item > 64 && item < 91) || (item > 96 && item < 123) {
|
||||||
buffer = append(buffer, item)
|
buffer = append(buffer, item)
|
||||||
} else if item == ' ' || item == '(' || item == ')' || item == '-' || (item > 47 && item < 58) || item == '_' || item == ';' || item == '.' || item == '+' || item == '~' || (item == ':' && bytes.Equal(buffer,[]byte("http"))) || item == ',' || item == '/' {
|
} else if item == ' ' || item == '(' || item == ')' || item == '-' || (item > 47 && item < 58) || item == '_' || item == ';' || item == ':' || item == '.' || item == '+' || item == '~' || (item == ':' && bytes.Equal(buffer,[]byte("http"))) || item == ',' || item == '/' {
|
||||||
if len(buffer) != 0 {
|
if len(buffer) != 0 {
|
||||||
if len(buffer) > 2 {
|
if len(buffer) > 2 {
|
||||||
// Use an unsafe zero copy conversion here just to use the switch, it's not safe for this string to escape from here, as it will get mutated, so do a regular string conversion in append
|
// Use an unsafe zero copy conversion here just to use the switch, it's not safe for this string to escape from here, as it will get mutated, so do a regular string conversion in append
|
||||||
|
|
|
@ -137,7 +137,6 @@ func (ins *MysqlInstaller) TableDefs() (err error) {
|
||||||
_, err = ins.db.Exec(string(data))
|
_, err = ins.db.Exec(string(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed query:", string(data))
|
fmt.Println("Failed query:", string(data))
|
||||||
panic("Failed query: " + string(data))
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -518,6 +518,7 @@
|
||||||
"quick_topic.add_file_button":"Add File",
|
"quick_topic.add_file_button":"Add File",
|
||||||
"quick_topic.cancel_button":"Cancel",
|
"quick_topic.cancel_button":"Cancel",
|
||||||
|
|
||||||
|
"topic_list.search_head":"Search Results",
|
||||||
"topic_list.create_topic_tooltip":"Create Topic",
|
"topic_list.create_topic_tooltip":"Create Topic",
|
||||||
"topic_list.create_topic_aria":"Create a topic",
|
"topic_list.create_topic_aria":"Create a topic",
|
||||||
"topic_list.moderate":"Moderate",
|
"topic_list.moderate":"Moderate",
|
||||||
|
@ -831,6 +832,7 @@
|
||||||
"panel_statistics_topic_counts_head":"Topic Counts",
|
"panel_statistics_topic_counts_head":"Topic Counts",
|
||||||
"panel_statistics_requests_head":"Requests",
|
"panel_statistics_requests_head":"Requests",
|
||||||
|
|
||||||
|
"panel_statistics_time_range_one_year":"1 year",
|
||||||
"panel_statistics_time_range_three_months":"3 months",
|
"panel_statistics_time_range_three_months":"3 months",
|
||||||
"panel_statistics_time_range_one_month":"1 month",
|
"panel_statistics_time_range_one_month":"1 month",
|
||||||
"panel_statistics_time_range_one_week":"1 week",
|
"panel_statistics_time_range_one_week":"1 week",
|
||||||
|
|
|
@ -6,7 +6,21 @@
|
||||||
// TODO: Load rawLabels and seriesData dynamically rather than potentially fiddling with nonces for the CSP?
|
// TODO: Load rawLabels and seriesData dynamically rather than potentially fiddling with nonces for the CSP?
|
||||||
function buildStatsChart(rawLabels, seriesData, timeRange, legendNames) {
|
function buildStatsChart(rawLabels, seriesData, timeRange, legendNames) {
|
||||||
let labels = [];
|
let labels = [];
|
||||||
if(timeRange=="one-month") {
|
if(timeRange=="one-year") {
|
||||||
|
labels = ["today","01 months"];
|
||||||
|
for(let i = 2; i < 12; i++) {
|
||||||
|
let label = "0" + i + " months";
|
||||||
|
if(label.length > "01 months".length) label = label.substr(1);
|
||||||
|
labels.push(label);
|
||||||
|
}
|
||||||
|
} else if(timeRange=="three-months") {
|
||||||
|
labels = ["today","01 days"];
|
||||||
|
for(let i = 2; i < 90; i = i + 3) {
|
||||||
|
let label = "0" + i + " days";
|
||||||
|
if(label.length > "01 days".length) label = label.substr(1);
|
||||||
|
labels.push(label);
|
||||||
|
}
|
||||||
|
} else if(timeRange=="one-month") {
|
||||||
labels = ["today","01 days"];
|
labels = ["today","01 days"];
|
||||||
for(let i = 2; i < 30; i++) {
|
for(let i = 2; i < 30; i++) {
|
||||||
let label = "0" + i + " days";
|
let label = "0" + i + " days";
|
||||||
|
|
|
@ -234,7 +234,6 @@ function runWebSockets() {
|
||||||
console.log("empty topic list");
|
console.log("empty topic list");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Fix the data race where the function hasn't been loaded yet
|
// TODO: Fix the data race where the function hasn't been loaded yet
|
||||||
let renTopic = Template_topics_topic(topic);
|
let renTopic = Template_topics_topic(topic);
|
||||||
$(".topic_row[data-tid='"+topic.ID+"']").addClass("ajax_topic_dupe");
|
$(".topic_row[data-tid='"+topic.ID+"']").addClass("ajax_topic_dupe");
|
||||||
|
@ -318,7 +317,7 @@ function PageOffset(count, page, perPage) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't want the offset to overflow the slices, if everything's in memory
|
// We don't want the offset to overflow the slices, if everything's in memory
|
||||||
if(offset >= (count - 1)) offset = 0;
|
//if(offset >= (count - 1)) offset = 0;
|
||||||
return {Offset:offset, Page:page, LastPage:lastPage}
|
return {Offset:offset, Page:page, LastPage:lastPage}
|
||||||
}
|
}
|
||||||
function LastPage(count, perPage) {
|
function LastPage(count, perPage) {
|
||||||
|
@ -517,6 +516,8 @@ function mainInit(){
|
||||||
for(let i = 0; i < topics.length;i++) out += Template_topics_topic(topics[i]);
|
for(let i = 0; i < topics.length;i++) out += Template_topics_topic(topics[i]);
|
||||||
$(".topic_list").html(out);
|
$(".topic_list").html(out);
|
||||||
|
|
||||||
|
document.title = phraseBox["topic_list"]["topic_list.search_head"];
|
||||||
|
$(".topic_list_title h1").text(phraseBox["topic_list"]["topic_list.search_head"]);
|
||||||
let obj = {Title: document.title, Url: url+q};
|
let obj = {Title: document.title, Url: url+q};
|
||||||
history.pushState(obj, obj.Title, obj.Url);
|
history.pushState(obj, obj.Title, obj.Url);
|
||||||
rebuildPaginator(data.LastPage);
|
rebuildPaginator(data.LastPage);
|
||||||
|
@ -1046,6 +1047,17 @@ function mainInit(){
|
||||||
this.innerText = formattedTime;
|
this.innerText = formattedTime;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(".unix_to_date").each(function(){
|
||||||
|
// TODO: Localise this
|
||||||
|
let monthList = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
|
||||||
|
let date = new Date(this.innerText * 1000);
|
||||||
|
console.log("date: ", date);
|
||||||
|
let day = "0" + date.getDate();
|
||||||
|
let formattedTime = monthList[date.getMonth()] + " " + day.substr(-2) + " " + date.getFullYear();
|
||||||
|
console.log("formattedTime:", formattedTime);
|
||||||
|
this.innerText = formattedTime;
|
||||||
|
});
|
||||||
|
|
||||||
this.onkeyup = function(event) {
|
this.onkeyup = function(event) {
|
||||||
if(event.which == 37) this.querySelectorAll("#prevFloat a")[0].click();
|
if(event.which == 37) this.querySelectorAll("#prevFloat a")[0].click();
|
||||||
if(event.which == 39) this.querySelectorAll("#nextFloat a")[0].click();
|
if(event.which == 39) this.querySelectorAll("#nextFloat a")[0].click();
|
||||||
|
|
|
@ -560,7 +560,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
for _, item := range StringToBytes(ua) {
|
for _, item := range StringToBytes(ua) {
|
||||||
if (item > 64 && item < 91) || (item > 96 && item < 123) {
|
if (item > 64 && item < 91) || (item > 96 && item < 123) {
|
||||||
buffer = append(buffer, item)
|
buffer = append(buffer, item)
|
||||||
} else if item == ' ' || item == '(' || item == ')' || item == '-' || (item > 47 && item < 58) || item == '_' || item == ';' || item == '.' || item == '+' || item == '~' || (item == ':' && bytes.Equal(buffer,[]byte("http"))) || item == ',' || item == '/' {
|
} else if item == ' ' || item == '(' || item == ')' || item == '-' || (item > 47 && item < 58) || item == '_' || item == ';' || item == ':' || item == '.' || item == '+' || item == '~' || (item == ':' && bytes.Equal(buffer,[]byte("http"))) || item == ',' || item == '/' {
|
||||||
if len(buffer) != 0 {
|
if len(buffer) != 0 {
|
||||||
if len(buffer) > 2 {
|
if len(buffer) > 2 {
|
||||||
// Use an unsafe zero copy conversion here just to use the switch, it's not safe for this string to escape from here, as it will get mutated, so do a regular string conversion in append
|
// Use an unsafe zero copy conversion here just to use the switch, it's not safe for this string to escape from here, as it will get mutated, so do a regular string conversion in append
|
||||||
|
|
|
@ -705,7 +705,7 @@ func AccountEditEmailTokenSubmit(w http.ResponseWriter, r *http.Request, user co
|
||||||
func AccountLogins(w http.ResponseWriter, r *http.Request, user common.User, header *common.Header) common.RouteError {
|
func AccountLogins(w http.ResponseWriter, r *http.Request, user common.User, header *common.Header) common.RouteError {
|
||||||
accountEditHead("account_logins", w, r, &user, header)
|
accountEditHead("account_logins", w, r, &user, header)
|
||||||
|
|
||||||
logCount := common.LoginLogs.GlobalCount()
|
logCount := common.LoginLogs.Count(user.ID)
|
||||||
page, _ := strconv.Atoi(r.FormValue("page"))
|
page, _ := strconv.Atoi(r.FormValue("page"))
|
||||||
perPage := 12
|
perPage := 12
|
||||||
offset, page, lastPage := common.PageOffset(logCount, page, perPage)
|
offset, page, lastPage := common.PageOffset(logCount, page, perPage)
|
||||||
|
|
|
@ -31,6 +31,12 @@ func analyticsTimeRange(rawTimeRange string) (timeRange AnalyticsTimeRange, err
|
||||||
|
|
||||||
switch rawTimeRange {
|
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
|
// 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":
|
||||||
|
timeRange.Quantity = 12
|
||||||
|
timeRange.Unit = "month"
|
||||||
|
timeRange.Slices = 12
|
||||||
|
timeRange.SliceWidth = 60 * 60 * 24 * 30
|
||||||
|
timeRange.Range = "one-year"
|
||||||
case "three-months":
|
case "three-months":
|
||||||
timeRange.Quantity = 90
|
timeRange.Quantity = 90
|
||||||
timeRange.Unit = "day"
|
timeRange.Unit = "day"
|
||||||
|
@ -153,8 +159,11 @@ func AnalyticsViews(w http.ResponseWriter, r *http.Request, user common.User) co
|
||||||
}
|
}
|
||||||
graph := common.PanelTimeGraph{Series: [][]int64{viewList}, Labels: labelList}
|
graph := common.PanelTimeGraph{Series: [][]int64{viewList}, Labels: labelList}
|
||||||
common.DebugLogf("graph: %+v\n", graph)
|
common.DebugLogf("graph: %+v\n", graph)
|
||||||
|
var ttime string
|
||||||
pi := common.PanelAnalyticsPage{basePage, graph, viewItems, timeRange.Range}
|
if timeRange.Range == "six-hours" || timeRange.Range == "twelve-hours" || timeRange.Range == "one-day" {
|
||||||
|
ttime = "time"
|
||||||
|
}
|
||||||
|
pi := common.PanelAnalyticsPage{basePage, graph, viewItems, timeRange.Range, timeRange.Unit, ttime}
|
||||||
return renderTemplate("panel_analytics_views", w, r, basePage.Header, &pi)
|
return renderTemplate("panel_analytics_views", w, r, basePage.Header, &pi)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,7 +425,7 @@ func AnalyticsTopics(w http.ResponseWriter, r *http.Request, user common.User) c
|
||||||
}
|
}
|
||||||
graph := common.PanelTimeGraph{Series: [][]int64{viewList}, Labels: labelList}
|
graph := common.PanelTimeGraph{Series: [][]int64{viewList}, Labels: labelList}
|
||||||
common.DebugLogf("graph: %+v\n", graph)
|
common.DebugLogf("graph: %+v\n", graph)
|
||||||
pi := common.PanelAnalyticsPage{basePage, graph, viewItems, timeRange.Range}
|
pi := common.PanelAnalyticsPage{basePage, graph, viewItems, timeRange.Range, timeRange.Unit, "time"}
|
||||||
return renderTemplate("panel_analytics_topics", w, r, basePage.Header, &pi)
|
return renderTemplate("panel_analytics_topics", w, r, basePage.Header, &pi)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -449,37 +458,10 @@ func AnalyticsPosts(w http.ResponseWriter, r *http.Request, user common.User) co
|
||||||
}
|
}
|
||||||
graph := common.PanelTimeGraph{Series: [][]int64{viewList}, Labels: labelList}
|
graph := common.PanelTimeGraph{Series: [][]int64{viewList}, Labels: labelList}
|
||||||
common.DebugLogf("graph: %+v\n", graph)
|
common.DebugLogf("graph: %+v\n", graph)
|
||||||
pi := common.PanelAnalyticsPage{basePage, graph, viewItems, timeRange.Range}
|
pi := common.PanelAnalyticsPage{basePage, graph, viewItems, timeRange.Range, timeRange.Unit, "time"}
|
||||||
return renderTemplate("panel_analytics_posts", w, r, basePage.Header, &pi)
|
return renderTemplate("panel_analytics_posts", w, r, basePage.Header, &pi)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*func analyticsRowsToViewMap(rows *sql.Rows, labelList []int64, viewMap map[int64]int64) (map[int64]int64, error) {
|
|
||||||
defer rows.Close()
|
|
||||||
for rows.Next() {
|
|
||||||
var count int64
|
|
||||||
var createdAt time.Time
|
|
||||||
err := rows.Scan(&count, &createdAt)
|
|
||||||
if err != nil {
|
|
||||||
return viewMap, err
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return viewMap, rows.Err()
|
|
||||||
}*/
|
|
||||||
|
|
||||||
func analyticsRowsToNameMap(rows *sql.Rows) (map[string]int, error) {
|
func analyticsRowsToNameMap(rows *sql.Rows) (map[string]int, error) {
|
||||||
nameMap := make(map[string]int)
|
nameMap := make(map[string]int)
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
@ -540,24 +522,96 @@ func analyticsRowsToDuoMap(rows *sql.Rows, labelList []int64, viewMap map[int64]
|
||||||
return vMap, nameMap, rows.Err()
|
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
|
||||||
|
for name, viewMap := range vMap {
|
||||||
|
var totcount int
|
||||||
|
for _, count := range viewMap {
|
||||||
|
totcount += int(count)
|
||||||
|
}
|
||||||
|
ovList = append(ovList, OVItem{name, totcount, viewMap})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
var tOVList []OVItem
|
||||||
|
for i := len(ovList) - 1; i >= 0; i-- {
|
||||||
|
tOVList = append(tOVList, ovList[i])
|
||||||
|
}
|
||||||
|
return tOVList
|
||||||
|
}
|
||||||
|
|
||||||
func AnalyticsForums(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
func AnalyticsForums(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||||
basePage, ferr := buildBasePage(w, r, &user, "analytics", "analytics")
|
basePage, ferr := PreAnalyticsDetail(w, r, &user)
|
||||||
if ferr != nil {
|
if ferr != nil {
|
||||||
return ferr
|
return ferr
|
||||||
}
|
}
|
||||||
|
basePage.AddScript("chartist/chartist-plugin-legend.min.js")
|
||||||
|
basePage.AddSheet("chartist/chartist-plugin-legend.css")
|
||||||
|
|
||||||
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
|
timeRange, err := analyticsTimeRange(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 := analyticsTimeRangeToLabelList(timeRange)
|
||||||
|
|
||||||
rows, err := qgen.NewAcc().Select("viewchunks_forums").Columns("count, forum").Where("forum != ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
|
rows, err := qgen.NewAcc().Select("viewchunks_forums").Columns("count, forum, createdAt").Where("forum != ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
return common.InternalError(err, w, r)
|
return common.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
forumMap, err := analyticsRowsToNameMap(rows)
|
vMap, forumMap, err := analyticsRowsToDuoMap(rows, labelList, viewMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.InternalError(err, w, r)
|
return common.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
|
ovList := analyticsVMapToOVList(vMap)
|
||||||
|
|
||||||
|
var vList [][]int64
|
||||||
|
var legendList []string
|
||||||
|
var i int
|
||||||
|
for _, ovitem := range ovList {
|
||||||
|
var viewList []int64
|
||||||
|
for _, value := range revLabelList {
|
||||||
|
viewList = append(viewList, ovitem.viewMap[value])
|
||||||
|
}
|
||||||
|
vList = append(vList, viewList)
|
||||||
|
fid, err := strconv.Atoi(ovitem.name)
|
||||||
|
if err != nil {
|
||||||
|
return common.InternalError(err, w, r)
|
||||||
|
}
|
||||||
|
var lName string
|
||||||
|
forum, err := common.Forums.Get(fid)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
// TODO: Localise this
|
||||||
|
lName = "Deleted Forum"
|
||||||
|
} else if err != nil {
|
||||||
|
return common.InternalError(err, w, r)
|
||||||
|
} else {
|
||||||
|
lName = forum.Name
|
||||||
|
}
|
||||||
|
legendList = append(legendList, lName)
|
||||||
|
if i >= 6 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
graph := common.PanelTimeGraph{Series: vList, Labels: labelList, Legends: legendList}
|
||||||
|
common.DebugLogf("graph: %+v\n", graph)
|
||||||
|
|
||||||
// TODO: Sort this slice
|
// TODO: Sort this slice
|
||||||
var forumItems []common.PanelAnalyticsAgentsItem
|
var forumItems []common.PanelAnalyticsAgentsItem
|
||||||
|
@ -566,39 +620,68 @@ func AnalyticsForums(w http.ResponseWriter, r *http.Request, user common.User) c
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.InternalError(err, w, r)
|
return common.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
|
var lName string
|
||||||
forum, err := common.Forums.Get(fid)
|
forum, err := common.Forums.Get(fid)
|
||||||
if err != nil {
|
if err == sql.ErrNoRows {
|
||||||
|
// TODO: Localise this
|
||||||
|
lName = "Deleted Forum"
|
||||||
|
} else if err != nil {
|
||||||
return common.InternalError(err, w, r)
|
return common.InternalError(err, w, r)
|
||||||
|
} else {
|
||||||
|
lName = forum.Name
|
||||||
}
|
}
|
||||||
forumItems = append(forumItems, common.PanelAnalyticsAgentsItem{
|
forumItems = append(forumItems, common.PanelAnalyticsAgentsItem{
|
||||||
Agent: sfid,
|
Agent: sfid,
|
||||||
FriendlyAgent: forum.Name,
|
FriendlyAgent: lName,
|
||||||
Count: count,
|
Count: count,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pi := common.PanelAnalyticsAgentsPage{basePage, forumItems, timeRange.Range}
|
pi := common.PanelAnalyticsDuoPage{basePage, forumItems, graph, timeRange.Range}
|
||||||
return renderTemplate("panel_analytics_forums", w, r, basePage.Header, &pi)
|
return renderTemplate("panel_analytics_forums", w, r, basePage.Header, &pi)
|
||||||
}
|
}
|
||||||
|
|
||||||
func AnalyticsRoutes(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
func AnalyticsRoutes(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||||
basePage, ferr := buildBasePage(w, r, &user, "analytics", "analytics")
|
basePage, ferr := PreAnalyticsDetail(w, r, &user)
|
||||||
if ferr != nil {
|
if ferr != nil {
|
||||||
return ferr
|
return ferr
|
||||||
}
|
}
|
||||||
|
basePage.AddScript("chartist/chartist-plugin-legend.min.js")
|
||||||
|
basePage.AddSheet("chartist/chartist-plugin-legend.css")
|
||||||
|
|
||||||
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
|
timeRange, err := analyticsTimeRange(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 := analyticsTimeRangeToLabelList(timeRange)
|
||||||
|
|
||||||
rows, err := qgen.NewAcc().Select("viewchunks").Columns("count, route").Where("route != ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
|
rows, err := qgen.NewAcc().Select("viewchunks").Columns("count, route, createdAt").Where("route != ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
return common.InternalError(err, w, r)
|
return common.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
routeMap, err := analyticsRowsToNameMap(rows)
|
vMap, routeMap, err := analyticsRowsToDuoMap(rows, labelList, viewMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.InternalError(err, w, r)
|
return common.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
|
ovList := analyticsVMapToOVList(vMap)
|
||||||
|
|
||||||
|
var vList [][]int64
|
||||||
|
var legendList []string
|
||||||
|
var i int
|
||||||
|
for _, ovitem := range ovList {
|
||||||
|
var viewList []int64
|
||||||
|
for _, value := range revLabelList {
|
||||||
|
viewList = append(viewList, ovitem.viewMap[value])
|
||||||
|
}
|
||||||
|
vList = append(vList, viewList)
|
||||||
|
legendList = append(legendList, ovitem.name)
|
||||||
|
if i >= 6 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
graph := common.PanelTimeGraph{Series: vList, Labels: labelList, Legends: legendList}
|
||||||
|
common.DebugLogf("graph: %+v\n", graph)
|
||||||
|
|
||||||
// TODO: Sort this slice
|
// TODO: Sort this slice
|
||||||
var routeItems []common.PanelAnalyticsRoutesItem
|
var routeItems []common.PanelAnalyticsRoutesItem
|
||||||
|
@ -609,16 +692,10 @@ func AnalyticsRoutes(w http.ResponseWriter, r *http.Request, user common.User) c
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pi := common.PanelAnalyticsRoutesPage{basePage, routeItems, timeRange.Range}
|
pi := common.PanelAnalyticsRoutesPage{basePage, routeItems, graph, timeRange.Range}
|
||||||
return renderTemplate("panel_analytics_routes", w, r, basePage.Header, &pi)
|
return renderTemplate("panel_analytics_routes", w, r, basePage.Header, &pi)
|
||||||
}
|
}
|
||||||
|
|
||||||
type OVItem struct {
|
|
||||||
name string
|
|
||||||
count int
|
|
||||||
viewMap map[int64]int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trialling multi-series charts
|
// Trialling multi-series charts
|
||||||
func AnalyticsAgents(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
func AnalyticsAgents(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||||
basePage, ferr := PreAnalyticsDetail(w, r, &user)
|
basePage, ferr := PreAnalyticsDetail(w, r, &user)
|
||||||
|
@ -642,26 +719,7 @@ func AnalyticsAgents(w http.ResponseWriter, r *http.Request, user common.User) c
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.InternalError(err, w, r)
|
return common.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
|
ovList := analyticsVMapToOVList(vMap)
|
||||||
// Order the map
|
|
||||||
var ovList []OVItem
|
|
||||||
for name, viewMap := range vMap {
|
|
||||||
var totcount int
|
|
||||||
for _, count := range viewMap {
|
|
||||||
totcount += int(count)
|
|
||||||
}
|
|
||||||
ovList = append(ovList, OVItem{name, totcount, viewMap})
|
|
||||||
}
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var vList [][]int64
|
var vList [][]int64
|
||||||
var legendList []string
|
var legendList []string
|
||||||
|
@ -704,23 +762,50 @@ func AnalyticsAgents(w http.ResponseWriter, r *http.Request, user common.User) c
|
||||||
}
|
}
|
||||||
|
|
||||||
func AnalyticsSystems(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
func AnalyticsSystems(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||||
basePage, ferr := buildBasePage(w, r, &user, "analytics", "analytics")
|
basePage, ferr := PreAnalyticsDetail(w, r, &user)
|
||||||
if ferr != nil {
|
if ferr != nil {
|
||||||
return ferr
|
return ferr
|
||||||
}
|
}
|
||||||
|
basePage.AddScript("chartist/chartist-plugin-legend.min.js")
|
||||||
|
basePage.AddSheet("chartist/chartist-plugin-legend.css")
|
||||||
|
|
||||||
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
|
timeRange, err := analyticsTimeRange(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 := analyticsTimeRangeToLabelList(timeRange)
|
||||||
|
|
||||||
rows, err := qgen.NewAcc().Select("viewchunks_systems").Columns("count, system").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
|
rows, err := qgen.NewAcc().Select("viewchunks_systems").Columns("count, system, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
return common.InternalError(err, w, r)
|
return common.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
osMap, err := analyticsRowsToNameMap(rows)
|
vMap, osMap, err := analyticsRowsToDuoMap(rows, labelList, viewMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.InternalError(err, w, r)
|
return common.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
|
ovList := analyticsVMapToOVList(vMap)
|
||||||
|
|
||||||
|
var vList [][]int64
|
||||||
|
var legendList []string
|
||||||
|
var i int
|
||||||
|
for _, ovitem := range ovList {
|
||||||
|
var viewList []int64
|
||||||
|
for _, value := range revLabelList {
|
||||||
|
viewList = append(viewList, ovitem.viewMap[value])
|
||||||
|
}
|
||||||
|
vList = append(vList, viewList)
|
||||||
|
lName, ok := phrases.GetOSPhrase(ovitem.name)
|
||||||
|
if !ok {
|
||||||
|
lName = ovitem.name
|
||||||
|
}
|
||||||
|
legendList = append(legendList, lName)
|
||||||
|
if i >= 6 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
graph := common.PanelTimeGraph{Series: vList, Labels: labelList, Legends: legendList}
|
||||||
|
common.DebugLogf("graph: %+v\n", graph)
|
||||||
|
|
||||||
// TODO: Sort this slice
|
// TODO: Sort this slice
|
||||||
var systemItems []common.PanelAnalyticsAgentsItem
|
var systemItems []common.PanelAnalyticsAgentsItem
|
||||||
|
@ -736,28 +821,55 @@ func AnalyticsSystems(w http.ResponseWriter, r *http.Request, user common.User)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pi := common.PanelAnalyticsAgentsPage{basePage, systemItems, timeRange.Range}
|
pi := common.PanelAnalyticsDuoPage{basePage, systemItems, graph, timeRange.Range}
|
||||||
return renderTemplate("panel_analytics_systems", w, r, basePage.Header, &pi)
|
return renderTemplate("panel_analytics_systems", w, r, basePage.Header, &pi)
|
||||||
}
|
}
|
||||||
|
|
||||||
func AnalyticsLanguages(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
func AnalyticsLanguages(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||||
basePage, ferr := buildBasePage(w, r, &user, "analytics", "analytics")
|
basePage, ferr := PreAnalyticsDetail(w, r, &user)
|
||||||
if ferr != nil {
|
if ferr != nil {
|
||||||
return ferr
|
return ferr
|
||||||
}
|
}
|
||||||
|
basePage.AddScript("chartist/chartist-plugin-legend.min.js")
|
||||||
|
basePage.AddSheet("chartist/chartist-plugin-legend.css")
|
||||||
|
|
||||||
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
|
timeRange, err := analyticsTimeRange(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 := analyticsTimeRangeToLabelList(timeRange)
|
||||||
|
|
||||||
rows, err := qgen.NewAcc().Select("viewchunks_langs").Columns("count, lang").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
|
rows, err := qgen.NewAcc().Select("viewchunks_langs").Columns("count, lang, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
return common.InternalError(err, w, r)
|
return common.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
langMap, err := analyticsRowsToNameMap(rows)
|
vMap, langMap, err := analyticsRowsToDuoMap(rows, labelList, viewMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.InternalError(err, w, r)
|
return common.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
|
ovList := analyticsVMapToOVList(vMap)
|
||||||
|
|
||||||
|
var vList [][]int64
|
||||||
|
var legendList []string
|
||||||
|
var i int
|
||||||
|
for _, ovitem := range ovList {
|
||||||
|
var viewList []int64
|
||||||
|
for _, value := range revLabelList {
|
||||||
|
viewList = append(viewList, ovitem.viewMap[value])
|
||||||
|
}
|
||||||
|
vList = append(vList, viewList)
|
||||||
|
lName, ok := phrases.GetHumanLangPhrase(ovitem.name)
|
||||||
|
if !ok {
|
||||||
|
lName = ovitem.name
|
||||||
|
}
|
||||||
|
legendList = append(legendList, lName)
|
||||||
|
if i >= 6 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
graph := common.PanelTimeGraph{Series: vList, Labels: labelList, Legends: legendList}
|
||||||
|
common.DebugLogf("graph: %+v\n", graph)
|
||||||
|
|
||||||
// TODO: Can we de-duplicate these analytics functions further?
|
// TODO: Can we de-duplicate these analytics functions further?
|
||||||
// TODO: Sort this slice
|
// TODO: Sort this slice
|
||||||
|
@ -774,7 +886,7 @@ func AnalyticsLanguages(w http.ResponseWriter, r *http.Request, user common.User
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pi := common.PanelAnalyticsAgentsPage{basePage, langItems, timeRange.Range}
|
pi := common.PanelAnalyticsDuoPage{basePage, langItems, graph, timeRange.Range}
|
||||||
return renderTemplate("panel_analytics_langs", w, r, basePage.Header, &pi)
|
return renderTemplate("panel_analytics_langs", w, r, basePage.Header, &pi)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ func TopicListMostViewed(w http.ResponseWriter, r *http.Request, user common.Use
|
||||||
|
|
||||||
// TODO: Implement search
|
// TODO: Implement search
|
||||||
func TopicListCommon(w http.ResponseWriter, r *http.Request, user common.User, header *common.Header, torder string, tsorder string) common.RouteError {
|
func TopicListCommon(w http.ResponseWriter, r *http.Request, user common.User, header *common.Header, torder string, tsorder string) common.RouteError {
|
||||||
|
header.Title = phrases.GetTitlePhrase("topics")
|
||||||
header.Zone = "topics"
|
header.Zone = "topics"
|
||||||
header.Path = "/topics/"
|
header.Path = "/topics/"
|
||||||
header.MetaDesc = header.Settings["meta_desc"].(string)
|
header.MetaDesc = header.Settings["meta_desc"].(string)
|
||||||
|
@ -189,7 +190,6 @@ func TopicListCommon(w http.ResponseWriter, r *http.Request, user common.User, h
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
header.Title = phrases.GetTitlePhrase("topics")
|
|
||||||
pi := common.TopicListPage{header, topicList, forumList, common.Config.DefaultForum, common.TopicListSort{torder, false}, paginator}
|
pi := common.TopicListPage{header, topicList, forumList, common.Config.DefaultForum, common.TopicListSort{torder, false}, paginator}
|
||||||
return renderTemplate("topics", w, r, header, pi)
|
return renderTemplate("topics", w, r, header, pi)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<div id="panel_analytics_forums_chart" class="colstack_graph_holder">
|
||||||
|
<div class="ct_chart"></div>
|
||||||
|
</div>
|
||||||
<div id="panel_analytics_routes" class="colstack_item rowlist">
|
<div id="panel_analytics_routes" class="colstack_item rowlist">
|
||||||
{{range .ItemList}}
|
{{range .ItemList}}
|
||||||
<div class="rowitem panel_compactrow editable_parent">
|
<div class="rowitem panel_compactrow editable_parent">
|
||||||
|
@ -20,4 +23,5 @@
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
{{template "panel_analytics_script.html" . }}
|
||||||
{{template "footer.html" . }}
|
{{template "footer.html" . }}
|
||||||
|
|
|
@ -10,6 +10,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<div id="panel_analytics_langs_chart" class="colstack_graph_holder">
|
||||||
|
<div class="ct_chart"></div>
|
||||||
|
</div>
|
||||||
<div id="panel_analytics_langs" class="colstack_item rowlist">
|
<div id="panel_analytics_langs" class="colstack_item rowlist">
|
||||||
{{range .ItemList}}
|
{{range .ItemList}}
|
||||||
<div class="rowitem panel_compactrow editable_parent">
|
<div class="rowitem panel_compactrow editable_parent">
|
||||||
|
@ -20,4 +23,5 @@
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
{{template "panel_analytics_script.html" . }}
|
||||||
{{template "footer.html" . }}
|
{{template "footer.html" . }}
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<div id="panel_analytics_posts_table" class="colstack_item rowlist" aria-label="{{lang "panel_statistics_post_counts_table_aria"}}">
|
<div id="panel_analytics_posts_table" class="colstack_item rowlist" aria-label="{{lang "panel_statistics_post_counts_table_aria"}}">
|
||||||
{{range .ViewItems}}
|
{{range .ViewItems}}
|
||||||
<div class="rowitem panel_compactrow editable_parent">
|
<div class="rowitem panel_compactrow editable_parent">
|
||||||
<a class="panel_upshift unix_to_24_hour_time">{{.Time}}</a>
|
<a class="panel_upshift {{if or (or (or (eq $.TimeRange "six-hours") (eq $.TimeRange "twelve-hours")) (eq $.TimeRange "one-day")) (eq $.TimeRange "two-days")}}unix_to_24_hour_time{{else}}unix_to_date{{end}}">{{.Time}}</a>
|
||||||
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_statistics_posts_suffix"}}</span>
|
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_statistics_posts_suffix"}}</span>
|
||||||
</div>
|
</div>
|
||||||
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_statistics_post_counts_no_post_counts"}}</div>{{end}}
|
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_statistics_post_counts_no_post_counts"}}</div>{{end}}
|
||||||
|
|
|
@ -10,6 +10,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<div id="panel_analytics_routes_chart" class="colstack_graph_holder">
|
||||||
|
<div class="ct_chart"></div>
|
||||||
|
</div>
|
||||||
<div id="panel_analytics_routes" class="colstack_item rowlist">
|
<div id="panel_analytics_routes" class="colstack_item rowlist">
|
||||||
{{range .ItemList}}
|
{{range .ItemList}}
|
||||||
<div class="rowitem panel_compactrow editable_parent">
|
<div class="rowitem panel_compactrow editable_parent">
|
||||||
|
@ -20,4 +23,5 @@
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
{{template "panel_analytics_script.html" . }}
|
||||||
{{template "footer.html" . }}
|
{{template "footer.html" . }}
|
||||||
|
|
|
@ -10,5 +10,5 @@ let seriesData = [{{range .Graph.Series}}[{{range .}}
|
||||||
let legendNames = [{{range .Graph.Legends}}
|
let legendNames = [{{range .Graph.Legends}}
|
||||||
{{.}},{{end}}
|
{{.}},{{end}}
|
||||||
];
|
];
|
||||||
buildStatsChart(rawLabels, seriesData.reverse(), "{{.TimeRange}}",legendNames.reverse());
|
buildStatsChart(rawLabels, seriesData, "{{.TimeRange}}",legendNames);
|
||||||
</script>
|
</script>
|
|
@ -10,6 +10,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<div id="panel_analytics_systems_chart" class="colstack_graph_holder">
|
||||||
|
<div class="ct_chart"></div>
|
||||||
|
</div>
|
||||||
<div id="panel_analytics_systems" class="colstack_item rowlist">
|
<div id="panel_analytics_systems" class="colstack_item rowlist">
|
||||||
{{range .ItemList}}
|
{{range .ItemList}}
|
||||||
<div class="rowitem panel_compactrow editable_parent">
|
<div class="rowitem panel_compactrow editable_parent">
|
||||||
|
@ -20,4 +23,5 @@
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
{{template "panel_analytics_script.html" . }}
|
||||||
{{template "footer.html" . }}
|
{{template "footer.html" . }}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<select class="timeRangeSelector to_right" name="timeRange">
|
<select class="timeRangeSelector to_right" name="timeRange">
|
||||||
|
<option val="one-year"{{if eq .TimeRange "one-year"}} selected{{end}}>{{lang "panel_statistics_time_range_one_year"}}</option>
|
||||||
<option val="three-months"{{if eq .TimeRange "three-months"}} selected{{end}}>{{lang "panel_statistics_time_range_three_months"}}</option>
|
<option val="three-months"{{if eq .TimeRange "three-months"}} selected{{end}}>{{lang "panel_statistics_time_range_three_months"}}</option>
|
||||||
<option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>{{lang "panel_statistics_time_range_one_month"}}</option>
|
<option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>{{lang "panel_statistics_time_range_one_month"}}</option>
|
||||||
<option val="one-week"{{if eq .TimeRange "one-week"}} selected{{end}}>{{lang "panel_statistics_time_range_one_week"}}</option>
|
<option val="one-week"{{if eq .TimeRange "one-week"}} selected{{end}}>{{lang "panel_statistics_time_range_one_week"}}</option>
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<div id="panel_analytics_topics_table" class="colstack_item rowlist" aria-label="{{lang "panel_statistics_topic_counts_table_aria"}}">
|
<div id="panel_analytics_topics_table" class="colstack_item rowlist" aria-label="{{lang "panel_statistics_topic_counts_table_aria"}}">
|
||||||
{{range .ViewItems}}
|
{{range .ViewItems}}
|
||||||
<div class="rowitem panel_compactrow editable_parent">
|
<div class="rowitem panel_compactrow editable_parent">
|
||||||
<a class="panel_upshift unix_to_24_hour_time">{{.Time}}</a>
|
<a class="panel_upshift {{if or (or (or (eq $.TimeRange "six-hours") (eq $.TimeRange "twelve-hours")) (eq $.TimeRange "one-day")) (eq $.TimeRange "two-days")}}unix_to_24_hour_time{{else}}unix_to_date{{end}}">{{.Time}}</a>
|
||||||
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_statistics_topics_suffix"}}</span>
|
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_statistics_topics_suffix"}}</span>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<div id="panel_analytics_views_table" class="colstack_item rowlist" aria-label="{{lang "panel_statistics_requests_table_aria"}}">
|
<div id="panel_analytics_views_table" class="colstack_item rowlist" aria-label="{{lang "panel_statistics_requests_table_aria"}}">
|
||||||
{{range .ViewItems}}
|
{{range .ViewItems}}
|
||||||
<div class="rowitem panel_compactrow editable_parent">
|
<div class="rowitem panel_compactrow editable_parent">
|
||||||
<a class="panel_upshift unix_to_24_hour_time">{{.Time}}</a>
|
<a class="panel_upshift {{if or (or (or (eq $.TimeRange "six-hours") (eq $.TimeRange "twelve-hours")) (eq $.TimeRange "one-day")) (eq $.TimeRange "two-days")}}unix_to_24_hour_time{{else}}unix_to_date{{end}}">{{.Time}}</a>
|
||||||
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_statistics_views_suffix"}}</span>
|
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_statistics_views_suffix"}}</span>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -87,6 +87,10 @@
|
||||||
padding-right: 6px;
|
padding-right: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#panel_users .rowitem {
|
||||||
|
padding-left: 4px;
|
||||||
|
padding-right: 4px;
|
||||||
|
}
|
||||||
button, .formbutton, .panel_right_button:not(.has_inner_button), #panel_users .profile_url {
|
button, .formbutton, .panel_right_button:not(.has_inner_button), #panel_users .profile_url {
|
||||||
background: rgb(100,100,200);
|
background: rgb(100,100,200);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue