redis_analytics 0.1.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 -->