diff --git a/ROADMAP.md b/ROADMAP.md
new file mode 100644
index 0000000..563ca8d
--- /dev/null
+++ b/ROADMAP.md
@@ -0,0 +1,20 @@
+Ana Roadmap
+===========
+
+This is a general draft document for thoughts and todo's.
+
+### Key metrics
+
+- Unique visits per day (in period)
+- Pageviews per day (in period)
+- Demographic
+ - Country
+ - Browser + version
+ - Operating System
+- Acquisition
+
+
+### Admin themes
+
+- Pages, http://pages.revox.io/dashboard/latest/html/index.html#usa
+- Metronic, http://keenthemes.com/preview/metronic/
diff --git a/api/api.go b/api/api.go
index 59abda7..985da69 100644
--- a/api/api.go
+++ b/api/api.go
@@ -6,6 +6,7 @@ import (
)
func RegisterRoutes() {
+ http.HandleFunc("/api/visits/count/day", GetVisitsDayCountHandler)
http.HandleFunc("/api/visits/count/realtime", GetVisitsRealtimeCount)
http.HandleFunc("/api/visits", GetVisitsHandler)
http.HandleFunc("/api/pageviews", GetPageviewsHandler)
diff --git a/api/pageviews.go b/api/pageviews.go
index 9326164..1687f95 100644
--- a/api/pageviews.go
+++ b/api/pageviews.go
@@ -11,7 +11,8 @@ import (
func GetPageviewsHandler(w http.ResponseWriter, r *http.Request) {
stmt, err := core.DB.Prepare(`SELECT
path,
- COUNT(DISTINCT(ip_address)) AS pageviews
+ COUNT(ip_address) AS pageviews,
+ COUNT(DISTINCT(ip_address)) AS pageviews_unique
FROM visits
GROUP BY path`)
checkError(err)
@@ -24,7 +25,7 @@ func GetPageviewsHandler(w http.ResponseWriter, r *http.Request) {
defer rows.Close()
for rows.Next() {
var p models.Pageview
- err = rows.Scan(&p.Path, &p.Count);
+ err = rows.Scan(&p.Path, &p.Count, &p.CountUnique);
checkError(err)
results = append(results, p)
}
diff --git a/api/visits.go b/api/visits.go
index e92a177..03a2b49 100644
--- a/api/visits.go
+++ b/api/visits.go
@@ -5,6 +5,7 @@ import (
"github.com/dannyvankooten/ana/models"
"github.com/dannyvankooten/ana/core"
"encoding/json"
+ "strconv"
)
// URL: /api/visits
@@ -18,12 +19,19 @@ func GetVisitsHandler(w http.ResponseWriter, r *http.Request) {
path,
COALESCE(screen_resolution, '') AS screen_resolution,
timestamp
- FROM visits`)
+ FROM visits
+ ORDER BY timestamp DESC
+ LIMIT ?`)
checkError(err)
defer stmt.Close()
- rows, err := stmt.Query()
+ limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
+ if limit == 0 {
+ limit = 10
+ }
+
+ rows, err := stmt.Query(&limit)
checkError(err)
results := make([]models.Visit, 0)
@@ -44,10 +52,43 @@ func GetVisitsHandler(w http.ResponseWriter, r *http.Request) {
// URL: /api/visits/count/realtime
func GetVisitsRealtimeCount(w http.ResponseWriter, r *http.Request) {
- row := core.DB.QueryRow(`SELECT COUNT(DISTINCT(id)) FROM visits WHERE timestamp >= DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 3 HOUR_MINUTE)`)
+ row := core.DB.QueryRow(`SELECT COUNT(DISTINCT(ip_address)) FROM visits WHERE timestamp >= DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 3 HOUR_MINUTE)`)
var result int
row.Scan(&result)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(result)
}
+
+// URL: /api/visits/count/day
+func GetVisitsDayCountHandler(w http.ResponseWriter, r *http.Request) {
+ stmt, err := core.DB.Prepare(`SELECT
+ COUNT(*) AS count, DATE_FORMAT(timestamp, '%Y-%m-%d') AS date_group
+ FROM visits
+ GROUP BY date_group`)
+ checkError(err)
+ defer stmt.Close()
+
+ rows, err := stmt.Query()
+ checkError(err)
+
+ type Datapoint struct {
+ Count int
+ Label string
+ }
+
+ results := make([]Datapoint, 0)
+ defer rows.Close()
+ for rows.Next() {
+ v := Datapoint{}
+ err = rows.Scan(&v.Count, &v.Label);
+ checkError(err)
+ results = append(results, v)
+ }
+
+ err = rows.Err();
+ checkError(err)
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(results)
+}
diff --git a/assets/js/components/pageviews.js b/assets/js/components/pageviews.js
index d15200b..a9910c5 100644
--- a/assets/js/components/pageviews.js
+++ b/assets/js/components/pageviews.js
@@ -19,20 +19,22 @@ class PageviewsList extends React.Component {
const tableRows = this.state.records.map((p, i) =>
{i+1} |
- {p.Path} |
+ {p.Path} |
{p.Count} |
+ {p.CountUnique} |
);
return (
-
+
Pageviews
# |
- Path |
- Count |
+ URL |
+ Pageviews |
+ Unique |
{tableRows}
diff --git a/assets/js/components/realtime-visits.js b/assets/js/components/realtime-visits.js
index 9840a3f..c382c97 100644
--- a/assets/js/components/realtime-visits.js
+++ b/assets/js/components/realtime-visits.js
@@ -1,3 +1,5 @@
+'use strict';
+
import React, { Component } from 'react'
class RealtimeVisitsCount extends React.Component {
diff --git a/assets/js/components/visits-graph.js b/assets/js/components/visits-graph.js
new file mode 100644
index 0000000..56b8483
--- /dev/null
+++ b/assets/js/components/visits-graph.js
@@ -0,0 +1,75 @@
+'use strict';
+
+import React, { Component } from 'react'
+import Chart from 'chart.js'
+
+function rand(min, max, num) {
+ var rtn = [];
+ while (rtn.length < num) {
+ rtn.push((Math.random() * (max - min)) + min);
+ }
+ return rtn;
+ }
+
+class VisitsGraph extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = { data: [] }
+ this.refresh();
+ }
+
+ refresh() {
+ return fetch('/api/visits/count/day')
+ .then((r) => r.json())
+ .then((data) => {
+ this.setState({ data: data });
+ this.initChart(this.chartCtx);
+ });
+ }
+
+ initChart(ctx) {
+ if( ! this.state.data.length ) {
+ return;
+ }
+
+ console.log(this.state.data.map((d) => d.Label));
+ console.log(this.state.data.map((d) => d.Count ));
+ console.log("Init chart");
+ console.log(ctx);
+
+ var myChart = new Chart(ctx, {
+ type: 'line',
+ data: {
+ labels: this.state.data.map((d) => d.Label),
+ datasets: [{
+ label: '# of Visitors',
+ data: this.state.data.map((d) => d.Count),
+ backgroundColor: 'rgba(162, 52, 235, 0.2)'
+ }]
+ },
+ options: {
+ tooltips: {
+ xPadding: 12,
+ yPadding: 9,
+ },
+ scales: {
+ yAxes: [{
+ ticks: {
+ beginAtZero:true
+ }
+ }]
+ }
+ }
+ });
+ }
+
+ render() {
+ return (
+
+
+
+ );
+ }
+}
+
+export default VisitsGraph;
diff --git a/assets/js/components/visits-list.js b/assets/js/components/visits-list.js
index 5143e99..885462b 100644
--- a/assets/js/components/visits-list.js
+++ b/assets/js/components/visits-list.js
@@ -25,7 +25,7 @@ class VisitsList extends React.Component {
);
return (
-
+
Visits
diff --git a/assets/js/script.js b/assets/js/script.js
index 700dc12..2365871 100644
--- a/assets/js/script.js
+++ b/assets/js/script.js
@@ -5,20 +5,14 @@ import ReactDOM from 'react-dom';
import RealtimeVisitsCount from './components/realtime-visits.js';
import VisitsList from './components/visits-list.js';
import PageviewsList from './components/pageviews.js';
+import VisitsGraph from './components/visits-graph.js';
-function tick() {
- const element = (
-
- );
-
- ReactDOM.render(
- element, document.getElementById('root')
- );
-}
-
-tick() && window.setInterval(tick, 1000);
+ReactDOM.render(
+ ,
+ document.getElementById('root')
+);
diff --git a/assets/sass/_normalize.scss b/assets/sass/_normalize.scss
new file mode 100644
index 0000000..9b77e0e
--- /dev/null
+++ b/assets/sass/_normalize.scss
@@ -0,0 +1,461 @@
+/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */
+
+/**
+ * 1. Change the default font family in all browsers (opinionated).
+ * 2. Correct the line height in all browsers.
+ * 3. Prevent adjustments of font size after orientation changes in
+ * IE on Windows Phone and in iOS.
+ */
+
+/* Document
+ ========================================================================== */
+
+html {
+ font-family: sans-serif; /* 1 */
+ line-height: 1.15; /* 2 */
+ -ms-text-size-adjust: 100%; /* 3 */
+ -webkit-text-size-adjust: 100%; /* 3 */
+}
+
+/* Sections
+ ========================================================================== */
+
+/**
+ * Remove the margin in all browsers (opinionated).
+ */
+
+body {
+ margin: 0;
+}
+
+/**
+ * Add the correct display in IE 9-.
+ */
+
+article,
+aside,
+footer,
+header,
+nav,
+section {
+ display: block;
+}
+
+/**
+ * Correct the font size and margin on `h1` elements within `section` and
+ * `article` contexts in Chrome, Firefox, and Safari.
+ */
+
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+}
+
+/* Grouping content
+ ========================================================================== */
+
+/**
+ * Add the correct display in IE 9-.
+ * 1. Add the correct display in IE.
+ */
+
+figcaption,
+figure,
+main { /* 1 */
+ display: block;
+}
+
+/**
+ * Add the correct margin in IE 8.
+ */
+
+figure {
+ margin: 1em 40px;
+}
+
+/**
+ * 1. Add the correct box sizing in Firefox.
+ * 2. Show the overflow in Edge and IE.
+ */
+
+hr {
+ box-sizing: content-box; /* 1 */
+ height: 0; /* 1 */
+ overflow: visible; /* 2 */
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+pre {
+ font-family: monospace, monospace; /* 1 */
+ font-size: 1em; /* 2 */
+}
+
+/* Text-level semantics
+ ========================================================================== */
+
+/**
+ * 1. Remove the gray background on active links in IE 10.
+ * 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
+ */
+
+a {
+ background-color: transparent; /* 1 */
+ -webkit-text-decoration-skip: objects; /* 2 */
+}
+
+/**
+ * Remove the outline on focused links when they are also active or hovered
+ * in all browsers (opinionated).
+ */
+
+a:active,
+a:hover {
+ outline-width: 0;
+}
+
+/**
+ * 1. Remove the bottom border in Firefox 39-.
+ * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
+ */
+
+abbr[title] {
+ border-bottom: none; /* 1 */
+ text-decoration: underline; /* 2 */
+ text-decoration: underline dotted; /* 2 */
+}
+
+/**
+ * Prevent the duplicate application of `bolder` by the next rule in Safari 6.
+ */
+
+b,
+strong {
+ font-weight: inherit;
+}
+
+/**
+ * Add the correct font weight in Chrome, Edge, and Safari.
+ */
+
+b,
+strong {
+ font-weight: bolder;
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+code,
+kbd,
+samp {
+ font-family: monospace, monospace; /* 1 */
+ font-size: 1em; /* 2 */
+}
+
+/**
+ * Add the correct font style in Android 4.3-.
+ */
+
+dfn {
+ font-style: italic;
+}
+
+/**
+ * Add the correct background and color in IE 9-.
+ */
+
+mark {
+ background-color: #ff0;
+ color: #000;
+}
+
+/**
+ * Add the correct font size in all browsers.
+ */
+
+small {
+ font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` elements from affecting the line height in
+ * all browsers.
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+sup {
+ top: -0.5em;
+}
+
+/* Embedded content
+ ========================================================================== */
+
+/**
+ * Add the correct display in IE 9-.
+ */
+
+audio,
+video {
+ display: inline-block;
+}
+
+/**
+ * Add the correct display in iOS 4-7.
+ */
+
+audio:not([controls]) {
+ display: none;
+ height: 0;
+}
+
+/**
+ * Remove the border on images inside links in IE 10-.
+ */
+
+img {
+ border-style: none;
+}
+
+/**
+ * Hide the overflow in IE.
+ */
+
+svg:not(:root) {
+ overflow: hidden;
+}
+
+/* Forms
+ ========================================================================== */
+
+/**
+ * 1. Change the font styles in all browsers (opinionated).
+ * 2. Remove the margin in Firefox and Safari.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ font-family: sans-serif; /* 1 */
+ font-size: 100%; /* 1 */
+ line-height: 1.15; /* 1 */
+ margin: 0; /* 2 */
+}
+
+/**
+ * Show the overflow in IE.
+ * 1. Show the overflow in Edge.
+ */
+
+button,
+input { /* 1 */
+ overflow: visible;
+}
+
+/**
+ * Remove the inheritance of text transform in Edge, Firefox, and IE.
+ * 1. Remove the inheritance of text transform in Firefox.
+ */
+
+button,
+select { /* 1 */
+ text-transform: none;
+}
+
+/**
+ * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
+ * controls in Android 4.
+ * 2. Correct the inability to style clickable types in iOS and Safari.
+ */
+
+button,
+html [type="button"], /* 1 */
+[type="reset"],
+[type="submit"] {
+ -webkit-appearance: button; /* 2 */
+}
+
+/**
+ * Remove the inner border and padding in Firefox.
+ */
+
+button::-moz-focus-inner,
+[type="button"]::-moz-focus-inner,
+[type="reset"]::-moz-focus-inner,
+[type="submit"]::-moz-focus-inner {
+ border-style: none;
+ padding: 0;
+}
+
+/**
+ * Restore the focus styles unset by the previous rule.
+ */
+
+button:-moz-focusring,
+[type="button"]:-moz-focusring,
+[type="reset"]:-moz-focusring,
+[type="submit"]:-moz-focusring {
+ outline: 1px dotted ButtonText;
+}
+
+/**
+ * Change the border, margin, and padding in all browsers (opinionated).
+ */
+
+fieldset {
+ border: 1px solid #c0c0c0;
+ margin: 0 2px;
+ padding: 0.35em 0.625em 0.75em;
+}
+
+/**
+ * 1. Correct the text wrapping in Edge and IE.
+ * 2. Correct the color inheritance from `fieldset` elements in IE.
+ * 3. Remove the padding so developers are not caught out when they zero out
+ * `fieldset` elements in all browsers.
+ */
+
+legend {
+ box-sizing: border-box; /* 1 */
+ color: inherit; /* 2 */
+ display: table; /* 1 */
+ max-width: 100%; /* 1 */
+ padding: 0; /* 3 */
+ white-space: normal; /* 1 */
+}
+
+/**
+ * 1. Add the correct display in IE 9-.
+ * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera.
+ */
+
+progress {
+ display: inline-block; /* 1 */
+ vertical-align: baseline; /* 2 */
+}
+
+/**
+ * Remove the default vertical scrollbar in IE.
+ */
+
+textarea {
+ overflow: auto;
+}
+
+/**
+ * 1. Add the correct box sizing in IE 10-.
+ * 2. Remove the padding in IE 10-.
+ */
+
+[type="checkbox"],
+[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * Correct the cursor style of increment and decrement buttons in Chrome.
+ */
+
+[type="number"]::-webkit-inner-spin-button,
+[type="number"]::-webkit-outer-spin-button {
+ height: auto;
+}
+
+/**
+ * 1. Correct the odd appearance in Chrome and Safari.
+ * 2. Correct the outline style in Safari.
+ */
+
+[type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ outline-offset: -2px; /* 2 */
+}
+
+/**
+ * Remove the inner padding and cancel buttons in Chrome and Safari on macOS.
+ */
+
+[type="search"]::-webkit-search-cancel-button,
+[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/**
+ * 1. Correct the inability to style clickable types in iOS and Safari.
+ * 2. Change font properties to `inherit` in Safari.
+ */
+
+::-webkit-file-upload-button {
+ -webkit-appearance: button; /* 1 */
+ font: inherit; /* 2 */
+}
+
+/* Interactive
+ ========================================================================== */
+
+/*
+ * Add the correct display in IE 9-.
+ * 1. Add the correct display in Edge, IE, and Firefox.
+ */
+
+details, /* 1 */
+menu {
+ display: block;
+}
+
+/*
+ * Add the correct display in all browsers.
+ */
+
+summary {
+ display: list-item;
+}
+
+/* Scripting
+ ========================================================================== */
+
+/**
+ * Add the correct display in IE 9-.
+ */
+
+canvas {
+ display: inline-block;
+}
+
+/**
+ * Add the correct display in IE.
+ */
+
+template {
+ display: none;
+}
+
+/* Hidden
+ ========================================================================== */
+
+/**
+ * Add the correct display in IE 10-.
+ */
+
+[hidden] {
+ display: none;
+}
diff --git a/assets/sass/styles.scss b/assets/sass/styles.scss
index b534351..8cedb44 100644
--- a/assets/sass/styles.scss
+++ b/assets/sass/styles.scss
@@ -1,7 +1,10 @@
+@import "normalize";
+
body {
font-family: Arial, Verdana, sans-serif;
font-size: 14px;
color: #333;
+ background: #f5f5f5;
}
h1, h2, h3 {
@@ -22,6 +25,16 @@ table {
}
}
+.block {
+ width: auto;
+ background: white;
+ padding: 20px;
+ margin-bottom: 20px;
+
+ h1, h2, h3 {
+ margin-top: 0;
+ }
+}
.count {
font-size: 24px;
diff --git a/models/pageview.go b/models/pageview.go
index f5acfa5..871d7d5 100644
--- a/models/pageview.go
+++ b/models/pageview.go
@@ -3,4 +3,5 @@ package models
type Pageview struct {
Path string
Count int
+ CountUnique int
}
diff --git a/package.json b/package.json
index d570002..de6c93b 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"babelify": "^7.3.0",
+ "chart.js": "^2.4.0",
"gulp-util": "^3.0.7",
"react-dom": "^15.4.0"
}
diff --git a/yarn.lock b/yarn.lock
index 3e778c8..2992094 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -842,6 +842,26 @@ chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1:
strip-ansi "^3.0.0"
supports-color "^2.0.0"
+chart.js:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.4.0.tgz#44198073f0f43e5e16662e108420d92652a3c9a3"
+ dependencies:
+ chartjs-color "^2.0.0"
+ moment "^2.10.6"
+
+chartjs-color-string@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/chartjs-color-string/-/chartjs-color-string-0.4.0.tgz#57748d4530ae28d8db0a5492182ba06dfdf2f468"
+ dependencies:
+ color-name "^1.0.0"
+
+chartjs-color@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/chartjs-color/-/chartjs-color-2.0.0.tgz#7f60c7256589b24914814ece757659117381e35b"
+ dependencies:
+ chartjs-color-string "^0.4.0"
+ color-convert "^0.5.3"
+
cipher-base@^1.0.0, cipher-base@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.3.tgz#eeabf194419ce900da3018c207d212f2a6df0a07"
@@ -872,6 +892,14 @@ code-point-at@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
+color-convert@^0.5.3:
+ version "0.5.3"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-0.5.3.tgz#bdb6c69ce660fadffe0b0007cc447e1b9f7282bd"
+
+color-name@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.1.tgz#4b1415304cf50028ea81643643bd82ea05803689"
+
combine-source-map@~0.7.1:
version "0.7.2"
resolved "https://registry.yarnpkg.com/combine-source-map/-/combine-source-map-0.7.2.tgz#0870312856b307a87cc4ac486f3a9a62aeccc09e"
@@ -2276,6 +2304,10 @@ module-deps@^4.0.8:
through2 "^2.0.0"
xtend "^4.0.0"
+moment@^2.10.6:
+ version "2.17.0"
+ resolved "https://registry.yarnpkg.com/moment/-/moment-2.17.0.tgz#a4c292e02aac5ddefb29a6eed24f51938dd3b74f"
+
ms@0.7.2:
version "0.7.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765"