mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-08-03 16:38:31 +00:00
That's a good stopping point
This commit is contained in:
parent
b0ae3c27c6
commit
a327f6cf57
5 changed files with 254 additions and 0 deletions
59
socketserver/cmd/statsweb/html.go
Normal file
59
socketserver/cmd/statsweb/html.go
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
"bitbucket.org/stendec/frankerfacez/socketserver/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CalendarData struct {
|
||||||
|
Weeks []CalWeekData
|
||||||
|
}
|
||||||
|
type CalWeekData struct {
|
||||||
|
Days []CalDayData
|
||||||
|
}
|
||||||
|
type CalDayData struct {
|
||||||
|
NoData bool
|
||||||
|
Date int
|
||||||
|
UniqUsers int
|
||||||
|
}
|
||||||
|
|
||||||
|
type CalendarMonthInfo struct {
|
||||||
|
Year int
|
||||||
|
Month time.Month
|
||||||
|
// Ranges from -5 to +1.
|
||||||
|
// A value of +1 means the 1st of the month is a Sunday.
|
||||||
|
// A value of 0 means the 1st of the month is a Monday.
|
||||||
|
// A value of -5 means the 1st of the month is a Saturday.
|
||||||
|
FirstSundayOffset int
|
||||||
|
// True if the calendar for this month needs six sundays.
|
||||||
|
NeedSixSundays bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMonthInfo(at time.Time) CalendarMonthInfo {
|
||||||
|
year, month, _ := at.Date()
|
||||||
|
// 1 (start of month) - weekday of start of month = day offset of start of week at start of month
|
||||||
|
monthWeekStartDay := 1 - time.Date(year, month, 1, 0, 0, 0, 0, server.CounterLocation).Weekday()
|
||||||
|
// first day on calendar + 6 weeks < end of month?
|
||||||
|
sixthSundayDay := monthWeekStartDay + 5*7
|
||||||
|
sixthSundayDate := time.Date(year, month, sixthSundayDay, 0, 0, 0, 0, server.CounterLocation)
|
||||||
|
var needSixSundays bool = false
|
||||||
|
if sixthSundayDate.Month() == month {
|
||||||
|
needSixSundays = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return CalendarMonthInfo{
|
||||||
|
Year: year,
|
||||||
|
Month: month,
|
||||||
|
FirstSundayOffset: monthWeekStartDay,
|
||||||
|
NeedSixSundays: needSixSundays,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderCalendar(w http.ResponseWriter, at time.Time) {
|
||||||
|
layout, err := template.ParseFiles("./webroot/layout.template.html", "./webroot/cal_entry.hbs", "./webroot/calendar.hbs")
|
||||||
|
data := CalendarData{}
|
||||||
|
data.Weeks = make([]CalWeekData, 6)
|
||||||
|
|
||||||
|
}
|
|
@ -6,6 +6,11 @@ import (
|
||||||
"github.com/clarkduvall/hyperloglog"
|
"github.com/clarkduvall/hyperloglog"
|
||||||
"time"
|
"time"
|
||||||
"bitbucket.org/stendec/frankerfacez/socketserver/server"
|
"bitbucket.org/stendec/frankerfacez/socketserver/server"
|
||||||
|
"net/url"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"errors"
|
||||||
|
"github.com/dustin/gojson"
|
||||||
)
|
)
|
||||||
|
|
||||||
var configLocation = flag.String("config", "./config.json", "Location of the configuration file. Defaults to ./config.json")
|
var configLocation = flag.String("config", "./config.json", "Location of the configuration file. Defaults to ./config.json")
|
||||||
|
@ -25,9 +30,160 @@ func main() {
|
||||||
|
|
||||||
loadConfig()
|
loadConfig()
|
||||||
|
|
||||||
|
http.HandleFunc("/api", ServeAPI)
|
||||||
http.ListenAndServe(config.ListenAddr, http.DefaultServeMux)
|
http.ListenAndServe(config.ListenAddr, http.DefaultServeMux)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const RequestURIName = "q"
|
||||||
|
const separatorRange = "~"
|
||||||
|
const separatorAdd = " "
|
||||||
|
const jsonErrMalformedRequest = `{"status":"error","error":"malformed request uri"}`
|
||||||
|
const jsonErrBlankRequest = `{"status":"error","error":"no queries given"}`
|
||||||
|
const statusError = "error"
|
||||||
|
const statusPartial = "partial"
|
||||||
|
const statusOk = "ok"
|
||||||
|
type apiResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Responses []requestResponse `json:"resp"`
|
||||||
|
}
|
||||||
|
type requestResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Request string `json:"req"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
Count uint64 `json:"count,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type serverFilter struct {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
func (sf *serverFilter) IsServerAllowed(server string) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const serverFilterAll serverFilter
|
||||||
|
|
||||||
|
func ServeAPI(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
u, err := url.ParseRequestURI(r.RequestURI)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(400)
|
||||||
|
fmt.Fprint(w, jsonErrMalformedRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
query := u.Query()
|
||||||
|
reqCount := len(query[RequestURIName])
|
||||||
|
if reqCount == 0 {
|
||||||
|
w.WriteHeader(400)
|
||||||
|
fmt.Fprint(w, jsonErrBlankRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := apiResponse{Status: statusOk}
|
||||||
|
resp.Responses = make([]requestResponse, reqCount)
|
||||||
|
for i, v := range query[RequestURIName] {
|
||||||
|
resp.Responses[i] = processSingleRequest(v)
|
||||||
|
}
|
||||||
|
for _, v := range resp.Responses {
|
||||||
|
if v.Status == statusError {
|
||||||
|
resp.Status = statusPartial
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(200)
|
||||||
|
enc := json.NewEncoder(w)
|
||||||
|
enc.Encode(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
const errRangeFormatIncorrect = "incorrect range format, must be yyyy-mm-dd~yyyy-mm-dd"
|
||||||
|
|
||||||
|
func processSingleRequest(req string) (result requestResponse) {
|
||||||
|
// Forms:
|
||||||
|
// Single: 2016-01-02
|
||||||
|
// Range: 2016-01-03~2016-01-09
|
||||||
|
// Add disparate: 2016-01-02 2016-01-03 2016-01-09 2016-01-10
|
||||||
|
// NOTE: Spaces are uri-encoded as +
|
||||||
|
// Add ranges: 2016-01-04~2016-01-08 2016-01-11~2016-01-15
|
||||||
|
var hll hyperloglog.HyperLogLogPlus, _ = hyperloglog.NewPlus(server.CounterPrecision)
|
||||||
|
addSplit := strings.Split(req, separatorAdd)
|
||||||
|
|
||||||
|
result.Request = req
|
||||||
|
result.Status = statusOk
|
||||||
|
|
||||||
|
outerLoop:
|
||||||
|
for _, split1 := range addSplit {
|
||||||
|
if len(split1) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rangeSplit := strings.Split(split1, separatorRange)
|
||||||
|
if len(rangeSplit) == 1 {
|
||||||
|
at, err := parseDate(rangeSplit[0])
|
||||||
|
if err != nil {
|
||||||
|
result.Status = statusError
|
||||||
|
result.Error = err.Error()
|
||||||
|
break outerLoop
|
||||||
|
}
|
||||||
|
err = addSingleDate(at, serverFilterAll, &hll)
|
||||||
|
if err != nil {
|
||||||
|
result.Status = statusError
|
||||||
|
result.Error = err.Error()
|
||||||
|
break outerLoop
|
||||||
|
}
|
||||||
|
} else if len(rangeSplit) == 2 {
|
||||||
|
from, err := parseDate(rangeSplit[0])
|
||||||
|
if err != nil {
|
||||||
|
result.Status = statusError
|
||||||
|
result.Error = err.Error()
|
||||||
|
break outerLoop
|
||||||
|
}
|
||||||
|
to, err := parseDate(rangeSplit[1])
|
||||||
|
if err != nil {
|
||||||
|
result.Status = statusError
|
||||||
|
result.Error = err.Error()
|
||||||
|
break outerLoop
|
||||||
|
}
|
||||||
|
err = addRange(from, to, serverFilterAll, &hll)
|
||||||
|
if err != nil {
|
||||||
|
result.Status = statusError
|
||||||
|
result.Error = err.Error()
|
||||||
|
break outerLoop
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.Status = statusError
|
||||||
|
result.Error = errRangeFormatIncorrect
|
||||||
|
break outerLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Status == statusOk {
|
||||||
|
result.Count = hll.Count()
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
var errBadDate = errors.New("bad date format, must be yyyy-mm-dd")
|
||||||
|
var zeroTime = time.Unix(0, 0)
|
||||||
|
|
||||||
|
func parseDate(dateStr string) (time.Time, error) {
|
||||||
|
var year, month, day int
|
||||||
|
n, err := fmt.Sscanf(dateStr, "%d-%d-%d", &year, &month, &day)
|
||||||
|
if err != nil || n != 3 {
|
||||||
|
return zeroTime, errBadDate
|
||||||
|
}
|
||||||
|
return time.Date(year, month, day, 0, 0, 0, 0, server.CounterLocation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addSingleDate(at time.Time, filter serverFilter, dest *hyperloglog.HyperLogLogPlus) error {
|
||||||
|
// TODO
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addRange(start time.Time, end time.Time, filter serverFilter, dest *hyperloglog.HyperLogLogPlus) error {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func combineDateRange(from time.Time, to time.Time, dest *hyperloglog.HyperLogLogPlus) error {
|
func combineDateRange(from time.Time, to time.Time, dest *hyperloglog.HyperLogLogPlus) error {
|
||||||
from = server.TruncateToMidnight(from)
|
from = server.TruncateToMidnight(from)
|
||||||
to = server.TruncateToMidnight(to)
|
to = server.TruncateToMidnight(to)
|
||||||
|
|
6
socketserver/cmd/statsweb/webroot/cal_entry.hbs
Normal file
6
socketserver/cmd/statsweb/webroot/cal_entry.hbs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<td class="calentry {{if .NoData}}no_data{{end}}">
|
||||||
|
<span class="date">{{.Date}}</span>
|
||||||
|
{{if not .NoData}}
|
||||||
|
<span class="uniqusers">{{.UniqUsers}}</span>
|
||||||
|
{{end}}
|
||||||
|
</td>
|
18
socketserver/cmd/statsweb/webroot/calendar.hbs
Normal file
18
socketserver/cmd/statsweb/webroot/calendar.hbs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<table class="calendar">
|
||||||
|
<thead>
|
||||||
|
<th>Sunday</th>
|
||||||
|
<th>Monday</th>
|
||||||
|
<th>Tuesday</th>
|
||||||
|
<th>Wednesday</th>
|
||||||
|
<th>Thursday</th>
|
||||||
|
<th>Friday</th>
|
||||||
|
<th>Saturday</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range .Weeks}}
|
||||||
|
{{range .Days}}
|
||||||
|
{{template "cal_entry"}}
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
15
socketserver/cmd/statsweb/webroot/layout.template.html
Normal file
15
socketserver/cmd/statsweb/webroot/layout.template.html
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Socket Server Stats Dashboard</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="header">
|
||||||
|
{{template "header"}}
|
||||||
|
</div>
|
||||||
|
<div id="main">
|
||||||
|
{{template "content"}}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Add table
Add a link
Reference in a new issue