diff options
| author | Felix Hanley <felix@userspace.com.au> | 2020-03-05 11:45:33 +0000 |
|---|---|---|
| committer | Felix Hanley <felix@userspace.com.au> | 2020-03-05 11:45:33 +0000 |
| commit | 6f21095f6d71e0d5e2b75e754f9b28e15a552d36 (patch) | |
| tree | faaa098accc70ac2f6035fb17821a90c42093c51 | |
| parent | a1d9e95decc1fc51c5aaf56b7675f233eadaac7d (diff) | |
| download | sws-6f21095f6d71e0d5e2b75e754f9b28e15a552d36.tar.gz sws-6f21095f6d71e0d5e2b75e754f9b28e15a552d36.tar.bz2 | |
Add some basic time ranges
| -rw-r--r-- | charts.go | 3 | ||||
| -rw-r--r-- | cmd/server/charts.go | 2 | ||||
| -rw-r--r-- | cmd/server/handlers.go | 23 | ||||
| -rw-r--r-- | cmd/server/helpers.go | 38 | ||||
| -rw-r--r-- | cmd/server/main.go | 28 | ||||
| -rw-r--r-- | cmd/server/routes.go | 2 | ||||
| -rw-r--r-- | cmd/server/sites.go | 10 | ||||
| -rw-r--r-- | hit_set.go | 12 | ||||
| -rw-r--r-- | page_set.go | 2 | ||||
| -rw-r--r-- | templates/charts.tmpl | 2 | ||||
| -rw-r--r-- | templates/site.tmpl | 33 | ||||
| -rw-r--r-- | templates/timerange.tmpl | 38 |
12 files changed, 157 insertions, 36 deletions
@@ -68,8 +68,9 @@ func SparklineSVG(w io.Writer, data *HitSet, d time.Duration) error { StrokeColor: drawing.Color{R: 0, G: 0, B: 255, A: 100}, }, } + //data, _ = NewHitSet(FromHits(data.Hits()), Duration(d)) + //data.SortByDate() - data.SortByDate() var xVals []time.Time var yVals []float64 tmp := data.XSeries() diff --git a/cmd/server/charts.go b/cmd/server/charts.go index 59fd6d2..875b528 100644 --- a/cmd/server/charts.go +++ b/cmd/server/charts.go @@ -56,7 +56,7 @@ func sparklineHandler(db sws.HitStore) http.HandlerFunc { w.Header().Set("Content-Type", "image/svg+xml") w.Header().Set("Cache-Control", "public") - sws.SparklineSVG(w, data, time.Minute) + sws.SparklineSVG(w, data, time.Hour) } } diff --git a/cmd/server/handlers.go b/cmd/server/handlers.go index 18ee0fa..d13109d 100644 --- a/cmd/server/handlers.go +++ b/cmd/server/handlers.go @@ -10,17 +10,18 @@ import ( ) type templateData struct { - Payload string - Endpoint string - User *sws.User - Flash template.HTML - Begin *time.Time - End *time.Time - Site *sws.Site - Sites []*sws.Site - PageSet sws.PageSet - Browsers sws.BrowserSet - Hits *sws.HitSet + Payload string + Endpoint string + User *sws.User + Flash template.HTML + Begin time.Time + End time.Time + Site *sws.Site + Sites []*sws.Site + PageSet sws.PageSet + Browsers sws.BrowserSet + ReferrerSet sws.ReferrerSet + Hits *sws.HitSet } func newTemplateData(r *http.Request) *templateData { diff --git a/cmd/server/helpers.go b/cmd/server/helpers.go index 29f15ab..01552b2 100644 --- a/cmd/server/helpers.go +++ b/cmd/server/helpers.go @@ -11,8 +11,9 @@ import ( var funcMap = template.FuncMap{ "sparkline": func(id int) string { // This will enable "caching" for an hour - now := time.Now().Truncate(time.Hour) - then := now.Add(-168 * time.Hour) + now := time.Now() //.Truncate(time.Hour) + //then := now.Add(-720 * time.Hour) + then := now.Add(-168 * time.Hour) // 7 days //then := now.Add(-24 * time.Hour) //then := now.Add(-1 * time.Hour) return fmt.Sprintf("/sites/%d/sparklines/%d-%d.svg", id, then.Unix(), now.Unix()) @@ -22,15 +23,40 @@ var funcMap = template.FuncMap{ // TODO error return t.In(tz) }, - "timeShort": func(t time.Time) string { + /* + "seq": func(start, stop, step int) []int { + count := (stop - start) / step + out := make([]int, count) + c := start + for i := 0; i < count; i++ { + out[i] = c + c += step + } + return out + }, + "div": func(a, b int) int { + return a / b + }, + */ + "datetimeShort": func(t time.Time) string { return t.Format("2006-01-02 15:04") }, - "timeLong": func(t time.Time) string { + "datetimeLong": func(t time.Time) string { return t.Format(time.RFC3339) }, - "timeHour": func(t time.Time) string { + "datetimeHour": func(t time.Time) string { return t.Format("15:04 Jan 2") }, + "dateRFC": func(t time.Time) string { + return t.Format("2006-01-02") + }, + "timeRFC": func(t time.Time) string { + return t.Format("15:04") + }, + "datetimeRelative": func(d string) int64 { + dur, _ := time.ParseDuration(d) + return time.Now().Add(dur).Unix() + }, "percent": func(a, b int) float64 { return (float64(a) / float64(b)) * 100 }, @@ -46,7 +72,7 @@ func httpError(w http.ResponseWriter, code int, msg string) { func extractTimeRange(r *http.Request) (*time.Time, *time.Time) { begin := timePtr(time.Now().Truncate(time.Hour).Add(-168 * time.Hour)) - end := timePtr(time.Now()) + end := timePtr(time.Now().Truncate(time.Hour)) q := r.URL.Query() if b := q.Get("begin"); b != "" { if bs, err := strconv.ParseInt(b, 10, 64); err == nil { diff --git a/cmd/server/main.go b/cmd/server/main.go index 4e98b3b..900ef25 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -3,6 +3,7 @@ package main import ( "flag" "fmt" + "io" "net/http" "os" "strings" @@ -26,6 +27,7 @@ var ( addr *string dsn *string domain *string + logFile *string noMigrate *bool ) @@ -34,6 +36,7 @@ func init() { addr = stringFlag("listen", "l", "localhost:5000", "LISTEN", "listen address") dsn = stringFlag("dsn", "", "file:sws.db?cache=shared", "DSN", "database password") domain = stringFlag("domain", "", "stats.userspace.com.au", "DOMAIN", "stats domain") + logFile = stringFlag("log", "", "", "LOGFILE", "log to file") noMigrate = boolFlag("no-migrate", "m", false, "NOMIGRATE", "disable migrations") // Default to no log @@ -47,18 +50,27 @@ type Renderer interface { } func main() { + var err error flag.Parse() + var output io.Writer = os.Stdout + if logFile != nil && *logFile != "" { + if output, err = os.Create(*logFile); err != nil { + fmt.Fprintf(os.Stderr, "failed to open log file: %s", err) + os.Exit(1) + } + } + if *verbose { log = func(v ...interface{}) { - fmt.Fprintf(os.Stdout, "[%s] ", time.Now().Format(time.RFC3339)) - fmt.Fprintln(os.Stdout, v...) + fmt.Fprintf(output, "[%s] ", time.Now().Format(time.RFC3339)) + fmt.Fprintln(output, v...) } } if d := os.Getenv("DEBUG"); d != "" { debug = func(v ...interface{}) { - fmt.Fprintf(os.Stdout, "[%s] ", time.Now().Format(time.RFC3339)) - fmt.Fprintln(os.Stdout, v...) + fmt.Fprintf(output, "[%s] ", time.Now().Format(time.RFC3339)) + fmt.Fprintln(output, v...) } } log("version", Version) @@ -71,7 +83,7 @@ func main() { if noMigrate == nil || !*noMigrate { v, err := migrateDatabase(driver, *dsn) if err != nil { - fmt.Fprintf(os.Stderr, "failed to migrate: %s", err) + log("failed to migrate:", err) os.Exit(2) } log("database at version", v) @@ -79,12 +91,12 @@ func main() { db, err := sqlx.Open(driver, *dsn) if err != nil { - log(err) + log("failed to open database:", err) os.Exit(1) } defer db.Close() if err := db.Ping(); err != nil { - log(err) + log("failed to connect to database:", err) os.Exit(1) } var st sws.Store @@ -96,7 +108,7 @@ func main() { r, err := createRouter(st) if err != nil { - log(err) + log("failed to create router:", err) os.Exit(1) } diff --git a/cmd/server/routes.go b/cmd/server/routes.go index 7d7ab32..c8d0264 100644 --- a/cmd/server/routes.go +++ b/cmd/server/routes.go @@ -24,7 +24,7 @@ func init() { func createRouter(db sws.Store) (chi.Router, error) { tmplsCommon := []string{"flash.tmpl", "navbar.tmpl"} - tmplsAuthed := append(tmplsCommon, []string{"layouts/base.tmpl", "charts.tmpl"}...) + tmplsAuthed := append(tmplsCommon, []string{"layouts/base.tmpl", "charts.tmpl", "timerange.tmpl"}...) tmplsPublic := append(tmplsCommon, "layouts/public.tmpl") tmpls, err := LoadHTMLTemplateMap(map[string][]string{ diff --git a/cmd/server/sites.go b/cmd/server/sites.go index 46bcdf3..1b09e6e 100644 --- a/cmd/server/sites.go +++ b/cmd/server/sites.go @@ -58,6 +58,10 @@ func handleSite(db sws.SiteStore, rndr Renderer) http.HandlerFunc { httpError(w, 406, "invalid time range") return } + payload.Begin = *begin + payload.End = *end + debug("begin", *begin) + debug("end", *end) hits, err := db.GetHits(*site, map[string]interface{}{ "begin": *begin, @@ -90,6 +94,12 @@ func handleSite(db sws.SiteStore, rndr Renderer) http.HandlerFunc { } browserSet := sws.NewBrowserSet(hitSet) payload.Browsers = browserSet + + refSet := sws.NewReferrerSet(hitSet) + if refSet != nil { + refSet.SortByHits() + payload.ReferrerSet = refSet + } } if err := rndr.Render(w, "site", payload); err != nil { @@ -30,10 +30,16 @@ func TimeZone(s string) HitSetOption { } } -func Duration(s string) HitSetOption { +func Duration(d time.Duration) HitSetOption { return func(hs *HitSet) error { - var err error - hs.duration, err = time.ParseDuration(s) + hs.duration = d + return nil + } +} +func DurationString(s string) HitSetOption { + return func(hs *HitSet) error { + d, err := time.ParseDuration(s) + Duration(d) return err } } diff --git a/page_set.go b/page_set.go index 9077731..c9a77ff 100644 --- a/page_set.go +++ b/page_set.go @@ -60,7 +60,7 @@ func (ps *PageSet) SortByHits() { }) } -func (ps PageSet) Page(s string) *Page { +func (ps PageSet) GetPage(s string) *Page { for _, p := range ps { if p.Path == s { return p diff --git a/templates/charts.tmpl b/templates/charts.tmpl index e6979c6..b02d3b7 100644 --- a/templates/charts.tmpl +++ b/templates/charts.tmpl @@ -37,7 +37,7 @@ <ul class="chart bar stacked"> {{ $max := .CountMax }} {{ range .Data }} - <li class="slot{{ if eq .Time.Hour 0 }} midnight{{ end }}" data-date="{{ .Time|timeHour }}" data-count="{{ .Count }}" data-percent="{{ percent .Count $max }}"> + <li class="slot{{ if eq .Time.Hour 0 }} midnight{{ end }}" data-date="{{ .Time|datetimeHour }}" data-count="{{ .Count }}" data-percent="{{ percent .Count $max }}"> <div class="bar" style="height:{{ percent .Count $max }}%" /> </li> {{ end }} diff --git a/templates/site.tmpl b/templates/site.tmpl index fc74180..3fcd5fb 100644 --- a/templates/site.tmpl +++ b/templates/site.tmpl @@ -10,6 +10,9 @@ <h1>New Site</h1> {{ end }} </header> + {{ if .Site.ID }} + {{ template "timerange" . }} + {{ end }} {{ if .Hits }} {{ template "siteView" . }} {{ else }} @@ -28,7 +31,7 @@ <li> <h4 class="path">{{ .Path }}</h4> <span class="title">{{ .Title }}</span> - <span class="last-visit">{{ .LastVisitedAt|timeLong }}</span> + <span class="last-visit">{{ .LastVisitedAt|datetimeLong }}</span> <span class="count">{{ .Count }}</span> </li> {{ end }} @@ -36,7 +39,15 @@ {{ define "browserForList" }} <li> <h4 class="name">{{ .Name }}</h4> - <span class="last-seen">{{ .LastSeenAt|timeLong }}</span> + <span class="last-seen">{{ .LastSeenAt|datetimeLong }}</span> + <span class="count">{{ .Count }}</span> + </li> +{{ end }} + +{{ define "referrerForList" }} + <li> + <h4 class="name">{{ .Name }}</h4> + <span class="last-seen">{{ .LastSeenAt|datetimeLong }}</span> <span class="count">{{ .Count }}</span> </li> {{ end }} @@ -65,7 +76,7 @@ {{ range .PageSet }} {{ template "pageForList" . }} <fig> - {{ $pathHits := $pages.Page .Path }} + {{ $pathHits := $pages.GetPage .Path }} {{ template "timeBarChart" $pathHits }} </fig> {{ end }} @@ -76,6 +87,22 @@ </div> <div class="panel"> + <h2>Referrers</h2> + {{ if .ReferrerSet }} + <fig> + {{ template "barChart" .ReferrerSet }} + </fig> + <ul class="referrers"> + {{ range .ReferrerSet }} + {{ template "referrerForList" . }} + {{ end }} + </ul> + {{ else }} + <p>No referrers yet</p> + {{ end }} + </div> + + <div class="panel"> <h2>User Agents</h2> {{ if .Browsers }} <fig> diff --git a/templates/timerange.tmpl b/templates/timerange.tmpl new file mode 100644 index 0000000..b58fc36 --- /dev/null +++ b/templates/timerange.tmpl @@ -0,0 +1,38 @@ +{{ define "timerange" }} + <!-- + <form> + <input type="date" name="bdate" value="{{ .Begin|dateRFC }}" /> + <input type="time" name="btime" value="{{ .Begin|timeRFC }}" /> + <input type="date" name="edate" value="{{ .End|dateRFC }}" /> + <input type="time" name="etime" value="{{ .End|timeRFC }}" /> + <select name="timezone"> + <option value="-39600">-1100</option> + <option value="-36000">-1000</option> + <option value="-32400">-0900</option> + <option value="-28800">-0800</option> + <option value="-25200">-0700</option> + <option value="-21600">-0600</option> + <option value="-18000">-0500</option> + <option value="-14400">-0400</option> + <option value="-10800">-0300</option> + <option value="-7200">-0200</option> + <option value="-3600">-0100</option> + <option value="0">UTC</option> + <option value="3600">+0100</option> + <option value="7200">+0200</option> + <option value="10800">+0300</option> + <option value="14400">+0400</option> + <option value="18000">+0500</option> + <option value="21600">+0600</option> + <option value="25200">+0700</option> + <option value="28800">+0800</option> + <option value="32400">+0900</option> + <option value="36000">+1000</option> + <option value="39600">+1100</option> + </select> + </form> + --> + <a href="?begin={{ datetimeRelative "-24h"}}">last day</a> + <a href="?begin={{ datetimeRelative "-168h"}}">last 7 days</a> + <a href="?begin={{ datetimeRelative "-720h"}}">last 30 days</a> +{{ end }} |
