redis_analytics 0.1.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/CHANGELOG.md +12 -0
  2. data/CONTRIBUTING.md +33 -0
  3. data/Guardfile +10 -0
  4. data/README.md +105 -23
  5. data/Rakefile +3 -11
  6. data/TODO.md +12 -0
  7. data/bin/redis_analytics_dashboard +1 -1
  8. data/config.ru +13 -0
  9. data/lib/redis_analytics.rb +5 -1
  10. data/lib/redis_analytics/analytics.rb +17 -203
  11. data/lib/redis_analytics/api.rb +60 -0
  12. data/lib/redis_analytics/configuration.rb +47 -16
  13. data/lib/redis_analytics/dashboard.rb +28 -89
  14. data/lib/redis_analytics/dashboard/public/{favicon.ico → img/favicon.ico} +0 -0
  15. data/lib/redis_analytics/dashboard/public/javascripts/{bootstrap.min.js → vendor/bootstrap/bootstrap.min.js} +0 -0
  16. data/lib/redis_analytics/dashboard/public/javascripts/{jquery-1.9.1.min.js → vendor/jquery-1.9.1.min.js} +0 -0
  17. data/lib/redis_analytics/dashboard/public/javascripts/{jquery-jvectormap-1.2.2.min.js → vendor/jquery-jvectormap-1.2.2.min.js} +0 -0
  18. data/lib/redis_analytics/dashboard/public/javascripts/{jquery-jvectormap-world-mill-en.js → vendor/jquery-jvectormap-world-mill-en.js} +0 -0
  19. data/lib/redis_analytics/dashboard/public/javascripts/{morris.min.js → vendor/morris.min.js} +0 -0
  20. data/lib/redis_analytics/dashboard/public/javascripts/{raphael-min.js → vendor/raphael-min.js} +0 -0
  21. data/lib/redis_analytics/dashboard/views/activity.erb +7 -0
  22. data/lib/redis_analytics/dashboard/views/dialogs/unique_visits.erb +41 -0
  23. data/lib/redis_analytics/dashboard/views/dialogs/visits.erb +40 -0
  24. data/lib/redis_analytics/dashboard/views/footer.erb +1 -3
  25. data/lib/redis_analytics/dashboard/views/header.erb +17 -42
  26. data/lib/redis_analytics/dashboard/views/layout.erb +5 -22
  27. data/lib/redis_analytics/dashboard/views/visits.erb +38 -143
  28. data/lib/redis_analytics/dashboard/views/visits_js.erb +110 -247
  29. data/lib/redis_analytics/dashboard/views/widgets/bounce_rate.erb +3 -0
  30. data/lib/redis_analytics/dashboard/views/widgets/browsers_donut.erb +8 -0
  31. data/lib/redis_analytics/dashboard/views/widgets/first_visits.erb +3 -0
  32. data/lib/redis_analytics/dashboard/views/widgets/page_depth.erb +3 -0
  33. data/lib/redis_analytics/dashboard/views/widgets/referers_donut.erb +8 -0
  34. data/lib/redis_analytics/dashboard/views/widgets/total_page_views.erb +3 -0
  35. data/lib/redis_analytics/dashboard/views/widgets/total_visits.erb +3 -0
  36. data/lib/redis_analytics/dashboard/views/widgets/unique_visits_line.erb +26 -0
  37. data/lib/redis_analytics/dashboard/views/widgets/visit_duration.erb +3 -0
  38. data/lib/redis_analytics/dashboard/views/widgets/visit_spark.erb +23 -0
  39. data/lib/redis_analytics/dashboard/views/widgets/visitor_recency_slices.erb +39 -0
  40. data/lib/redis_analytics/dashboard/views/widgets/visits_area.erb +30 -0
  41. data/lib/redis_analytics/dashboard/views/widgets/visits_donut.erb +8 -0
  42. data/lib/redis_analytics/dashboard/views/widgets/world_map.erb +50 -0
  43. data/lib/redis_analytics/filter.rb +33 -0
  44. data/lib/redis_analytics/helpers.rb +36 -30
  45. data/lib/redis_analytics/metrics.rb +96 -0
  46. data/lib/redis_analytics/time_ext.rb +72 -2
  47. data/lib/redis_analytics/tracker.rb +13 -5
  48. data/lib/redis_analytics/version.rb +1 -1
  49. data/lib/redis_analytics/visit.rb +122 -0
  50. data/redis_analytics.gemspec +19 -14
  51. data/spec/lib/redis_analytics/analytics_spec.rb +59 -0
  52. data/spec/lib/redis_analytics/configuration_spec.rb +158 -0
  53. data/spec/lib/redis_analytics/dashboard_spec.rb +32 -0
  54. data/spec/lib/redis_analytics/filter_spec.rb +34 -0
  55. data/spec/lib/redis_analytics/tracker_spec.rb +20 -0
  56. data/spec/spec_helper.rb +13 -6
  57. data/spec/support/fakeredis.rb +1 -0
  58. data/wsd.png +0 -0
  59. metadata +268 -126
  60. data/lib/redis_analytics/config.ru +0 -10
  61. data/spec/redis_analytics_spec.rb +0 -57
