gdash 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'sinatra'
3
+ require 'yaml'
4
+ require 'erb'
5
+ require 'redcarpet'
6
+
7
+ class GDash
8
+ require 'gdash/dashboard'
9
+ require 'gdash/monkey_patches'
10
+ require 'gdash/sinatra_app'
11
+ require 'graphite_graph'
12
+
13
+ attr_reader :graphite_base, :graphite_render, :dash_templates, :height, :width, :from, :until
14
+
15
+ def initialize(graphite_base, render_url, dash_templates, options={})
16
+ @graphite_base = graphite_base
17
+ @graphite_render = [@graphite_base, "/render/"].join
18
+ @dash_templates = dash_templates
19
+ @height = options.delete(:height)
20
+ @width = options.delete(:width)
21
+ @from = options.delete(:from)
22
+ @until = options.delete(:until)
23
+
24
+ raise "Dashboard templates directory #{@dash_templates} does not exist" unless File.directory?(@dash_templates)
25
+ end
26
+
27
+ def dashboard(name, options={})
28
+ options[:width] ||= @width
29
+ options[:height] ||= @height
30
+ options[:from] ||= @from
31
+ options[:until] ||= @until
32
+
33
+ Dashboard.new(name, dash_templates, options)
34
+ end
35
+
36
+ def list
37
+ dashboards.map {|dash| dash[:link]}
38
+ end
39
+
40
+ def dashboards
41
+ dashboards = []
42
+
43
+ Dir.entries(dash_templates).each do |dash|
44
+ begin
45
+ yaml_file = File.join(dash_templates, dash, "dash.yaml")
46
+ if File.exist?(yaml_file)
47
+ dashboards << YAML.load_file(yaml_file).merge({:link => dash})
48
+ end
49
+ rescue Exception => e
50
+ p e
51
+ end
52
+ end
53
+
54
+ dashboards.sort_by{|d| d[:name]}
55
+ end
56
+ end
@@ -0,0 +1,51 @@
1
+ class GDash
2
+ class Dashboard
3
+ attr_accessor :properties
4
+
5
+ def initialize(short_name, dir, options={})
6
+ raise "Cannot find dashboard directory #{dir}" unless File.directory?(dir)
7
+
8
+ @properties = {:graph_width => nil,
9
+ :graph_height => nil,
10
+ :graph_from => nil,
11
+ :graph_until => nil}
12
+
13
+ @properties[:short_name] = short_name
14
+ @properties[:directory] = File.join(dir, short_name)
15
+ @properties[:yaml] = File.join(dir, short_name, "dash.yaml")
16
+
17
+ raise "Cannot find YAML file #{yaml}" unless File.exist?(yaml)
18
+
19
+ @properties.merge!(YAML.load_file(yaml))
20
+
21
+ # Properties defined in dashboard config file are overridden when given on initialization
22
+ @properties[:graph_width] = options.delete(:width) || graph_width
23
+ @properties[:graph_height] = options.delete(:height) || graph_height
24
+ @properties[:graph_from] = options.delete(:from) || graph_from
25
+ @properties[:graph_until] = options.delete(:until) || graph_until
26
+ end
27
+
28
+ def graphs(options={})
29
+ options[:width] ||= graph_width
30
+ options[:height] ||= graph_height
31
+ options[:from] ||= graph_from
32
+ options[:until] ||= graph_until
33
+
34
+ graphs = Dir.entries(directory).select{|f| f.match(/\.graph$/)}
35
+
36
+ overrides = options.reject { |k,v| v.nil? }
37
+
38
+ graphs.sort.map do |graph|
39
+ {:name => File.basename(graph, ".graph"), :graphite => GraphiteGraph.new(File.join(directory, graph), overrides)}
40
+ end
41
+ end
42
+
43
+ def method_missing(method, *args)
44
+ if properties.include?(method)
45
+ properties[method]
46
+ else
47
+ super
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,37 @@
1
+ class Array
2
+ def in_groups_of(chunk_size, padded_with=nil)
3
+ if chunk_size <= 1
4
+ if block_given?
5
+ self.each{|a| yield([a])}
6
+ else
7
+ self
8
+ end
9
+ else
10
+ arr = self.clone
11
+
12
+ # how many to add
13
+ padding = chunk_size - (arr.size % chunk_size)
14
+ padding = 0 if padding == chunk_size
15
+
16
+ # pad at the end
17
+ arr.concat([padded_with] * padding)
18
+
19
+ # how many chunks we'll make
20
+ count = arr.size / chunk_size
21
+
22
+ # make that many arrays
23
+ result = []
24
+ count.times {|s| result << arr[s * chunk_size, chunk_size]}
25
+
26
+ if block_given?
27
+ result.each{|a| yield(a)}
28
+ else
29
+ result
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ class GraphiteGraph
36
+ attr_accessor :properties, :file
37
+ end
@@ -0,0 +1,162 @@
1
+ class GDash
2
+ class SinatraApp < ::Sinatra::Base
3
+ def initialize(graphite_base, graph_templates, options = {})
4
+ # where the whisper data is
5
+ @whisper_dir = options.delete(:whisper_dir) || "/var/lib/carbon/whisper"
6
+
7
+ # where graphite lives
8
+ @graphite_base = graphite_base
9
+
10
+ # where the graphite renderer is
11
+ @graphite_render = [@graphite_base, "/render/"].join
12
+
13
+ # where to find graph, dash etc templates
14
+ @graph_templates = graph_templates
15
+
16
+ # the dash site might have a prefix for its css etc
17
+ @prefix = options.delete(:prefix) || ""
18
+
19
+ # the page refresh rate
20
+ @refresh_rate = options.delete(:refresh_rate) || 60
21
+
22
+ # how many columns of graphs do you want on a page
23
+ @graph_columns = options.delete(:graph_columns) || 2
24
+
25
+ # how wide each graph should be
26
+ @graph_width = options.delete(:graph_width)
27
+
28
+ # how hight each graph sould be
29
+ @graph_height = options.delete(:graph_height)
30
+
31
+ # Dashboard title
32
+ @dash_title = options.delete(:title) || "Graphite Dashboard"
33
+
34
+ # Time filters in interface
35
+ @interval_filters = options.delete(:interval_filters) || Array.new
36
+
37
+ @intervals = options.delete(:intervals) || []
38
+
39
+ @top_level = Hash.new
40
+ Dir.entries(@graph_templates).each do |category|
41
+ if File.directory?("#{@graph_templates}/#{category}")
42
+ unless ("#{category}" =~ /^\./ )
43
+ @top_level["#{category}"] = GDash.new(@graphite_base, "/render/", File.join(@graph_templates, "/#{category}"), {:width => @graph_width, :height => @graph_height})
44
+ end
45
+ end
46
+ end
47
+
48
+ super()
49
+ end
50
+
51
+ set :static, true
52
+ set :views, File.join(File.expand_path(File.dirname(__FILE__)), "../..", "views")
53
+ if Sinatra.const_defined?("VERSION") && Gem::Version.new(Sinatra::VERSION) >= Gem::Version.new("1.3.0")
54
+ set :public_folder, File.join(File.expand_path(File.dirname(__FILE__)), "../..", "public")
55
+ else
56
+ set :public, File.join(File.expand_path(File.dirname(__FILE__)), "../..", "public")
57
+ end
58
+
59
+ get '/' do
60
+ if @top_level.empty?
61
+ @error = "No dashboards found in the templates directory"
62
+ end
63
+
64
+ erb :index
65
+ end
66
+
67
+ get '/:category/:dash/details/:name' do
68
+ if @top_level["#{params[:category]}"].list.include?(params[:dash])
69
+ @dashboard = @top_level[@params[:category]].dashboard(params[:dash])
70
+ else
71
+ @error = "No dashboard called #{params[:dash]} found in #{params[:category]}/#{@top_level[params[:category]].list.join ','}."
72
+ end
73
+
74
+ if @intervals.empty?
75
+ @error = "No intervals defined in configuration"
76
+ end
77
+
78
+ if main_graph = @dashboard.graphs[params[:name].to_i][:graphite]
79
+ @graphs = @intervals.map do |e|
80
+ new_props = {:from => e[0], :title => "#{main_graph.properties[:title]} - #{e[1]}"}
81
+ new_props = main_graph.properties.merge new_props
82
+ GraphiteGraph.new(main_graph.file, new_props)
83
+ end
84
+ else
85
+ @error = "No such graph available"
86
+ end
87
+
88
+ erb :detailed_dashboard
89
+ end
90
+
91
+ get '/:category/:dash/full/?*' do
92
+ options = {}
93
+ params["splat"] = params["splat"].first.split("/")
94
+
95
+ params["columns"] = params["splat"][0].to_i || @graph_columns
96
+
97
+ if params["splat"].size == 3
98
+ options[:width] = params["splat"][1].to_i
99
+ options[:height] = params["splat"][2].to_i
100
+ else
101
+ options[:width] = @graph_width
102
+ options[:height] = @graph_height
103
+ end
104
+
105
+ options.merge!(query_params)
106
+
107
+ if @top_level["#{params[:category]}"].list.include?(params[:dash])
108
+ @dashboard = @top_level[@params[:category]].dashboard(params[:dash], options)
109
+ else
110
+ @error = "No dashboard called #{params[:dash]} found in #{params[:category]}/#{@top_level[params[:category]].list.join ','}"
111
+ end
112
+
113
+ erb :full_size_dashboard, :layout => false
114
+ end
115
+
116
+ get '/:category/:dash/?*' do
117
+ options = {}
118
+ params["splat"] = params["splat"].first.split("/")
119
+
120
+ case params["splat"][0]
121
+ when 'time'
122
+ options[:from] = params["splat"][1] || "-1hour"
123
+ options[:until] = params["splat"][2] || "now"
124
+ end
125
+
126
+ options.merge!(query_params)
127
+
128
+ if @top_level["#{params[:category]}"].list.include?(params[:dash])
129
+ @dashboard = @top_level[@params[:category]].dashboard(params[:dash], options)
130
+ else
131
+ @error = "No dashboard called #{params[:dash]} found in #{params[:category]}/#{@top_level[params[:category]].list.join ','}."
132
+ end
133
+
134
+ erb :dashboard
135
+ end
136
+
137
+ get '/docs/' do
138
+ markdown :README, :layout_engine => :erb
139
+ end
140
+
141
+ helpers do
142
+ include Rack::Utils
143
+
144
+ alias_method :h, :escape_html
145
+
146
+ def link_to_interval(options)
147
+ "<a href=\"#{ [@prefix, params[:category], params[:dash], 'time', h(options[:from]), h(options[:to])].join('/') }\">#{ h(options[:label]) }</a>"
148
+ end
149
+
150
+ def query_params
151
+ hash = {}
152
+ protected_keys = [:category, :dash, :splat]
153
+
154
+ params.each do |k, v|
155
+ hash[k.to_sym] = v unless protected_keys.include?(k.to_sym)
156
+ end
157
+
158
+ hash
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,104 @@
1
+ /* ==========================================================
2
+ * bootstrap-alerts.js v1.3.0
3
+ * http://twitter.github.com/bootstrap/javascript.html#alerts
4
+ * ==========================================================
5
+ * Copyright 2011 Twitter, Inc.
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ * ========================================================== */
19
+
20
+
21
+ !function( $ ){
22
+
23
+ /* CSS TRANSITION SUPPORT (https://gist.github.com/373874)
24
+ * ======================================================= */
25
+
26
+ var transitionEnd
27
+
28
+ $(document).ready(function () {
29
+
30
+ $.support.transition = (function () {
31
+ var thisBody = document.body || document.documentElement
32
+ , thisStyle = thisBody.style
33
+ , support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined
34
+ return support
35
+ })()
36
+
37
+ // set CSS transition event type
38
+ if ( $.support.transition ) {
39
+ transitionEnd = "TransitionEnd"
40
+ if ( $.browser.webkit ) {
41
+ transitionEnd = "webkitTransitionEnd"
42
+ } else if ( $.browser.mozilla ) {
43
+ transitionEnd = "transitionend"
44
+ } else if ( $.browser.opera ) {
45
+ transitionEnd = "oTransitionEnd"
46
+ }
47
+ }
48
+
49
+ })
50
+
51
+ /* ALERT CLASS DEFINITION
52
+ * ====================== */
53
+
54
+ var Alert = function ( content, selector ) {
55
+ this.$element = $(content)
56
+ .delegate(selector || '.close', 'click', this.close)
57
+ }
58
+
59
+ Alert.prototype = {
60
+
61
+ close: function (e) {
62
+ var $element = $(this).parent('.alert-message')
63
+
64
+ e && e.preventDefault()
65
+ $element.removeClass('in')
66
+
67
+ function removeElement () {
68
+ $element.remove()
69
+ }
70
+
71
+ $.support.transition && $element.hasClass('fade') ?
72
+ $element.bind(transitionEnd, removeElement) :
73
+ removeElement()
74
+ }
75
+
76
+ }
77
+
78
+
79
+ /* ALERT PLUGIN DEFINITION
80
+ * ======================= */
81
+
82
+ $.fn.alert = function ( options ) {
83
+
84
+ if ( options === true ) {
85
+ return this.data('alert')
86
+ }
87
+
88
+ return this.each(function () {
89
+ var $this = $(this)
90
+
91
+ if ( typeof options == 'string' ) {
92
+ return $this.data('alert')[options]()
93
+ }
94
+
95
+ $(this).data('alert', new Alert( this ))
96
+
97
+ })
98
+ }
99
+
100
+ $(document).ready(function () {
101
+ new Alert($('body'), '.alert-message[data-alert] .close')
102
+ })
103
+
104
+ }( window.jQuery || window.ender );
@@ -0,0 +1,50 @@
1
+ /* ============================================================
2
+ * bootstrap-dropdown.js v1.3.0
3
+ * http://twitter.github.com/bootstrap/javascript.html#dropdown
4
+ * ============================================================
5
+ * Copyright 2011 Twitter, Inc.
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ * ============================================================ */
19
+
20
+
21
+ !function( $ ){
22
+
23
+ var d = 'a.menu, .dropdown-toggle'
24
+
25
+ function clearMenus() {
26
+ $(d).parent('li').removeClass('open')
27
+ }
28
+
29
+ $(function () {
30
+ $('html').bind("click", clearMenus)
31
+ $('body').dropdown( '[data-dropdown] a.menu, [data-dropdown] .dropdown-toggle' )
32
+ })
33
+
34
+ /* DROPDOWN PLUGIN DEFINITION
35
+ * ========================== */
36
+
37
+ $.fn.dropdown = function ( selector ) {
38
+ return this.each(function () {
39
+ $(this).delegate(selector || d, 'click', function (e) {
40
+ var li = $(this).parent('li')
41
+ , isActive = li.hasClass('open')
42
+
43
+ clearMenus()
44
+ !isActive && li.toggleClass('open')
45
+ return false
46
+ })
47
+ })
48
+ }
49
+
50
+ }( window.jQuery || window.ender );