aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelix Hanley <felix@userspace.com.au>2020-03-05 11:45:33 +0000
committerFelix Hanley <felix@userspace.com.au>2020-03-05 11:45:33 +0000
commit6f21095f6d71e0d5e2b75e754f9b28e15a552d36 (patch)
treefaaa098accc70ac2f6035fb17821a90c42093c51
parenta1d9e95decc1fc51c5aaf56b7675f233eadaac7d (diff)
downloadsws-6f21095f6d71e0d5e2b75e754f9b28e15a552d36.tar.gz
sws-6f21095f6d71e0d5e2b75e754f9b28e15a552d36.tar.bz2
Add some basic time ranges
-rw-r--r--charts.go3
-rw-r--r--cmd/server/charts.go2
-rw-r--r--cmd/server/handlers.go23
-rw-r--r--cmd/server/helpers.go38
-rw-r--r--cmd/server/main.go28
-rw-r--r--cmd/server/routes.go2
-rw-r--r--cmd/server/sites.go10
-rw-r--r--hit_set.go12
-rw-r--r--page_set.go2
-rw-r--r--templates/charts.tmpl2
-rw-r--r--templates/site.tmpl33
-rw-r--r--templates/timerange.tmpl38
12 files changed, 157 insertions, 36 deletions
diff --git a/charts.go b/charts.go
index 937edc4..fe63dbe 100644
--- a/charts.go
+++ b/charts.go
@@ -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 {
diff --git a/hit_set.go b/hit_set.go
index f34b585..cd9210f 100644
--- a/hit_set.go
+++ b/hit_set.go
@@ -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 }}