@@ -0,0 +1,60 @@
1
+ require 'sinatra/base'
2
+ require 'json'
3
+
4
+ module Rack
5
+ module RedisAnalytics
6
+
7
+ class Api < Sinatra::Base
8
+ helpers Rack::RedisAnalytics::Helpers
9
+
10
+ get '/data/?' do
11
+
12
+ begin
13
+ to_date_time = Date.parse(params[:to_date_time]).to_time rescue Time.now
14
+ unit = params[:unit] || 'day'
15
+ aggregate = (params[:aggregate] == 'yes')
16
+ units = (params[:unit_count] || 1).to_i
17
+ p = params[:p].split(',')
18
+ from_date_time = to_date_time - units.send(unit)
19
+ results = []
20
+
21
+ p.each_with_index do |q, j|
22
+ result = self.send("#{unit}ly_#{q}", from_date_time, :to_date => to_date_time, :aggregate => aggregate)
23
+ if result.is_a?(Array) # time range data (non-aggregate)
24
+ result.each_with_index do |r, i|
25
+ results[i] ||= {}
26
+ date_value = r[0][0..2]
27
+ time_value = r[0][3..-1]
28
+ date_time_value = []
29
+ date_time_value << date_value.join('-')
30
+ date_time_value << time_value.join(':') if time_value
31
+ # results[i]['raw'] = date_time_value.join(' ').strip
32
+ results[i]['unix'] = Time.mktime(*r[0].map(&:to_i)).to_i
33
+ strf = case unit
34
+ when 'minute'
35
+ '%H%Mhrs'
36
+ when 'hour'
37
+ '%a %H00hrs'
38
+ when 'day'
39
+ '%a'
40
+ when 'month'
41
+ '%b'
42
+ when 'year'
43
+ '%Y'
44
+ end
45
+ results[i]['raw'] = Time.at(results[i]['unix']).strftime(strf)
46
+ results[i][q] = r[1]
47
+ end
48
+ elsif result.is_a?(Hash) or result.is_a?(Fixnum) # aggregate data
49
+ results[j] = {q => result}
50
+ end
51
+ end
52
+ content_type :json
53
+ results.to_json
54
+ rescue Exception => e
55
+ halt 500, [500, [e.message, e.backtrace]].to_json
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -3,23 +3,30 @@ module Rack
3
3
  module Configuration
4
4
  # Redis connection instance
5
5
  attr_accessor :redis_connection
6
-
6
+
7
7
  # Redis namespace for keys
8
8
  attr_writer :redis_namespace
9
9
 
10
- # Name of the cookie which tracks returning visitors (known visitors)
11
- attr_writer :returning_user_cookie_name
10
+ # Name of the cookie which tracks first visitors
11
+ attr_writer :first_visit_cookie_name
12
12
 
13
- # Name of the cookie which tracks visits
14
- attr_writer :visit_cookie_name
13
+ # Name of the cookie which tracks current visits
14
+ attr_writer :current_visit_cookie_name
15
15
 
16
16
  # Minutes the visit should timeout after (if no hit is received)
17
17
  attr_writer :visit_timeout
18
18
 
19
19
  # Endpoint for dashboard
20
- attr_accessor :dashboard_endpoint
20
+ attr_writer :dashboard_endpoint
21
21
 
22
- # Redis namespace for keys
22
+ # Endpoint for api
23
+ attr_writer :api_endpoint
24
+
25
+ attr_writer :path_filters
26
+
27
+ attr_writer :filters
28
+
29
+ # Path to the Geo IP Database file
23
30
  attr_writer :geo_ip_data_path
