Browse Source

Integrated live visitor tracking: fix #5

dependabot/npm_and_yarn/web/prismjs-1.21.0
Max Schmitt 8 years ago
parent
commit
4b8193a81b
  1. 1
      handlers/public.go
  2. 40
      static/src/Visitors/Visitors.js
  3. 2
      static/src/index.js
  4. 3
      store/store.go

1
handlers/public.go

@ -61,6 +61,7 @@ func (h *Handler) handleAccess(c *gin.Context) {
IP: c.ClientIP(), IP: c.ClientIP(),
Timestamp: time.Now(), Timestamp: time.Now(),
Referer: c.GetHeader("Referer"), Referer: c.GetHeader("Referer"),
UserAgent: c.GetHeader("User-Agent"),
UTMSource: c.Query("utm_source"), UTMSource: c.Query("utm_source"),
UTMMedium: c.Query("utm_medium"), UTMMedium: c.Query("utm_medium"),
UTMCampaign: c.Query("utm_campaign"), UTMCampaign: c.Query("utm_campaign"),

40
static/src/Visitors/Visitors.js

@ -5,14 +5,14 @@ import toastr from 'toastr'
export default class VisitorComponent extends Component { export default class VisitorComponent extends Component {
state = { state = {
id: "", visitors: [],
visitors: null info: null
} }
componentDidMount() { componentDidMount() {
this.setState({ id: this.props.match.params.id }) this.setState({ id: this.props.match.params.id })
fetch('/api/v1/protected/visitors', { fetch("/api/v1/protected/lookup", {
method: 'POST', method: "POST",
body: JSON.stringify({ body: JSON.stringify({
ID: this.props.match.params.id 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(res => res.ok ? res.json() : Promise.reject(res.json()))
.then(visitors => this.setState({ visitors })) .then(info => this.setState({ info }))
.catch(e => e.done(res => toastr.error(`Could not fetch visitors: ${res}`))) .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 // getUTMSource is a function which generates the output for the utm[...] table column
@ -36,14 +57,18 @@ export default class VisitorComponent extends Component {
} }
render() { render() {
const { visitors } = this.state const { visitors, id, info } = this.state
return ( return (
<Container > <Container >
{info && <p>
Entry with id {id} was created at {moment(info.CreatedOn).format('LLL')} and redirects to '{info.URL}'. Currently it has {visitors.length} visits.
</p>}
<Table celled> <Table celled>
<Table.Header> <Table.Header>
<Table.Row> <Table.Row>
<Table.HeaderCell>Timestamp</Table.HeaderCell> <Table.HeaderCell>Timestamp</Table.HeaderCell>
<Table.HeaderCell>IP</Table.HeaderCell> <Table.HeaderCell>IP</Table.HeaderCell>
<Table.HeaderCell>User Agent</Table.HeaderCell>
<Table.HeaderCell>Referer</Table.HeaderCell> <Table.HeaderCell>Referer</Table.HeaderCell>
<Table.HeaderCell>UTM (source, medium, campaign, content, term)</Table.HeaderCell> <Table.HeaderCell>UTM (source, medium, campaign, content, term)</Table.HeaderCell>
</Table.Row> </Table.Row>
@ -52,6 +77,7 @@ export default class VisitorComponent extends Component {
{visitors && visitors.map((visit, idx) => <Table.Row key={idx}> {visitors && visitors.map((visit, idx) => <Table.Row key={idx}>
<Table.Cell>{moment(visit.Timestamp).format('LLL')}</Table.Cell> <Table.Cell>{moment(visit.Timestamp).format('LLL')}</Table.Cell>
<Table.Cell>{visit.IP}</Table.Cell> <Table.Cell>{visit.IP}</Table.Cell>
<Table.Cell>{visit.UserAgent}</Table.Cell>
<Table.Cell>{visit.Referer}</Table.Cell> <Table.Cell>{visit.Referer}</Table.Cell>
<Table.Cell>{this.getUTMSource(visit)}</Table.Cell> <Table.Cell>{this.getUTMSource(visit)}</Table.Cell>
</Table.Row>)} </Table.Row>)}

2
static/src/index.js

@ -130,7 +130,7 @@ export default class BaseComponent extends Component {
} }
return ( return (
<HashRouter> <HashRouter>
<Container style={{ paddingTop: "15px" }}> <Container style={{ padding: "15px 0" }}>
<Menu stackable> <Menu stackable>
<Menu.Item as={Link} to="/" name='shorten' onClick={this.handleItemClick} > <Menu.Item as={Link} to="/" name='shorten' onClick={this.handleItemClick} >
<Image src={userData.Picture} alt='user profile' circular size='mini' /> <Image src={userData.Picture} alt='user profile' circular size='mini' />

3
store/store.go

@ -34,7 +34,7 @@ type Entry struct {
// Visitor is the entry which is stored in the visitors bucket // Visitor is the entry which is stored in the visitors bucket
type Visitor struct { type Visitor struct {
IP, Referer string IP, Referer, UserAgent string
Timestamp time.Time Timestamp time.Time
UTMSource, UTMMedium, UTMCampaign, UTMContent, UTMTerm string `json:",omitempty"` UTMSource, UTMMedium, UTMCampaign, UTMContent, UTMTerm string `json:",omitempty"`
} }
@ -202,6 +202,7 @@ func (s *Store) RegisterVisit(id string, visitor Visitor) {
"ID": id, "ID": id,
"RequestID": requestID, "RequestID": requestID,
}).Info("New redirect was registered...") }).Info("New redirect was registered...")
err := s.db.Update(func(tx *bolt.Tx) error { err := s.db.Update(func(tx *bolt.Tx) error {
bucket, err := tx.CreateBucketIfNotExists([]byte(id)) bucket, err := tx.CreateBucketIfNotExists([]byte(id))
if err != nil { if err != nil {

Loading…
Cancel
Save