Added the views graph to the Control Panel.
Added support for strikethrough and underline HTML. Added DateCutoff to the Accumulator Select Builder for MySQL.
This commit is contained in:
parent
1639d81618
commit
dcfcd08248
|
@ -149,6 +149,20 @@ type PanelDashboardPage struct {
|
||||||
GridItems []GridElement
|
GridItems []GridElement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PanelTimeGraph struct {
|
||||||
|
Series []int64 // The counts on the left
|
||||||
|
Labels []int64 // unixtimes for the bottom, gets converted into 1:00, 2:00, etc. with JS
|
||||||
|
}
|
||||||
|
|
||||||
|
type PanelAnalyticsPage struct {
|
||||||
|
Title string
|
||||||
|
CurrentUser User
|
||||||
|
Header *HeaderVars
|
||||||
|
Stats PanelStats
|
||||||
|
Zone string
|
||||||
|
PrimaryGraph PanelTimeGraph
|
||||||
|
}
|
||||||
|
|
||||||
type PanelThemesPage struct {
|
type PanelThemesPage struct {
|
||||||
Title string
|
Title string
|
||||||
CurrentUser User
|
CurrentUser User
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
//"fmt"
|
//"fmt"
|
||||||
"bytes"
|
"bytes"
|
||||||
"html"
|
"html"
|
||||||
"log"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -183,6 +182,8 @@ func PreparseMessage(msg string) string {
|
||||||
msg = ""
|
msg = ""
|
||||||
var inBold = false
|
var inBold = false
|
||||||
var inItalic = false
|
var inItalic = false
|
||||||
|
var inStrike = false
|
||||||
|
var inUnderline = false
|
||||||
var stepForward = func(i int, step int, runes []rune) int {
|
var stepForward = func(i int, step int, runes []rune) int {
|
||||||
i += step
|
i += step
|
||||||
if i < len(runes) {
|
if i < len(runes) {
|
||||||
|
@ -192,36 +193,45 @@ func PreparseMessage(msg string) string {
|
||||||
}
|
}
|
||||||
for i := 0; i < len(runes); i++ {
|
for i := 0; i < len(runes); i++ {
|
||||||
char := runes[i]
|
char := runes[i]
|
||||||
log.Print("string(char): ", string(char))
|
|
||||||
if char == '&' && peek(i, 1, runes) == 'l' && peek(i, 2, runes) == 't' && peek(i, 3, runes) == ';' {
|
if char == '&' && peek(i, 1, runes) == 'l' && peek(i, 2, runes) == 't' && peek(i, 3, runes) == ';' {
|
||||||
log.Print("past less than")
|
|
||||||
i = stepForward(i, 4, runes)
|
i = stepForward(i, 4, runes)
|
||||||
char := runes[i]
|
char := runes[i]
|
||||||
if char == '/' {
|
if char == '/' {
|
||||||
log.Print("in /")
|
|
||||||
i = stepForward(i, 1, runes)
|
i = stepForward(i, 1, runes)
|
||||||
char := runes[i]
|
char := runes[i]
|
||||||
if inItalic && char == 'e' && peekMatch(i, "m>", runes) {
|
if inItalic && char == 'e' && peekMatch(i, "m>", runes) {
|
||||||
log.Print("in inItalic")
|
|
||||||
i += 5
|
i += 5
|
||||||
inItalic = false
|
inItalic = false
|
||||||
msg += "</em>"
|
msg += "</em>"
|
||||||
} else if inBold && char == 's' && peekMatch(i, "trong>", runes) {
|
} else if inBold && char == 's' && peekMatch(i, "trong>", runes) {
|
||||||
log.Print("in inBold")
|
|
||||||
i += 9
|
i += 9
|
||||||
inBold = false
|
inBold = false
|
||||||
msg += "</strong>"
|
msg += "</strong>"
|
||||||
|
} else if inStrike && char == 'd' && peekMatch(i, "el>", runes) {
|
||||||
|
i += 6
|
||||||
|
inStrike = false
|
||||||
|
msg += "</del>"
|
||||||
|
} else if inUnderline && char == 'u' && peekMatch(i, ">", runes) {
|
||||||
|
i += 4
|
||||||
|
inUnderline = false
|
||||||
|
msg += "</u>"
|
||||||
}
|
}
|
||||||
} else if !inItalic && char == 'e' && peekMatch(i, "m>", runes) {
|
} else if !inItalic && char == 'e' && peekMatch(i, "m>", runes) {
|
||||||
log.Print("in !inItalic")
|
|
||||||
i += 5
|
i += 5
|
||||||
inItalic = true
|
inItalic = true
|
||||||
msg += "<em>"
|
msg += "<em>"
|
||||||
} else if !inBold && char == 's' && peekMatch(i, "trong>", runes) {
|
} else if !inBold && char == 's' && peekMatch(i, "trong>", runes) {
|
||||||
log.Print("in !inBold")
|
|
||||||
i += 9
|
i += 9
|
||||||
inBold = true
|
inBold = true
|
||||||
msg += "<strong>"
|
msg += "<strong>"
|
||||||
|
} else if !inStrike && char == 'd' && peekMatch(i, "el>", runes) {
|
||||||
|
i += 6
|
||||||
|
inStrike = true
|
||||||
|
msg += "<del>"
|
||||||
|
} else if !inUnderline && char == 'u' && peekMatch(i, ">", runes) {
|
||||||
|
i += 4
|
||||||
|
inUnderline = true
|
||||||
|
msg += "<u>"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
msg += string(char)
|
msg += string(char)
|
||||||
|
@ -234,6 +244,12 @@ func PreparseMessage(msg string) string {
|
||||||
if inBold {
|
if inBold {
|
||||||
msg += "</strong>"
|
msg += "</strong>"
|
||||||
}
|
}
|
||||||
|
if inStrike {
|
||||||
|
msg += "</del>"
|
||||||
|
}
|
||||||
|
if inUnderline {
|
||||||
|
msg += "</u>"
|
||||||
|
}
|
||||||
|
|
||||||
return shortcodeToUnicode(msg)
|
return shortcodeToUnicode(msg)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,10 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"./common"
|
"./common"
|
||||||
|
"./query_gen/lib"
|
||||||
"github.com/Azareal/gopsutil/mem"
|
"github.com/Azareal/gopsutil/mem"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -430,14 +432,72 @@ func routePanelAnalyticsViews(w http.ResponseWriter, r *http.Request, user commo
|
||||||
if ferr != nil {
|
if ferr != nil {
|
||||||
return ferr
|
return ferr
|
||||||
}
|
}
|
||||||
|
headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css")
|
||||||
|
headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js")
|
||||||
|
|
||||||
pi := common.PanelPage{common.GetTitlePhrase("panel-analytics"), user, headerVars, stats, "analytics", tList, nil}
|
var revLabelList []int64
|
||||||
|
var labelList []int64
|
||||||
|
var viewMap = make(map[int64]int64)
|
||||||
|
var currentTime = time.Now().Unix()
|
||||||
|
|
||||||
|
for i := 1; i <= 12; i++ {
|
||||||
|
var label = currentTime - int64(i*60*30)
|
||||||
|
revLabelList = append(revLabelList, label)
|
||||||
|
viewMap[label] = 0
|
||||||
|
}
|
||||||
|
for _, value := range revLabelList {
|
||||||
|
labelList = append(labelList, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
var viewList []int64
|
||||||
|
|
||||||
|
acc := qgen.Builder.Accumulator()
|
||||||
|
rows, err := acc.Select("viewchunks").Columns("count, createdAt, route").Where("route = ''").DateCutoff("createdAt", 6, "hour").Query()
|
||||||
|
if err != nil && err != ErrNoRows {
|
||||||
|
return common.InternalError(err, w, r)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
log.Print("WE HAVE ROWS")
|
||||||
|
var count int64
|
||||||
|
var createdAt time.Time
|
||||||
|
var route string
|
||||||
|
err := rows.Scan(&count, &createdAt, &route)
|
||||||
|
if err != nil {
|
||||||
|
return common.InternalError(err, w, r)
|
||||||
|
}
|
||||||
|
log.Print("count: ", count)
|
||||||
|
log.Print("createdAt: ", createdAt)
|
||||||
|
log.Print("route: ", route)
|
||||||
|
|
||||||
|
var unixCreatedAt = createdAt.Unix()
|
||||||
|
log.Print("unixCreatedAt: ", unixCreatedAt)
|
||||||
|
for _, value := range labelList {
|
||||||
|
if unixCreatedAt > value {
|
||||||
|
viewMap[value] += count
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = rows.Err()
|
||||||
|
if err != nil {
|
||||||
|
return common.InternalError(err, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, value := range revLabelList {
|
||||||
|
viewList = append(viewList, viewMap[value])
|
||||||
|
}
|
||||||
|
graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
|
||||||
|
log.Printf("graph: %+v\n", graph)
|
||||||
|
|
||||||
|
pi := common.PanelAnalyticsPage{common.GetTitlePhrase("panel-analytics"), user, headerVars, stats, "analytics", graph}
|
||||||
if common.PreRenderHooks["pre_render_panel_analytics"] != nil {
|
if common.PreRenderHooks["pre_render_panel_analytics"] != nil {
|
||||||
if common.RunPreRenderHook("pre_render_panel_analytics", w, r, &user, &pi) {
|
if common.RunPreRenderHook("pre_render_panel_analytics", w, r, &user, &pi) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err := common.Templates.ExecuteTemplate(w, "panel-analytics-views.html", pi)
|
err = common.Templates.ExecuteTemplate(w, "panel-analytics-views.html", pi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.InternalError(err, w, r)
|
return common.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@ type accSelectBuilder struct {
|
||||||
where string
|
where string
|
||||||
orderby string
|
orderby string
|
||||||
limit string
|
limit string
|
||||||
|
dateCutoff *dateCutoff // We might want to do this in a slightly less hacky way
|
||||||
|
|
||||||
build *Accumulator
|
build *Accumulator
|
||||||
}
|
}
|
||||||
|
@ -60,6 +61,11 @@ func (selectItem *accSelectBuilder) Where(where string) *accSelectBuilder {
|
||||||
return selectItem
|
return selectItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (selectItem *accSelectBuilder) DateCutoff(column string, quantity int, unit string) *accSelectBuilder {
|
||||||
|
selectItem.dateCutoff = &dateCutoff{column, quantity, unit}
|
||||||
|
return selectItem
|
||||||
|
}
|
||||||
|
|
||||||
func (selectItem *accSelectBuilder) Orderby(orderby string) *accSelectBuilder {
|
func (selectItem *accSelectBuilder) Orderby(orderby string) *accSelectBuilder {
|
||||||
selectItem.orderby = orderby
|
selectItem.orderby = orderby
|
||||||
return selectItem
|
return selectItem
|
||||||
|
@ -71,6 +77,11 @@ func (selectItem *accSelectBuilder) Limit(limit string) *accSelectBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (selectItem *accSelectBuilder) Prepare() *sql.Stmt {
|
func (selectItem *accSelectBuilder) Prepare() *sql.Stmt {
|
||||||
|
// TODO: Phase out the procedural API and use the adapter's OO API? The OO API might need a bit more work before we do that and it needs to be rolled out to MSSQL.
|
||||||
|
if selectItem.dateCutoff != nil {
|
||||||
|
selectBuilder := selectItem.build.GetAdapter().Builder().Select().FromAcc(selectItem)
|
||||||
|
return selectItem.build.prepare(selectItem.build.GetAdapter().ComplexSelect(selectBuilder))
|
||||||
|
}
|
||||||
return selectItem.build.SimpleSelect(selectItem.table, selectItem.columns, selectItem.where, selectItem.orderby, selectItem.limit)
|
return selectItem.build.SimpleSelect(selectItem.table, selectItem.columns, selectItem.where, selectItem.orderby, selectItem.limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -198,7 +198,7 @@ func (build *Accumulator) Update(table string) *accUpdateBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) Select(table string) *accSelectBuilder {
|
func (build *Accumulator) Select(table string) *accSelectBuilder {
|
||||||
return &accSelectBuilder{table, "", "", "", "", build}
|
return &accSelectBuilder{table, "", "", "", "", nil, build}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *Accumulator) Insert(table string) *accInsertBuilder {
|
func (build *Accumulator) Insert(table string) *accInsertBuilder {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/* WIP Under Construction */
|
/* WIP Under Construction */
|
||||||
package qgen
|
package qgen
|
||||||
|
|
||||||
//import "log"
|
|
||||||
import "database/sql"
|
import "database/sql"
|
||||||
|
|
||||||
var Builder *builder
|
var Builder *builder
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
package qgen
|
package qgen
|
||||||
|
|
||||||
|
type dateCutoff struct {
|
||||||
|
Column string
|
||||||
|
Quantity int
|
||||||
|
Unit string
|
||||||
|
}
|
||||||
|
|
||||||
type prebuilder struct {
|
type prebuilder struct {
|
||||||
adapter Adapter
|
adapter Adapter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *prebuilder) Select(nlist ...string) *selectPrebuilder {
|
func (build *prebuilder) Select(nlist ...string) *selectPrebuilder {
|
||||||
name := optString(nlist, "_builder")
|
name := optString(nlist, "_builder")
|
||||||
return &selectPrebuilder{name, "", "", "", "", "", build.adapter}
|
return &selectPrebuilder{name, "", "", "", "", "", nil, build.adapter}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (build *prebuilder) Insert(nlist ...string) *insertPrebuilder {
|
func (build *prebuilder) Insert(nlist ...string) *insertPrebuilder {
|
||||||
|
@ -89,6 +95,7 @@ type selectPrebuilder struct {
|
||||||
where string
|
where string
|
||||||
orderby string
|
orderby string
|
||||||
limit string
|
limit string
|
||||||
|
dateCutoff *dateCutoff
|
||||||
|
|
||||||
build Adapter
|
build Adapter
|
||||||
}
|
}
|
||||||
|
@ -118,10 +125,23 @@ func (selectItem *selectPrebuilder) Limit(limit string) *selectPrebuilder {
|
||||||
return selectItem
|
return selectItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: We probably want to avoid the double allocation of two builders somehow
|
||||||
|
func (selectItem *selectPrebuilder) FromAcc(accBuilder *accSelectBuilder) *selectPrebuilder {
|
||||||
|
selectItem.table = accBuilder.table
|
||||||
|
selectItem.columns = accBuilder.columns
|
||||||
|
selectItem.where = accBuilder.where
|
||||||
|
selectItem.dateCutoff = accBuilder.dateCutoff
|
||||||
|
selectItem.orderby = accBuilder.orderby
|
||||||
|
selectItem.limit = accBuilder.limit
|
||||||
|
return selectItem
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add support for dateCutoff
|
||||||
func (selectItem *selectPrebuilder) Text() (string, error) {
|
func (selectItem *selectPrebuilder) Text() (string, error) {
|
||||||
return selectItem.build.SimpleSelect(selectItem.name, selectItem.table, selectItem.columns, selectItem.where, selectItem.orderby, selectItem.limit)
|
return selectItem.build.SimpleSelect(selectItem.name, selectItem.table, selectItem.columns, selectItem.where, selectItem.orderby, selectItem.limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Add support for dateCutoff
|
||||||
func (selectItem *selectPrebuilder) Parse() {
|
func (selectItem *selectPrebuilder) Parse() {
|
||||||
selectItem.build.SimpleSelect(selectItem.name, selectItem.table, selectItem.columns, selectItem.where, selectItem.orderby, selectItem.limit)
|
selectItem.build.SimpleSelect(selectItem.name, selectItem.table, selectItem.columns, selectItem.where, selectItem.orderby, selectItem.limit)
|
||||||
}
|
}
|
||||||
|
|
|
@ -435,7 +435,6 @@ func (adapter *MssqlAdapter) SimpleSelect(name string, table string, columns str
|
||||||
for _, column := range colslice {
|
for _, column := range colslice {
|
||||||
querystr += "[" + strings.TrimSpace(column) + "],"
|
querystr += "[" + strings.TrimSpace(column) + "],"
|
||||||
}
|
}
|
||||||
// Remove the trailing comma
|
|
||||||
querystr = querystr[0 : len(querystr)-1]
|
querystr = querystr[0 : len(querystr)-1]
|
||||||
|
|
||||||
querystr += " FROM [" + table + "]"
|
querystr += " FROM [" + table + "]"
|
||||||
|
@ -510,6 +509,11 @@ func (adapter *MssqlAdapter) SimpleSelect(name string, table string, columns str
|
||||||
return querystr, nil
|
return querystr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: ComplexSelect
|
||||||
|
func (adapter *MssqlAdapter) ComplexSelect(preBuilder *selectPrebuilder) (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
func (adapter *MssqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
|
func (adapter *MssqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return "", errors.New("You need a name for this statement")
|
return "", errors.New("You need a name for this statement")
|
||||||
|
|
|
@ -304,9 +304,10 @@ func (adapter *MysqlAdapter) Purge(name string, table string) (string, error) {
|
||||||
return "DELETE FROM `" + table + "`", nil
|
return "DELETE FROM `" + table + "`", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add support for BETWEEN x.x
|
|
||||||
func (adapter *MysqlAdapter) buildWhere(where string) (querystr string, err error) {
|
func (adapter *MysqlAdapter) buildWhere(where string) (querystr string, err error) {
|
||||||
if len(where) != 0 {
|
if len(where) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
querystr = " WHERE"
|
querystr = " WHERE"
|
||||||
for _, loc := range processWhere(where) {
|
for _, loc := range processWhere(where) {
|
||||||
for _, token := range loc.Expr {
|
for _, token := range loc.Expr {
|
||||||
|
@ -323,9 +324,36 @@ func (adapter *MysqlAdapter) buildWhere(where string) (querystr string, err erro
|
||||||
}
|
}
|
||||||
querystr += " AND"
|
querystr += " AND"
|
||||||
}
|
}
|
||||||
querystr = querystr[0 : len(querystr)-4]
|
return querystr[0 : len(querystr)-4], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The new version of buildWhere() currently only used in ComplexSelect for complex OO builder queries
|
||||||
|
func (adapter *MysqlAdapter) buildFlexiWhere(where string, dateCutoff *dateCutoff) (querystr string, err error) {
|
||||||
|
if len(where) == 0 && dateCutoff == nil {
|
||||||
|
return "", nil
|
||||||
}
|
}
|
||||||
return querystr, nil
|
querystr = " WHERE"
|
||||||
|
if dateCutoff != nil {
|
||||||
|
querystr += " " + dateCutoff.Column + " BETWEEN (UTC_TIMESTAMP() - interval " + strconv.Itoa(dateCutoff.Quantity) + " " + dateCutoff.Unit + ") AND UTC_TIMESTAMP() AND"
|
||||||
|
}
|
||||||
|
if len(where) != 0 {
|
||||||
|
for _, loc := range processWhere(where) {
|
||||||
|
for _, token := range loc.Expr {
|
||||||
|
switch token.Type {
|
||||||
|
case "function", "operator", "number", "substitute":
|
||||||
|
querystr += " " + token.Contents
|
||||||
|
case "column":
|
||||||
|
querystr += " `" + token.Contents + "`"
|
||||||
|
case "string":
|
||||||
|
querystr += " '" + token.Contents + "'"
|
||||||
|
default:
|
||||||
|
return querystr, errors.New("This token doesn't exist o_o")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
querystr += " AND"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return querystr[0 : len(querystr)-4], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adapter *MysqlAdapter) buildOrderby(orderby string) (querystr string) {
|
func (adapter *MysqlAdapter) buildOrderby(orderby string) (querystr string) {
|
||||||
|
@ -354,8 +382,7 @@ func (adapter *MysqlAdapter) SimpleSelect(name string, table string, columns str
|
||||||
var querystr = "SELECT "
|
var querystr = "SELECT "
|
||||||
|
|
||||||
// Slice up the user friendly strings into something easier to process
|
// Slice up the user friendly strings into something easier to process
|
||||||
var colslice = strings.Split(strings.TrimSpace(columns), ",")
|
for _, column := range strings.Split(strings.TrimSpace(columns), ",") {
|
||||||
for _, column := range colslice {
|
|
||||||
querystr += "`" + strings.TrimSpace(column) + "`,"
|
querystr += "`" + strings.TrimSpace(column) + "`,"
|
||||||
}
|
}
|
||||||
querystr = querystr[0 : len(querystr)-1]
|
querystr = querystr[0 : len(querystr)-1]
|
||||||
|
@ -372,6 +399,37 @@ func (adapter *MysqlAdapter) SimpleSelect(name string, table string, columns str
|
||||||
return querystr, nil
|
return querystr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (adapter *MysqlAdapter) ComplexSelect(preBuilder *selectPrebuilder) (string, error) {
|
||||||
|
if preBuilder.name == "" {
|
||||||
|
return "", errors.New("You need a name for this statement")
|
||||||
|
}
|
||||||
|
if preBuilder.table == "" {
|
||||||
|
return "", errors.New("You need a name for this table")
|
||||||
|
}
|
||||||
|
if len(preBuilder.columns) == 0 {
|
||||||
|
return "", errors.New("No columns found for ComplexSelect")
|
||||||
|
}
|
||||||
|
|
||||||
|
var querystr = "SELECT "
|
||||||
|
|
||||||
|
// Slice up the user friendly strings into something easier to process
|
||||||
|
for _, column := range strings.Split(strings.TrimSpace(preBuilder.columns), ",") {
|
||||||
|
querystr += "`" + strings.TrimSpace(column) + "`,"
|
||||||
|
}
|
||||||
|
querystr = querystr[0 : len(querystr)-1]
|
||||||
|
|
||||||
|
whereStr, err := adapter.buildFlexiWhere(preBuilder.where, preBuilder.dateCutoff)
|
||||||
|
if err != nil {
|
||||||
|
return querystr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
querystr += " FROM `" + preBuilder.table + "`" + whereStr + adapter.buildOrderby(preBuilder.orderby) + adapter.buildLimit(preBuilder.limit)
|
||||||
|
|
||||||
|
querystr = strings.TrimSpace(querystr)
|
||||||
|
adapter.pushStatement(preBuilder.name, "select", querystr)
|
||||||
|
return querystr, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (adapter *MysqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
|
func (adapter *MysqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return "", errors.New("You need a name for this statement")
|
return "", errors.New("You need a name for this statement")
|
||||||
|
|
|
@ -252,6 +252,20 @@ func (adapter *PgsqlAdapter) SimpleSelect(name string, table string, columns str
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Implement this
|
||||||
|
func (adapter *PgsqlAdapter) ComplexSelect(prebuilder *selectPrebuilder) (string, error) {
|
||||||
|
if prebuilder.name == "" {
|
||||||
|
return "", errors.New("You need a name for this statement")
|
||||||
|
}
|
||||||
|
if prebuilder.table == "" {
|
||||||
|
return "", errors.New("You need a name for this table")
|
||||||
|
}
|
||||||
|
if len(prebuilder.columns) == 0 {
|
||||||
|
return "", errors.New("No columns found for ComplexSelect")
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Implement this
|
// TODO: Implement this
|
||||||
func (adapter *PgsqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
|
func (adapter *PgsqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
|
|
|
@ -100,11 +100,6 @@ type Adapter interface {
|
||||||
GetName() string
|
GetName() string
|
||||||
CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error)
|
CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error)
|
||||||
SimpleInsert(name string, table string, columns string, fields string) (string, error)
|
SimpleInsert(name string, table string, columns string, fields string) (string, error)
|
||||||
|
|
||||||
// ! DEPRECATED
|
|
||||||
//SimpleReplace(name string, table string, columns string, fields string) (string, error)
|
|
||||||
// ! NOTE: MySQL doesn't support upserts properly, so I'm removing this from the interface until we find a way to patch it in
|
|
||||||
//SimpleUpsert(name string, table string, columns string, fields string, where string) (string, error)
|
|
||||||
SimpleUpdate(name string, table string, set string, where string) (string, error)
|
SimpleUpdate(name string, table string, set string, where string) (string, error)
|
||||||
SimpleDelete(name string, table string, where string) (string, error)
|
SimpleDelete(name string, table string, where string) (string, error)
|
||||||
Purge(name string, table string) (string, error)
|
Purge(name string, table string) (string, error)
|
||||||
|
@ -116,6 +111,8 @@ type Adapter interface {
|
||||||
SimpleInsertInnerJoin(string, DBInsert, DBJoin) (string, error)
|
SimpleInsertInnerJoin(string, DBInsert, DBJoin) (string, error)
|
||||||
SimpleCount(string, string, string, string) (string, error)
|
SimpleCount(string, string, string, string) (string, error)
|
||||||
|
|
||||||
|
ComplexSelect(*selectPrebuilder) (string, error)
|
||||||
|
|
||||||
Builder() *prebuilder
|
Builder() *prebuilder
|
||||||
Write() error
|
Write() error
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,34 @@
|
||||||
<div class="rowitem"><a>Views</a></div>
|
<div class="rowitem"><a>Views</a></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="panel_analytics" class="colstack_graph_holder">
|
<div id="panel_analytics" class="colstack_graph_holder">
|
||||||
|
<div class="ct-chart"></div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
<script>
|
||||||
|
let labels = [];
|
||||||
|
let rawLabels = [{{range .PrimaryGraph.Labels}}
|
||||||
|
{{.}},{{end}}
|
||||||
|
];
|
||||||
|
for(const i in rawLabels) {
|
||||||
|
let date = new Date(rawLabels[i]*1000);
|
||||||
|
console.log("date: ", date);
|
||||||
|
let minutes = "0" + date.getMinutes();
|
||||||
|
let label = date.getHours() + ":" + minutes.substr(-2);
|
||||||
|
console.log("label:", label);
|
||||||
|
labels.push(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
let seriesData = [{{range .PrimaryGraph.Series}}
|
||||||
|
{{.}},{{end}}
|
||||||
|
];
|
||||||
|
seriesData = seriesData.reverse();
|
||||||
|
|
||||||
|
Chartist.Line('.ct-chart', {
|
||||||
|
labels: labels,
|
||||||
|
series: [seriesData],
|
||||||
|
}, {
|
||||||
|
height: '250px',
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{{template "footer.html" . }}
|
{{template "footer.html" . }}
|
||||||
|
|
|
@ -155,3 +155,17 @@
|
||||||
.perm_preset_default:before {
|
.perm_preset_default:before {
|
||||||
content: "Default";
|
content: "Default";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.colstack_graph_holder {
|
||||||
|
background-color: var(--element-background-color);
|
||||||
|
border: 1px solid var(--element-border-color);
|
||||||
|
border-bottom: 2px solid var(--element-border-color);
|
||||||
|
margin-left: 16px;
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
.ct-series-a .ct-bar, .ct-series-a .ct-line, .ct-series-a .ct-point, .ct-series-a .ct-slice-donut {
|
||||||
|
stroke: hsl(359,98%,53%) !important;
|
||||||
|
}
|
||||||
|
.ct-point {
|
||||||
|
stroke: hsl(359,98%,33%) !important;
|
||||||
|
}
|
Loading…
Reference in New Issue