24
31
 
25
32
  # Redis namespace for keys
@@ -32,16 +39,32 @@ module Rack
32
39
  @visit_timeout ||= 1 # minutes
33
40
  end
34
41
 
35
- # Name of the cookie which tracks returning visitors (known visitors)
36
- def returning_user_cookie_name
37
- @returning_user_cookie_name ||= '_rucn'
42
+ # Name of the cookie which tracks first visitors (unknown visitors)
43
+ def first_visit_cookie_name
44
+ @first_visit_cookie_name ||= '_rucn'
38
45
  end
39
46
 
40
47
  # Name of the cookie which tracks visits
41
- def visit_cookie_name
42
- @visit_cookie_name ||= '_vcn'
48
+ def current_visit_cookie_name
49
+ @current_visit_cookie_name ||= '_vcn'
43
50
  end
44
-
51
+
52
+ def filters
53
+ @filters ||= []
54
+ end
55
+
56
+ def path_filters
57
+ @path_filters ||= []
58
+ end
59
+
60
+ def add_filter(&proc)
61
+ filters << RedisAnalytics::Filter.new(proc)
62
+ end
63
+
64
+ def add_path_filter(path)
65
+ path_filters << RedisAnalytics::PathFilter.new(path)
66
+ end
67
+
45
68
  def geo_ip_data_path
46
69
  @geo_ip_data_path = ::File.expand_path(::File.join(::File.dirname(__FILE__),'..','..')) + "/bin/GeoIP.dat"
47
70
  end
@@ -55,17 +78,25 @@ module Rack
55
78
  end
56
79
 
57
80
  def redis_key_timestamps # [format, expire in seconds or nil]
58
- ['%Y', '%Y_%m', '%Y_%m_%d', '%Y_%m_%d_%H', ['%Y_%m_%d_%H_%M', 1.day + 1.minute]]
81
+ ['%Y', '%Y_%m', '%Y_%m_%d', '%Y_%m_%d_%H', '%Y_%m_%d_%H_%M']
59
82
  end
60
83
 
61
84
  def time_range_formats
62
85
  [[:year, :month, "%b"], [:week, :day, "%a"], [:day, :hour, "%l%P"]]
63
86
  end
64
-
87
+
65
88
  def configure
66
89
  yield self
67
90
  end
68
-
91
+
92
+ def api_endpoint
93
+ @api_endpoint || dashboard_endpoint + '/api'
94
+ end
95
+
96
+ def dashboard_endpoint
97
+ @dashboard_endpoint || '/redis_analytics'
98
+ end
99
+
69
100
  end
70
101
  end
71
102
  end
@@ -1,7 +1,5 @@
1
1
  require 'sinatra/base'
2
- require 'json'
3
- require 'active_support/core_ext'
4
- require 'redis_analytics'
2
+ require 'sinatra/assetpack'
5
3
 
6
4
  if defined? Encoding
7
5
  Encoding.default_external = Encoding::UTF_8
@@ -9,106 +7,47 @@ end
9
7
 
10
8
  module Rack
11
9
  module RedisAnalytics
12
- class Dashboard < Sinatra::Base
13
-
14
- dir = ::File.expand_path(::File.dirname(__FILE__))
15
-
16
- set :views, "#{dir}/dashboard/views"
17
-
18
- if respond_to? :public_folder
19
- set :public_folder, "#{dir}/dashboard/public"
20
- else
21
- set :public, "#{dir}/dashboard/public"
22
- end
23
-
24
- helpers do
25
- include Rack::RedisAnalytics::Helpers
26
10
 
27
- def realistic(n, r = 1000)
28
- return n
29
- n + r + rand(r)
30
- end
31
-
32
- def parse_float(float)
33
- float.nan? ? '0.0' : float
34
- end
35
-
36
- def with_benchmarking
37
- @t0 = Time.now
38
- yield
39
- @t1 = Time.now
40
- @t = @t1 - @t0
41
- puts "Time Taken: #{@t} seconds"
42
- end
11
+ class Dashboard < Sinatra::Base
12
+ register Sinatra::AssetPack
43
13
 
