From 4b8193a81be14c6a0aadbd44d2b963d145104de7 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Sat, 25 Nov 2017 20:33:33 +0100 Subject: [PATCH] Integrated live visitor tracking: fix #5 --- handlers/public.go | 1 + static/src/Visitors/Visitors.js | 40 +++++++++++++++++++++++++++------ static/src/index.js | 2 +- store/store.go | 3 ++- 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/handlers/public.go b/handlers/public.go index a18cf08..7122d99 100644 --- a/handlers/public.go +++ b/handlers/public.go @@ -61,6 +61,7 @@ func (h *Handler) handleAccess(c *gin.Context) { IP: c.ClientIP(), Timestamp: time.Now(), Referer: c.GetHeader("Referer"), + UserAgent: c.GetHeader("User-Agent"), UTMSource: c.Query("utm_source"), UTMMedium: c.Query("utm_medium"), UTMCampaign: c.Query("utm_campaign"), diff --git a/static/src/Visitors/Visitors.js b/static/src/Visitors/Visitors.js index 8d9f41a..96884f3 100644 --- a/static/src/Visitors/Visitors.js +++ b/static/src/Visitors/Visitors.js @@ -5,14 +5,14 @@ import toastr from 'toastr' export default class VisitorComponent extends Component { state = { - id: "", - visitors: null + visitors: [], + info: null } componentDidMount() { this.setState({ id: this.props.match.params.id }) - fetch('/api/v1/protected/visitors', { - method: 'POST', + fetch("/api/v1/protected/lookup", { + method: "POST", body: JSON.stringify({ ID: this.props.match.params.id }), @@ -22,8 +22,29 @@ export default class VisitorComponent extends Component { } }) .then(res => res.ok ? res.json() : Promise.reject(res.json())) - .then(visitors => this.setState({ visitors })) - .catch(e => e.done(res => toastr.error(`Could not fetch visitors: ${res}`))) + .then(info => this.setState({ info })) + .catch(e => { + toastr.error(`Could not fetch lookup: ${e}`) + }) + this.loop = setInterval(() => { + fetch('/api/v1/protected/visitors', { + method: 'POST', + body: JSON.stringify({ + ID: this.props.match.params.id + }), + headers: { + 'Authorization': window.localStorage.getItem('token'), + 'Content-Type': 'application/json' + } + }) + .then(res => res.ok ? res.json() : Promise.reject(res.json())) + .then(visitors => this.setState({ visitors })) + .catch(e => e.done(res => toastr.error(`Could not fetch visitors: ${res}`))) + }, 1000) + } + + componentWillUnmount() { + clearInterval(this.loop) } // getUTMSource is a function which generates the output for the utm[...] table column @@ -36,14 +57,18 @@ export default class VisitorComponent extends Component { } render() { - const { visitors } = this.state + const { visitors, id, info } = this.state return ( + {info &&

+ Entry with id {id} was created at {moment(info.CreatedOn).format('LLL')} and redirects to '{info.URL}'. Currently it has {visitors.length} visits. +

} Timestamp IP + User Agent Referer UTM (source, medium, campaign, content, term) @@ -52,6 +77,7 @@ export default class VisitorComponent extends Component { {visitors && visitors.map((visit, idx) => {moment(visit.Timestamp).format('LLL')} {visit.IP} + {visit.UserAgent} {visit.Referer} {this.getUTMSource(visit)} )} diff --git a/static/src/index.js b/static/src/index.js index 8f59a24..ecd1ed0 100644 --- a/static/src/index.js +++ b/static/src/index.js @@ -130,7 +130,7 @@ export default class BaseComponent extends Component { } return ( - + diff --git a/store/store.go b/store/store.go index 6ad1a0a..119f019 100644 --- a/store/store.go +++ b/store/store.go @@ -34,7 +34,7 @@ type Entry struct { // Visitor is the entry which is stored in the visitors bucket type Visitor struct { - IP, Referer string + IP, Referer, UserAgent string Timestamp time.Time UTMSource, UTMMedium, UTMCampaign, UTMContent, UTMTerm string `json:",omitempty"` } @@ -202,6 +202,7 @@ func (s *Store) RegisterVisit(id string, visitor Visitor) { "ID": id, "RequestID": requestID, }).Info("New redirect was registered...") + err := s.db.Update(func(tx *bolt.Tx) error { bucket, err := tx.CreateBucketIfNotExists([]byte(id)) if err != nil {