44
- end
45
-
14
+ set :root, ::File.expand_path(::File.dirname(__FILE__))
15
+ set :views, "#{settings.root}/dashboard/views"
46
16
  set :static, true
47
17
 
48
- def initialize
49
- $template_prefix = Rack::RedisAnalytics.dashboard_endpoint
50
- super
18
+ helpers Rack::RedisAnalytics::Helpers
19
+
20
+ assets do
21
+ serve '/css', from: "dashboard/public/css"
22
+ serve '/javascripts', from: "dashboard/public/javascripts"
23
+ serve '/img', from: "dashboard/public/img"
24
+ js :app, [
25
+ '/javascripts/vendor/*.js',
26
+ '/javascripts/*.js'
27
+ ]
28
+ js :bootstrap, [
29
+ '/javascripts/vendor/bootstrap/*.js'
30
+ ]
31
+ css :application, [
32
+ '/css/*.css'
33
+ ]
34
+ js_compression :jsmin # :jsmin | :yui | :closure | :uglify
35
+ css_compression :simple # :simple | :sass | :yui | :sqwish
51
36
  end
52
37
 
53
38
  get '/' do
54
- status, headers, body = call env.merge("PATH_INFO" => '/visits')
55
- [status, headers, body]
39
+ redirect url('visits')
56
40
  end
57
41
 
58
- get '/activity' do
59
- with_benchmarking do
60
- # code
61
- end
42
+ get '/activity/?' do
62
43
  erb :activity
63
44
  end
64
45
 
65
- get '/visits' do
66
- with_benchmarking do
67
- @range = (request.cookies["_rarng"] || RedisAnalytics.default_range).to_sym # should first try to fetch from cookie what the default range is
68
- @data = {}
69
-
70
- RedisAnalytics.time_range_formats.each do |range, unit, time_format|
71
- multiple = (1.send(range)/1.send(unit)).round
72
- time_range = @t0 - 1.send(range) + 1.send(unit)
73
- @data[range] ||= {}
74
- @data[range][:visits] = self.send("#{unit}ly_visits".to_sym, time_range)
75
- @data[range][:total_visits] = @data[range][:visits].inject(0){|s, x| s += x[1].to_i; s}
76
- @data[range][:new_visits] = self.send("#{unit}ly_new_visits", time_range)
77
- @data[range][:total_new_visits] = @data[range][:new_visits].inject(0){|s, x| s += x[1].to_i; s}
78
- @data[range][:page_views] = self.send("#{unit}ly_page_views", time_range)
79
-
80
- @data[range][:total_page_views] = @data[range][:page_views].inject(0){|s, x| s += x[1].to_i; s}
81
- @data[range][:visit_time] = self.send("#{unit}ly_visit_time", time_range)
82
- @data[range][:avg_visit_time] = Hash[@data[range][:visit_time]].values.sum.to_f/@data[range][:visit_time].length.to_f
83
-
84
- @data[range][:visits_new_visits_plot] = @data[range][:new_visits].inject(Hash[@data[range][:visits]]){|a, i| a[i[0]] = [i[1], a[i[0]]];a}.map{|k,v| {'date'=> k.strftime(time_format), 'new_visits' => v[0].to_i, 'returning_visits' => v[1].to_i - v[0].to_i}}
85
- @data[range][:visits_new_visits_donut] = [{'label' => 'Returning Visitors', 'value' => @data[range][:total_visits] - @data[range][:total_new_visits]}, {'label' => 'New Visitors', 'value' => @data[range][:total_new_visits]}]
86
-
87
- @data[range][:browsers_donut] = self.send("#{unit}ly_ratio_browsers", time_range, :aggregate => true).map{|x| {'label' => x[0], 'value' => x[1].to_i}}
88
- @data[range][:platforms_donut] = self.send("#{unit}ly_ratio_platforms", time_range, :aggregate => true).map{|x| {'label' => x[0], 'value' => x[1].to_i}}
89
- @data[range][:devices_donut] = self.send("#{unit}ly_ratio_platforms", time_range, :aggregate => true).map{|x| {'label' => x[0], 'value' => x[1].to_i}}
90
- @data[range][:referrers_donut] = self.send("#{unit}ly_ratio_referrers", time_range, :aggregate => true).map{|x| {'label' => x[0], 'value' => x[1].to_i}}
91
-
92
- unique_visits = self.send("#{unit}ly_unique_visits", time_range - 1.send(range)).map{|x,y| [x.strftime(time_format), y]}
93
-
94
- # puts unique_visits[0..(multiple-1)].inject(Hash[unique_visits[multiple..(multiple*2-1)]]){|a, i| a[i[0]] = [i[1], a[i[0]]];a}.inspect
95
- @data[range][:unique_visits] = unique_visits[0..(multiple-1)].inject(Hash[unique_visits[multiple..(multiple*2-1)]]){|a, i| a[i[0]] = [i[1], a[i[0]]];a}.map{|k,v| {'unit'=> k, 'unique_visits_last' => v[0].to_i, 'unique_visits_this' => v[1].to_i}}
96
- second_page_views = self.send("#{unit}ly_second_page_views", time_range)
97
- @data[range][:total_second_page_views] = second_page_views.inject(0){|s, x| s += x[1].to_i; s}
98
-
99
- visitor_recency = self.send("#{unit}ly_ratio_recency", time_range, :aggregate => true)
100
- @data[range][:visitor_recency_slices] = [0, RedisAnalytics.visitor_recency_slices, '*'].flatten.each_cons(2).inject([]) do |h, (x, y)|
101
- h << [[x, y], visitor_recency.select{|a, b| a.to_i >= x and (a.to_i < y or y == '*') }.map{|p, q| q}.sum]
102
- end
103
- @data[range][:country_map] = Hash[self.send("#{unit}ly_ratio_country", time_range, :aggregate => true)]
104
- end
105
- @range = @data.keys[0] unless @data[@range]
106
- end
107
- @data[:all_visits] = daily_visits(Time.now - 1.year).map{|x,y| [x.to_i * 1000, realistic(y)]}
108
- @data[:all_unique_visits] = daily_unique_visits(Time.now - 1.year).map{|x,y| [x.to_i * 1000, realistic(y)]}
46
+ get '/visits/?' do
47
+ @range = time_range
109
48
  erb :visits
110
49
  end
111
-
50
+
112
51
  end
113
52
  end
114
53
  end
@@ -0,0 +1,7 @@
1
+ <div class="container-fluid">
2
+ <div class="row-fluid">
3
+ <div class="span12 well">
4
+ This feature is not implemented yet.
5
+ </div>
6
+ </div>
7
+ </div>
@@ -0,0 +1,41 @@
1
+ <!-- Unique Visits Modal -->
2
+ <div id="unique_visits_modal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
3
+ <div class="modal-header">
4
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
5
+ <h3 id="myModalLabel">Daily Unique Visits</h3>
6
+ </div>
7
+ <div class="modal-body">
8
+ <div id="unique_visits_detail" style="width:900px;height:400px;"></div>
9
+ </div>
10
+ <div class="modal-footer">
11
+ <button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
12
+ <!-- <button class="btn btn-primary">Save changes</button> -->
13
+ </div>
14
+ </div>
15
+
16
+ <script type="text/javascript">
17
+ $('#unique_visits_modal').on('show', function () {
18
+ colors = Highcharts.getOptions().colors;
19
+ // create the chart when all data is loaded
20
+ $(function(){
21
+ $('#unique_visits_detail').highcharts('StockChart', {
22
+ chart: {
23
+ },
24
+ // tooltip: {
25
+ // pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b> ({point.change}%)<br/>',
26
+ // valueDecimals: 2
27
+ // },
28
+ yAxis: {
29
+ allowDecimals: false
30
+ },
31
+ xAxis: {
32
+ allowDecimals: false
33
+ },
34
+ series: [{
35
+ name: 'Unique Visits',
36
+ data: <%=@data[:all_unique_visits].to_json%>
37
+ }]
38
+ });
39
+ });
40
+ });
41
+ </script>
@@ -0,0 +1,40 @@
1
+ <!-- Visits Modal -->
2
+ <div id="visits_modal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
3
+ <div class="modal-header">
4
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
5
+ <h3 id="myModalLabel">Daily Visits</h3>
6
+ </div>
7
+ <div class="modal-body">
8
+ <div id="visits_detail" style="width:900px;height:400px;"></div>
9
+ </div>
10
+ <div class="modal-footer">
11
+ <button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
12
+ <!-- <button class="btn btn-primary">Save changes</button> -->
13
+ </div>
14
+ </div>
15
+
16
+ <script type="text/javascript">
17
+ $('#visits_modal').on('show', function () {
18
+ colors = Highcharts.getOptions().colors;
19
+ // create the chart when all data is loaded
20
+ $(function(){
21
+ $('#visits_detail').highcharts('StockChart', {
22
+ chart: {},
23
+ // tooltip: {
24
+ // pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b> ({point.change}%)<br/>',
25
+ // valueDecimals: 2
26
+ // },
27
+ yAxis: {
28
+ allowDecimals: false
29
+ },
30
+ xAxis: {
31
+ allowDecimals: false
32
+ },
33
+ series: [{
34
+ name: 'Visits',
35
+ data: <%=@data[:all_visits].to_json%>
36
+ }]
37
+ });
38
+ });
39
+ });
40
+ </script>
@@ -1,5 +1,3 @@
1
1
  <!-- start footer -->
2
-
3
-
4
- <script language="javascript" type="text/javascript" src="<%=$template_prefix%>/javascripts/bootstrap.min.js"></script>
2
+ <%= js :bootstrap %>
5
3
  <!-- end footer -->
@@ -1,44 +1,19 @@
1
1
  <!-- start header -->
2
- <div class="navbar">
3
- <div class="navbar-inner">
4
- <a class="brand" href="http://www.github.com/saturnine/redis_analytics">Redis Analytics</a>
5
- <ul class="nav nav-pills pull-right">
6
- <!-- <li><a><small><%#@t.round(2)%> sec</small></a></li> -->
7
- <li>
8
- <div class="btn-group">
9
- <!-- <button class="btn btn-small dropdown-toggle" data-toggle="dropdown">Settings</button> -->
10
- <ul class="dropdown-menu">
11
-
12
- <li class="dropdown-submenu">
13
- <a tabindex="-1" href="#">Theme</a>
14
- <ul class="dropdown-menu">
15
- <li><a href="?template=journal">Journal</a></li>
16
- <li><a href="?template=spacelab">Spacelab</a></li>
17
- <li><a href="?template=united">United</a></li>
18
- <li class="divider"></li>
19
- <li><a href="?">Default</a></li>
20
- </ul>
21
- </li>
22
- <li><a href="#">Preferences</a></li>
23
- <li class="divider"></li>
24
- <li><a href="#">About</a></li>
25
- </ul>
26
- </div>
27
-
28
- <div class="btn-group" data-toggle="buttons-radio">
29
- <!-- <a href="/visits" class="btn btn-small<%#' active' if request.path == '/visits'%>">Visits</a> -->
30
- <!-- <a href="/activity" class="btn btn-small<%=' active' if request.path == '/activity'%>">Activity</a> -->
31
-
32
- <!-- <button type="button" class="btn btn-small" onclick='javascript:visits_area.grid=true'>Transactions</button> -->
33
- </div>
34
- <div class="btn-group" data-toggle="buttons-radio">
35
- <% Rack::RedisAnalytics.time_range_formats.map{|x| x[0]}.each do |range| %>
36
- <button type="button" class="btn btn-small <%="active" if @range == range%>" onclick='changeTimeFrame("<%=range.to_s%>")'><%=range.to_s%></button>
37
- <% end %>
38
- </div>
39
-
40
- </li>
41
- </ul>
42
- </div>
2
+ <div class="navbar">
3
+ <div class="navbar-inner">
4
+ <a class="brand" href="http://www.github.com/saturnine/redis_analytics">Redis Analytics</a>
5
+ <ul class="nav nav-pills pull-right">
6
+ <li>
7
+ <div class="btn-group" data-toggle="buttons-radio">
8
+ <a href="visits" class="btn btn-small <%=' active' if request.path.end_with?('/visits') %>">Visits</a>
9
+ </div>
10
+ <div class="btn-group" data-toggle="buttons-radio" id="timeRange">
11
+ <% Rack::RedisAnalytics.time_range_formats.map{|x| x[0]}.each do |range| %>
12
+ <button type="button" class="btn btn-small <%="active" if @range == range%>" onclick='changeTimeFrame("<%=range.to_s%>")'><%=range.to_s%></button>
13
+ <% end %>
14
+ </div>
15
+ </li>
16
+ </ul>
43
17
  </div>
44
- <!-- end header -->
18
+ </div>
19
+ <!-- end header -->