├── .gitignore ├── CHANGELOG ├── CONTRIBUTORS ├── COPYING ├── Gemfile ├── Gemfile.lock ├── README.md ├── Rakefile ├── config.ru ├── config └── gdash.yaml-sample ├── gdash.gemspec ├── graph_templates ├── dashboards │ └── README.md └── node_templates │ └── collectd │ ├── cpu-average.graph │ ├── cpu-max.graph │ ├── dash.yaml │ ├── disk-IO.graph │ ├── disk-usage.graph │ ├── entropy.graph │ ├── fork_rate.graph │ ├── load.graph │ ├── memory-usage.graph │ ├── network.eth0.errors.graph │ ├── network.eth0.trafic.graph │ ├── network.eth1.errors.graph │ ├── network.eth1.trafic.graph │ ├── processes.graph │ ├── uptime.graph │ └── users.graph ├── lib ├── gdash.rb └── gdash │ ├── dashboard.rb │ ├── monkey_patches.rb │ └── sinatra_app.rb ├── public ├── img │ ├── glyphicons-halflings-white.png │ └── glyphicons-halflings.png └── js │ ├── bootstrap-alert.js │ ├── bootstrap-alerts.js │ ├── bootstrap-button.js │ ├── bootstrap-carousel.js │ ├── bootstrap-collapse.js │ ├── bootstrap-dropdown.js │ ├── bootstrap-modal.js │ ├── bootstrap-popover.js │ ├── bootstrap-scrollspy.js │ ├── bootstrap-tab.js │ ├── bootstrap-tabs.js │ ├── bootstrap-tooltip.js │ ├── bootstrap-transition.js │ ├── bootstrap-twipsy.js │ ├── bootstrap-typeahead.js │ ├── jquery-1.7.2.min.js │ ├── jquery-ui-1.10.0.custom.min.js │ ├── jquery-ui-sliderAccess.js │ ├── jquery-ui-timepicker-addon.css │ ├── jquery-ui-timepicker-addon.js │ ├── jquery.cookie.js │ ├── jquery.tablesorter.min.js │ └── selectdate.js ├── sample ├── README.md ├── email-full-screen.png ├── email.png └── email │ ├── cpu.graph │ ├── dash.yaml │ ├── io.graph │ ├── load.graph │ └── network.graph ├── tools └── dashboards-validation.rb └── views ├── README.md ├── _interval_filter.erb ├── bootstrap ├── accordion.less ├── alerts.less ├── bootstrap.less ├── breadcrumbs.less ├── button-groups.less ├── buttons.less ├── carousel.less ├── close.less ├── code.less ├── component-animations.less ├── dropdowns.less ├── forms.less ├── grid.less ├── hero-unit.less ├── labels-badges.less ├── layouts.less ├── mixins.less ├── modals.less ├── navbar.less ├── navs.less ├── pager.less ├── pagination.less ├── popovers.less ├── progress-bars.less ├── reset.less ├── responsive-1200px-min.less ├── responsive-767px-max.less ├── responsive-768px-979px.less ├── responsive-navbar.less ├── responsive-utilities.less ├── responsive.less ├── scaffolding.less ├── sprites.less ├── tables.less ├── tests │ ├── css-tests.css │ ├── css-tests.html │ ├── forms.html │ └── navbar.html ├── thumbnails.less ├── tooltip.less ├── type.less ├── utilities.less ├── variables.less └── wells.less ├── dashboard.erb ├── detailed_dashboard.erb ├── full_size_dashboard.erb ├── graph.erb ├── index.erb ├── layout.erb ├── print_dashboard.erb └── print_detailed_dashboard.erb /.gitignore: -------------------------------------------------------------------------------- 1 | config/gdash.yaml 2 | graph_templates 3 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 2012/02/11 - Support time interval views for any graph 2 | 2011/11/12 - Include the uncompiled less files and a js script to compile on demand 3 | making it easier to customize the look and feel of gdash - thanks Joe Miller 4 | 2011/10/18 - Multiple top level categories are supported 5 | 2011/10/16 - The settings that used to be in config.ru is now in a config file 6 | 2011/10/10 - Add a full screen dashboard mode 7 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | * Nathan Haneysmith - Added support for multiple top level categories 2 | * Elmer Rivera - Made the config.ru configurable 3 | * Jeremy Carroll - Bug fixes to the top level menu 4 | * @nstielau - Use absolute paths in views 5 | * Jason Dixon - Fixed spelling errors in code 6 | * Pierre-Yves Ritschard - Time interval graphs 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | gem 'sinatra' 3 | gem 'redcarpet' 4 | gem 'less' 5 | gem 'therubyracer' 6 | gem 'json' 7 | gem 'graphite_graph', "~>0.0.8" 8 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | commonjs (0.2.6) 5 | graphite_graph (0.0.8) 6 | json (1.7.7) 7 | less (2.2.1) 8 | commonjs (~> 0.2.6) 9 | libv8 (3.3.10.4) 10 | rack (1.4.5) 11 | rack-protection (1.2.0) 12 | rack 13 | redcarpet (2.1.1) 14 | sinatra (1.3.3) 15 | rack (~> 1.3, >= 1.3.6) 16 | rack-protection (~> 1.2) 17 | tilt (~> 1.3, >= 1.3.3) 18 | therubyracer (0.10.1) 19 | libv8 (~> 3.3.10) 20 | tilt (1.3.3) 21 | 22 | PLATFORMS 23 | ruby 24 | 25 | DEPENDENCIES 26 | graphite_graph (~> 0.0.8) 27 | json 28 | less 29 | redcarpet 30 | sinatra 31 | therubyracer 32 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/clean' 2 | require 'rubygems' 3 | require 'rubygems/package_task' 4 | 5 | spec = eval(File.read('gdash.gemspec')) 6 | 7 | Gem::PackageTask.new(spec) do |pkg| 8 | end 9 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | $: << File.join(File.dirname(__FILE__), "lib") 2 | 3 | require 'bundler/setup' 4 | 5 | require 'gdash' 6 | 7 | set :run, false 8 | 9 | config = YAML.load_file(File.expand_path("../config/gdash.yaml", __FILE__)) 10 | 11 | # If you want basic HTTP authentication 12 | # include :username and :password in gdash.yaml 13 | if config[:username] && config[:password] 14 | use Rack::Auth::Basic do |username, password| 15 | username == config[:username] && password == config[:password] 16 | end 17 | end 18 | 19 | run GDash::SinatraApp.new(config[:graphite], config[:templatedir], config[:options]) 20 | -------------------------------------------------------------------------------- /config/gdash.yaml-sample: -------------------------------------------------------------------------------- 1 | :graphite: http://graphite.example.net 2 | :templatedir: /path/to/my/graph/templates 3 | #:username: admin 4 | #:password: secret 5 | :options: 6 | :title: My Dashboard 7 | :prefix: "" 8 | :refresh_rate: 60 9 | :graph_columns: 2 10 | :graph_width: 500 11 | :graph_height: 250 12 | :interval_filters: 13 | - :label: Last Hour 14 | :from: -1hour 15 | :to: now 16 | - :label: Last Day 17 | :from: -1day 18 | - :label: Last Week 19 | :from: -1week 20 | - :label: Last Month 21 | :from: -1month 22 | - :label: Last Year 23 | :from: -1year 24 | :intervals: 25 | - [ "-1hour", "1 hour" ] 26 | - [ "-2hour", "2 hour" ] 27 | - [ "-1day", "1 day" ] 28 | - [ "-1month", "1 month" ] 29 | - [ "-1year", "1 year" ] 30 | -------------------------------------------------------------------------------- /gdash.gemspec: -------------------------------------------------------------------------------- 1 | spec = Gem::Specification.new do |s| 2 | s.name = 'gdash' 3 | s.version = "0.0.5" 4 | s.author = 'R.I.Pienaar' 5 | s.email = 'rip@devco.net' 6 | s.homepage = 'http://devco.net/' 7 | s.platform = Gem::Platform::RUBY 8 | s.summary = 'Graphite Dashboard' 9 | s.description = "A simple dashboard for creating and displaying Graphite graphs" 10 | # Add your other files here if you make them 11 | s.files = FileList["{README.md,COPYING,CONTRIBUTORS,bin,lib,public,views,sample,Gemfile,Gemfile.lock}/**/*"].to_a 12 | s.require_paths << 'lib' 13 | s.has_rdoc = false 14 | s.add_development_dependency('rake') 15 | s.add_development_dependency('rdoc') 16 | s.add_dependency 'graphite_graph' 17 | s.add_dependency 'sinatra' 18 | s.add_dependency 'redcarpet' 19 | end 20 | -------------------------------------------------------------------------------- /graph_templates/dashboards/README.md: -------------------------------------------------------------------------------- 1 | Placeholder for dashboards 2 | -------------------------------------------------------------------------------- /graph_templates/node_templates/collectd/cpu-average.graph: -------------------------------------------------------------------------------- 1 | title "CPU Usage: Average for all CPU" 2 | vtitle "percent" 3 | area :stacked 4 | description "The combined CPU usage for node" 5 | hide_legend false 6 | 7 | field :iowait, :color => "red", 8 | :alias => "IO Wait", 9 | :data => "averageSeries(node.cpu.*.wait)" 10 | 11 | field :system, :color => "orange", 12 | :alias => "System", 13 | :data => "averageSeries(node.cpu.*.system)" 14 | 15 | field :user, :color => "yellow", 16 | :alias => "User", 17 | :data => "averageSeries(node.cpu.*.user)" 18 | 19 | -------------------------------------------------------------------------------- /graph_templates/node_templates/collectd/cpu-max.graph: -------------------------------------------------------------------------------- 1 | title "CPU Usage: Value for the most used CPU" 2 | vtitle "percent" 3 | area :stacked 4 | description "The combined CPU usage for node" 5 | hide_legend false 6 | 7 | field :iowait, :color => "red", 8 | :alias => "IO Wait", 9 | :data => "maxSeries(node.cpu.*.wait)" 10 | 11 | field :system, :color => "orange", 12 | :alias => "System", 13 | :data => "maxSeries(node.cpu.*.system)" 14 | 15 | field :user, :color => "yellow", 16 | :alias => "User", 17 | :data => "maxSeries(node.cpu.*.user)" 18 | 19 | -------------------------------------------------------------------------------- /graph_templates/node_templates/collectd/dash.yaml: -------------------------------------------------------------------------------- 1 | :name: node example 2 | :description: The cpu, load, etc. usage for node 3 | 4 | -------------------------------------------------------------------------------- /graph_templates/node_templates/collectd/disk-IO.graph: -------------------------------------------------------------------------------- 1 | title "Disk Usage (in percent)" 2 | area :none 3 | description "Disk usage (in percent)" 4 | hide_legend false 5 | linewidth 3 6 | 7 | field :opt, :color => "red", 8 | :alias => "/opt", 9 | :data => "asPercent(node.df.opt.used,sumSeries(node.df.opt.used, node.df.opt.free))" 10 | 11 | field :var, :color => "orange", 12 | :alias => "/var", 13 | :data => "asPercent(node.df.var.used,sumSeries(node.df.var.used, node.df.var.free))" 14 | 15 | field :tmp, :color => "yellow", 16 | :alias => "/tmp", 17 | :data => "asPercent(node.df.tmp.used,sumSeries(node.df.tmp.used, node.df.tmp.free))" 18 | 19 | field :home, :color => "blue", 20 | :alias => "/home", 21 | :data => "asPercent(node.df.home.used,sumSeries(node.df.home.used, node.df.home.free))" 22 | 23 | field :root, :color => "green", 24 | :alias => "/root", 25 | :data => "asPercent(node.df.root.used,sumSeries(node.df.root.used, node.df.root.free))" 26 | 27 | -------------------------------------------------------------------------------- /graph_templates/node_templates/collectd/disk-usage.graph: -------------------------------------------------------------------------------- 1 | title "Disk Usage (in percent)" 2 | area :none 3 | description "Disk usage (in percent)" 4 | hide_legend false 5 | linewidth 3 6 | 7 | field :opt, :color => "red", 8 | :alias => "/opt", 9 | :data => "asPercent(node.df.opt.used,sumSeries(node.df.opt.used, l52eatsrv-cs1.df.opt.free))" 10 | 11 | field :var, :color => "orange", 12 | :alias => "/var", 13 | :data => "asPercent(node.df.var.used,sumSeries(node.df.var.used, l52eatsrv-cs1.df.var.free))" 14 | 15 | field :tmp, :color => "yellow", 16 | :alias => "/tmp", 17 | :data => "asPercent(node.df.tmp.used,sumSeries(node.df.tmp.used, l52eatsrv-cs1.df.tmp.free))" 18 | 19 | field :home, :color => "blue", 20 | :alias => "/home", 21 | :data => "asPercent(node.df.home.used,sumSeries(node.df.home.used, l52eatsrv-cs1.df.home.free))" 22 | 23 | field :root, :color => "green", 24 | :alias => "/root", 25 | :data => "asPercent(node.df.root.used,sumSeries(node.df.root.used, l52eatsrv-cs1.df.root.free))" 26 | 27 | -------------------------------------------------------------------------------- /graph_templates/node_templates/collectd/entropy.graph: -------------------------------------------------------------------------------- 1 | title "Entropy" 2 | area :all 3 | hide_legend false 4 | 5 | field :entropy, :alias => "Entropy", 6 | :data => "node.entropy.entropy.entropy" 7 | 8 | -------------------------------------------------------------------------------- /graph_templates/node_templates/collectd/fork_rate.graph: -------------------------------------------------------------------------------- 1 | title "Fork rate" 2 | area :all 3 | hide_legend false 4 | 5 | field :fork, :alias => "Fork rate", 6 | :data => "node.processes.fork_rate" 7 | 8 | -------------------------------------------------------------------------------- /graph_templates/node_templates/collectd/load.graph: -------------------------------------------------------------------------------- 1 | title "Load" 2 | area :none 3 | description "The combined CPU usage for node" 4 | hide_legend false 5 | 6 | field :short, :color => "red", 7 | :alias => "Short term (1 minute)", 8 | :data => "node.load.shortterm" 9 | 10 | field :mid, :color => "orange", 11 | :alias => "Mid term (5 minutes)", 12 | :data => "node.load.midterm" 13 | 14 | field :long, :color => "yellow", 15 | :alias => "Long term (15 minutes)", 16 | :data => "node.load.longterm" 17 | 18 | -------------------------------------------------------------------------------- /graph_templates/node_templates/collectd/memory-usage.graph: -------------------------------------------------------------------------------- 1 | title "RAM Usage (in percent)" 2 | area :stacked 3 | description "RAM usage (in percent)" 4 | hide_legend false 5 | 6 | field :use, :color => "red", 7 | :alias => "used by application", 8 | :data => "asPercent(diffSeries(node.memory.used,node.memory.cached,node.memory.buffered),sumSeries(node.memory.used, node.memory.free))" 9 | / 10 | 11 | field :buffer, :color => "orange", 12 | :alias => "buffer", 13 | :data => "asPercent(node.memory.buffered,sumSeries(node.memory.used, node.memory.free))" 14 | / 15 | 16 | field :cache, :color => "yellow", 17 | :alias => "cache", 18 | :data => "asPercent(node.memory.cached,sumSeries(node.memory.used, node.memory.free)" 19 | / 20 | 21 | field :free, :color => "green", 22 | :alias => "free", 23 | :data => "asPercent(node.memory.free,sumSeries(node.memory.used, node.memory.free))" 24 | / 25 | 26 | -------------------------------------------------------------------------------- /graph_templates/node_templates/collectd/network.eth0.errors.graph: -------------------------------------------------------------------------------- 1 | title "Errors on eth0" 2 | area :first 3 | hide_legend false 4 | 5 | field :receiving, :color => "green", 6 | :alias => "Receiving", 7 | :data => "node.interface.errors.eth0.rx" 8 | 9 | field :transmitting, :color => "blue", 10 | :alias => "Transmiting", 11 | :data => "node.interface.errors.eth0.tx" 12 | 13 | -------------------------------------------------------------------------------- /graph_templates/node_templates/collectd/network.eth0.trafic.graph: -------------------------------------------------------------------------------- 1 | title "Traffic on eth0" 2 | area :first 3 | hide_legend false 4 | 5 | field :receiving, :color => "green", 6 | :alias => "Receiving", 7 | :data => "node.interface.packets.eth0.rx" 8 | 9 | field :transmitting, :color => "blue", 10 | :alias => "Transmiting", 11 | :data => "node.interface.packets.eth0.tx" 12 | 13 | -------------------------------------------------------------------------------- /graph_templates/node_templates/collectd/network.eth1.errors.graph: -------------------------------------------------------------------------------- 1 | title "Errors on eth1" 2 | area :first 3 | hide_legend false 4 | 5 | field :receiving, :color => "green", 6 | :alias => "Receiving", 7 | :data => "node.interface.errors.eth1.rx" 8 | 9 | field :transmitting, :color => "blue", 10 | :alias => "Transmiting", 11 | :data => "node.interface.errors.eth1.tx" 12 | 13 | -------------------------------------------------------------------------------- /graph_templates/node_templates/collectd/network.eth1.trafic.graph: -------------------------------------------------------------------------------- 1 | title "Traffic on eth1" 2 | area :first 3 | hide_legend false 4 | 5 | field :receiving, :color => "green", 6 | :alias => "Receiving", 7 | :data => "node.interface.packets.eth1.rx" 8 | 9 | field :transmitting, :color => "blue", 10 | :alias => "Transmiting", 11 | :data => "node.interface.packets.eth1.tx" 12 | 13 | -------------------------------------------------------------------------------- /graph_templates/node_templates/collectd/processes.graph: -------------------------------------------------------------------------------- 1 | title "Processes" 2 | vtitle "Number of processes" 3 | area :stacked 4 | description "Number of processes for node" 5 | hide_legend false 6 | 7 | field :paging, :color => "purple", 8 | :alias => "paging", 9 | :data => "node.processes.ps_state.paging" 10 | 11 | field :zombie, :color => "white", 12 | :alias => "zombies", 13 | :data => "node.processes.ps_state.zombie" 14 | 15 | field :uninterruptible, :color => "orange", 16 | :alias => "uninteruptible", 17 | :data => "node.processes.ps_state.blocked" 18 | 19 | field :dead, :color => "red", 20 | :alias => "dead", 21 | :data => "node.processes.ps_state.stopped" 22 | 23 | field :sleeping, :color => "blue", 24 | :alias => "sleeping", 25 | :data => "node.processes.ps_state.sleeping" 26 | 27 | field :runnable, :color => "green", 28 | :alias => "runnable", 29 | :data => "node.processes.ps_state.running" 30 | 31 | -------------------------------------------------------------------------------- /graph_templates/node_templates/collectd/uptime.graph: -------------------------------------------------------------------------------- 1 | title "Uptime" 2 | area :all 3 | hide_legend false 4 | 5 | field :uptime, :alias => "Uptime", 6 | :data => "scale(node.uptime,0.000011574)" 7 | -------------------------------------------------------------------------------- /graph_templates/node_templates/collectd/users.graph: -------------------------------------------------------------------------------- 1 | title "User connected" 2 | area :all 3 | hide_legend false 4 | 5 | field :users, :alias => "Users", 6 | :data => "node.users.users.users" 7 | -------------------------------------------------------------------------------- /lib/gdash.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinatra' 3 | require 'yaml' 4 | require 'erb' 5 | require 'redcarpet' 6 | require 'less' 7 | 8 | class GDash 9 | require 'gdash/dashboard' 10 | require 'gdash/monkey_patches' 11 | require 'gdash/sinatra_app' 12 | require 'graphite_graph' 13 | 14 | attr_reader :graphite_base, :graphite_render, :graph_templates, :category, :dash_templates, :height, :width, :from, :until 15 | 16 | def initialize(graphite_base, render_url, graph_templates, category, options={}) 17 | @graphite_base = graphite_base 18 | @graphite_render = [@graphite_base, "/render/"].join 19 | @graph_templates = graph_templates 20 | @category = category 21 | @dash_templates = File.join(graph_templates, category) 22 | @height = options.delete(:height) 23 | @width = options.delete(:width) 24 | @from = options.delete(:from) 25 | @until = options.delete(:until) 26 | 27 | raise "Dashboard templates directory #{@dash_templates} does not exist" unless File.directory?(@dash_templates) 28 | end 29 | 30 | def dashboard(name, options={}) 31 | options[:width] ||= @width 32 | options[:height] ||= @height 33 | options[:from] ||= @from 34 | options[:until] ||= @until 35 | 36 | 37 | Dashboard.new(name, graph_templates, category, options, graphite_render) 38 | end 39 | 40 | def list 41 | dashboards.map {|dash| dash[:link]} 42 | end 43 | 44 | def dashboards 45 | dashboards = [] 46 | 47 | Dir.entries(dash_templates).each do |dash| 48 | begin 49 | yaml_file = File.join(dash_templates, dash, "dash.yaml") 50 | if File.exist?(yaml_file) 51 | dashboards << YAML.load_file(yaml_file).merge({:category => category, :link => dash}) 52 | end 53 | rescue Exception => e 54 | p e 55 | end 56 | end 57 | 58 | dashboards.sort_by{|d| d[:name].to_s} 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/gdash/dashboard.rb: -------------------------------------------------------------------------------- 1 | class GDash 2 | class Dashboard 3 | attr_accessor :properties 4 | 5 | def initialize(short_name, graph_templates, category, options={}, graphite_render="") 6 | 7 | @properties = {:graph_width => nil, 8 | :graph_height => nil, 9 | :graph_from => nil, 10 | :graph_until => nil} 11 | 12 | @properties[:short_name] = short_name 13 | @properties[:graph_templates] = graph_templates 14 | @properties[:category] = category 15 | @properties[:directory] = File.join(graph_templates, category, short_name) 16 | @properties[:graphite_render] = graphite_render 17 | 18 | raise "Cannot find dashboard directory #{directory}" unless File.directory?(directory) 19 | 20 | @properties[:yaml] = File.join(directory, "dash.yaml") 21 | 22 | raise "Cannot find YAML file #{yaml}" unless File.exists?(yaml) 23 | 24 | 25 | @properties.merge!(YAML.load_file(yaml)) 26 | 27 | if @properties[:include_properties] == nil || @properties[:include_properties].empty? 28 | property_includes = [] 29 | elsif @properties[:include_properties].is_a? Array 30 | property_includes = @properties[:include_properties] 31 | elsif @properties[:include_properties].is_a? String 32 | property_includes = [@properties[:include_properties]] 33 | else 34 | raise "Invalid value for include_properties in #{File.join(directory, 'dash.yaml')}" 35 | end 36 | 37 | property_includes << options[:include_properties] if options[:include_properties] 38 | 39 | for property_file in property_includes 40 | yaml_file = File.join(graph_templates, property_file) 41 | if File.exist?(yaml_file) 42 | @properties.rmerge!(YAML.load_file(yaml_file)) 43 | end 44 | end 45 | 46 | # Properties defined in dashboard config file are overridden when given on initialization 47 | @properties.rmerge!(options) 48 | @properties[:graph_width] = options.delete(:width) || graph_width 49 | @properties[:graph_height] = options.delete(:height) || graph_height 50 | @properties[:graph_from] = options.delete(:from) || graph_from 51 | @properties[:graph_until] = options.delete(:until) || graph_until 52 | 53 | if @properties[:include_graphs] == nil || @properties[:include_graphs].empty? 54 | graph_includes = [] 55 | elsif @properties[:include_graphs].is_a? Array 56 | graph_includes = @properties[:include_graphs] 57 | elsif @properties[:include_graphs].is_a? String 58 | graph_includes = [@properties[:include_graphs]] 59 | else 60 | raise "Invalid value for include in #{File.join(directory, 'dash.yaml')}" 61 | end 62 | 63 | @directories = graph_includes.map { |d| 64 | File.join(graph_templates, d) 65 | } 66 | @directories << directory 67 | 68 | #Graphite defined in gdash.yaml is overwritten if set in dash.yaml 69 | if !(@properties[:graphite] == nil || @properties[:graphite].empty?) 70 | @properties[:graphite_render] = @properties[:graphite]+"/render" 71 | end 72 | end 73 | 74 | def list_graphs 75 | graphs = {} 76 | @directories.each { |directory| 77 | current_graphs = Dir.entries(directory).select {|f| f.match(/\.graph$/)} 78 | current_graphs.each { |graph_filename| 79 | graph_name = File.basename(graph_filename, ".graph") 80 | graphs[graph_name] = File.join(directory, graph_filename) 81 | } 82 | } 83 | graphs 84 | end 85 | 86 | def graphs(options={}) 87 | options[:width] ||= graph_width 88 | options[:height] ||= graph_height 89 | options[:from] ||= graph_from 90 | options[:until] ||= graph_until 91 | 92 | overrides = options.reject { |k,v| v.nil? } 93 | overrides = overrides.merge!(@properties[:graph_properties]) if @properties[:graph_properties] 94 | overrides = overrides.merge!(options[:placeholders]) if options[:placeholders] 95 | 96 | graphs = list_graphs 97 | 98 | graphs.keys.sort.map do |graph_name| 99 | {:name => graph_name, 100 | :graphite => GraphiteGraph.new(graphs[graph_name], overrides)} 101 | end 102 | end 103 | 104 | def graph_by_name(name, options={}) 105 | options[:width] ||= graph_width 106 | options[:height] ||= graph_height 107 | options[:from] ||= graph_from 108 | options[:until] ||= graph_until 109 | 110 | overrides = options.reject { |k,v| v.nil? } 111 | overrides = overrides.merge!(@properties[:graph_properties]) if @properties[:graph_properties] 112 | overrides = overrides.merge!(options[:placeholders]) if options[:placeholders] 113 | 114 | graphs = list_graphs 115 | 116 | {:name => name, :graphite => GraphiteGraph.new(graphs[name], overrides)} 117 | end 118 | 119 | def method_missing(method, *args) 120 | if properties.include?(method) 121 | properties[method] 122 | else 123 | super 124 | end 125 | end 126 | end 127 | end 128 | -------------------------------------------------------------------------------- /lib/gdash/monkey_patches.rb: -------------------------------------------------------------------------------- 1 | class GraphiteGraph 2 | attr_accessor :properties, :file 3 | end 4 | 5 | class Hash 6 | def rmerge!(other_hash) 7 | merge!(other_hash) do |key, oldval, newval| 8 | oldval.class == self.class ? oldval.rmerge!(newval) : newval 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /public/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripienaar/gdash/0f334f006b39c6f72b5e496fd8ed0f203aa4495f/public/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /public/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripienaar/gdash/0f334f006b39c6f72b5e496fd8ed0f203aa4495f/public/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /public/js/bootstrap-alert.js: -------------------------------------------------------------------------------- 1 | /* ========================================================== 2 | * bootstrap-alert.js v2.0.4 3 | * http://twitter.github.com/bootstrap/javascript.html#alerts 4 | * ========================================================== 5 | * Copyright 2012 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 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* ALERT CLASS DEFINITION 27 | * ====================== */ 28 | 29 | var dismiss = '[data-dismiss="alert"]' 30 | , Alert = function (el) { 31 | $(el).on('click', dismiss, this.close) 32 | } 33 | 34 | Alert.prototype.close = function (e) { 35 | var $this = $(this) 36 | , selector = $this.attr('data-target') 37 | , $parent 38 | 39 | if (!selector) { 40 | selector = $this.attr('href') 41 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 42 | } 43 | 44 | $parent = $(selector) 45 | 46 | e && e.preventDefault() 47 | 48 | $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent()) 49 | 50 | $parent.trigger(e = $.Event('close')) 51 | 52 | if (e.isDefaultPrevented()) return 53 | 54 | $parent.removeClass('in') 55 | 56 | function removeElement() { 57 | $parent 58 | .trigger('closed') 59 | .remove() 60 | } 61 | 62 | $.support.transition && $parent.hasClass('fade') ? 63 | $parent.on($.support.transition.end, removeElement) : 64 | removeElement() 65 | } 66 | 67 | 68 | /* ALERT PLUGIN DEFINITION 69 | * ======================= */ 70 | 71 | $.fn.alert = function (option) { 72 | return this.each(function () { 73 | var $this = $(this) 74 | , data = $this.data('alert') 75 | if (!data) $this.data('alert', (data = new Alert(this))) 76 | if (typeof option == 'string') data[option].call($this) 77 | }) 78 | } 79 | 80 | $.fn.alert.Constructor = Alert 81 | 82 | 83 | /* ALERT DATA-API 84 | * ============== */ 85 | 86 | $(function () { 87 | $('body').on('click.alert.data-api', dismiss, Alert.prototype.close) 88 | }) 89 | 90 | }(window.jQuery); -------------------------------------------------------------------------------- /public/js/bootstrap-alerts.js: -------------------------------------------------------------------------------- 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 ); -------------------------------------------------------------------------------- /public/js/bootstrap-button.js: -------------------------------------------------------------------------------- 1 | /* ============================================================ 2 | * bootstrap-button.js v2.0.4 3 | * http://twitter.github.com/bootstrap/javascript.html#buttons 4 | * ============================================================ 5 | * Copyright 2012 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 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* BUTTON PUBLIC CLASS DEFINITION 27 | * ============================== */ 28 | 29 | var Button = function (element, options) { 30 | this.$element = $(element) 31 | this.options = $.extend({}, $.fn.button.defaults, options) 32 | } 33 | 34 | Button.prototype.setState = function (state) { 35 | var d = 'disabled' 36 | , $el = this.$element 37 | , data = $el.data() 38 | , val = $el.is('input') ? 'val' : 'html' 39 | 40 | state = state + 'Text' 41 | data.resetText || $el.data('resetText', $el[val]()) 42 | 43 | $el[val](data[state] || this.options[state]) 44 | 45 | // push to event loop to allow forms to submit 46 | setTimeout(function () { 47 | state == 'loadingText' ? 48 | $el.addClass(d).attr(d, d) : 49 | $el.removeClass(d).removeAttr(d) 50 | }, 0) 51 | } 52 | 53 | Button.prototype.toggle = function () { 54 | var $parent = this.$element.parent('[data-toggle="buttons-radio"]') 55 | 56 | $parent && $parent 57 | .find('.active') 58 | .removeClass('active') 59 | 60 | this.$element.toggleClass('active') 61 | } 62 | 63 | 64 | /* BUTTON PLUGIN DEFINITION 65 | * ======================== */ 66 | 67 | $.fn.button = function (option) { 68 | return this.each(function () { 69 | var $this = $(this) 70 | , data = $this.data('button') 71 | , options = typeof option == 'object' && option 72 | if (!data) $this.data('button', (data = new Button(this, options))) 73 | if (option == 'toggle') data.toggle() 74 | else if (option) data.setState(option) 75 | }) 76 | } 77 | 78 | $.fn.button.defaults = { 79 | loadingText: 'loading...' 80 | } 81 | 82 | $.fn.button.Constructor = Button 83 | 84 | 85 | /* BUTTON DATA-API 86 | * =============== */ 87 | 88 | $(function () { 89 | $('body').on('click.button.data-api', '[data-toggle^=button]', function ( e ) { 90 | var $btn = $(e.target) 91 | if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') 92 | $btn.button('toggle') 93 | }) 94 | }) 95 | 96 | }(window.jQuery); -------------------------------------------------------------------------------- /public/js/bootstrap-carousel.js: -------------------------------------------------------------------------------- 1 | /* ========================================================== 2 | * bootstrap-carousel.js v2.0.4 3 | * http://twitter.github.com/bootstrap/javascript.html#carousel 4 | * ========================================================== 5 | * Copyright 2012 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 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* CAROUSEL CLASS DEFINITION 27 | * ========================= */ 28 | 29 | var Carousel = function (element, options) { 30 | this.$element = $(element) 31 | this.options = options 32 | this.options.slide && this.slide(this.options.slide) 33 | this.options.pause == 'hover' && this.$element 34 | .on('mouseenter', $.proxy(this.pause, this)) 35 | .on('mouseleave', $.proxy(this.cycle, this)) 36 | } 37 | 38 | Carousel.prototype = { 39 | 40 | cycle: function (e) { 41 | if (!e) this.paused = false 42 | this.options.interval 43 | && !this.paused 44 | && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) 45 | return this 46 | } 47 | 48 | , to: function (pos) { 49 | var $active = this.$element.find('.active') 50 | , children = $active.parent().children() 51 | , activePos = children.index($active) 52 | , that = this 53 | 54 | if (pos > (children.length - 1) || pos < 0) return 55 | 56 | if (this.sliding) { 57 | return this.$element.one('slid', function () { 58 | that.to(pos) 59 | }) 60 | } 61 | 62 | if (activePos == pos) { 63 | return this.pause().cycle() 64 | } 65 | 66 | return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos])) 67 | } 68 | 69 | , pause: function (e) { 70 | if (!e) this.paused = true 71 | clearInterval(this.interval) 72 | this.interval = null 73 | return this 74 | } 75 | 76 | , next: function () { 77 | if (this.sliding) return 78 | return this.slide('next') 79 | } 80 | 81 | , prev: function () { 82 | if (this.sliding) return 83 | return this.slide('prev') 84 | } 85 | 86 | , slide: function (type, next) { 87 | var $active = this.$element.find('.active') 88 | , $next = next || $active[type]() 89 | , isCycling = this.interval 90 | , direction = type == 'next' ? 'left' : 'right' 91 | , fallback = type == 'next' ? 'first' : 'last' 92 | , that = this 93 | , e = $.Event('slide') 94 | 95 | this.sliding = true 96 | 97 | isCycling && this.pause() 98 | 99 | $next = $next.length ? $next : this.$element.find('.item')[fallback]() 100 | 101 | if ($next.hasClass('active')) return 102 | 103 | if ($.support.transition && this.$element.hasClass('slide')) { 104 | this.$element.trigger(e) 105 | if (e.isDefaultPrevented()) return 106 | $next.addClass(type) 107 | $next[0].offsetWidth // force reflow 108 | $active.addClass(direction) 109 | $next.addClass(direction) 110 | this.$element.one($.support.transition.end, function () { 111 | $next.removeClass([type, direction].join(' ')).addClass('active') 112 | $active.removeClass(['active', direction].join(' ')) 113 | that.sliding = false 114 | setTimeout(function () { that.$element.trigger('slid') }, 0) 115 | }) 116 | } else { 117 | this.$element.trigger(e) 118 | if (e.isDefaultPrevented()) return 119 | $active.removeClass('active') 120 | $next.addClass('active') 121 | this.sliding = false 122 | this.$element.trigger('slid') 123 | } 124 | 125 | isCycling && this.cycle() 126 | 127 | return this 128 | } 129 | 130 | } 131 | 132 | 133 | /* CAROUSEL PLUGIN DEFINITION 134 | * ========================== */ 135 | 136 | $.fn.carousel = function (option) { 137 | return this.each(function () { 138 | var $this = $(this) 139 | , data = $this.data('carousel') 140 | , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option) 141 | if (!data) $this.data('carousel', (data = new Carousel(this, options))) 142 | if (typeof option == 'number') data.to(option) 143 | else if (typeof option == 'string' || (option = options.slide)) data[option]() 144 | else if (options.interval) data.cycle() 145 | }) 146 | } 147 | 148 | $.fn.carousel.defaults = { 149 | interval: 5000 150 | , pause: 'hover' 151 | } 152 | 153 | $.fn.carousel.Constructor = Carousel 154 | 155 | 156 | /* CAROUSEL DATA-API 157 | * ================= */ 158 | 159 | $(function () { 160 | $('body').on('click.carousel.data-api', '[data-slide]', function ( e ) { 161 | var $this = $(this), href 162 | , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 163 | , options = !$target.data('modal') && $.extend({}, $target.data(), $this.data()) 164 | $target.carousel(options) 165 | e.preventDefault() 166 | }) 167 | }) 168 | 169 | }(window.jQuery); -------------------------------------------------------------------------------- /public/js/bootstrap-collapse.js: -------------------------------------------------------------------------------- 1 | /* ============================================================= 2 | * bootstrap-collapse.js v2.0.4 3 | * http://twitter.github.com/bootstrap/javascript.html#collapse 4 | * ============================================================= 5 | * Copyright 2012 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 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* COLLAPSE PUBLIC CLASS DEFINITION 27 | * ================================ */ 28 | 29 | var Collapse = function (element, options) { 30 | this.$element = $(element) 31 | this.options = $.extend({}, $.fn.collapse.defaults, options) 32 | 33 | if (this.options.parent) { 34 | this.$parent = $(this.options.parent) 35 | } 36 | 37 | this.options.toggle && this.toggle() 38 | } 39 | 40 | Collapse.prototype = { 41 | 42 | constructor: Collapse 43 | 44 | , dimension: function () { 45 | var hasWidth = this.$element.hasClass('width') 46 | return hasWidth ? 'width' : 'height' 47 | } 48 | 49 | , show: function () { 50 | var dimension 51 | , scroll 52 | , actives 53 | , hasData 54 | 55 | if (this.transitioning) return 56 | 57 | dimension = this.dimension() 58 | scroll = $.camelCase(['scroll', dimension].join('-')) 59 | actives = this.$parent && this.$parent.find('> .accordion-group > .in') 60 | 61 | if (actives && actives.length) { 62 | hasData = actives.data('collapse') 63 | if (hasData && hasData.transitioning) return 64 | actives.collapse('hide') 65 | hasData || actives.data('collapse', null) 66 | } 67 | 68 | this.$element[dimension](0) 69 | this.transition('addClass', $.Event('show'), 'shown') 70 | this.$element[dimension](this.$element[0][scroll]) 71 | } 72 | 73 | , hide: function () { 74 | var dimension 75 | if (this.transitioning) return 76 | dimension = this.dimension() 77 | this.reset(this.$element[dimension]()) 78 | this.transition('removeClass', $.Event('hide'), 'hidden') 79 | this.$element[dimension](0) 80 | } 81 | 82 | , reset: function (size) { 83 | var dimension = this.dimension() 84 | 85 | this.$element 86 | .removeClass('collapse') 87 | [dimension](size || 'auto') 88 | [0].offsetWidth 89 | 90 | this.$element[size !== null ? 'addClass' : 'removeClass']('collapse') 91 | 92 | return this 93 | } 94 | 95 | , transition: function (method, startEvent, completeEvent) { 96 | var that = this 97 | , complete = function () { 98 | if (startEvent.type == 'show') that.reset() 99 | that.transitioning = 0 100 | that.$element.trigger(completeEvent) 101 | } 102 | 103 | this.$element.trigger(startEvent) 104 | 105 | if (startEvent.isDefaultPrevented()) return 106 | 107 | this.transitioning = 1 108 | 109 | this.$element[method]('in') 110 | 111 | $.support.transition && this.$element.hasClass('collapse') ? 112 | this.$element.one($.support.transition.end, complete) : 113 | complete() 114 | } 115 | 116 | , toggle: function () { 117 | this[this.$element.hasClass('in') ? 'hide' : 'show']() 118 | } 119 | 120 | } 121 | 122 | 123 | /* COLLAPSIBLE PLUGIN DEFINITION 124 | * ============================== */ 125 | 126 | $.fn.collapse = function (option) { 127 | return this.each(function () { 128 | var $this = $(this) 129 | , data = $this.data('collapse') 130 | , options = typeof option == 'object' && option 131 | if (!data) $this.data('collapse', (data = new Collapse(this, options))) 132 | if (typeof option == 'string') data[option]() 133 | }) 134 | } 135 | 136 | $.fn.collapse.defaults = { 137 | toggle: true 138 | } 139 | 140 | $.fn.collapse.Constructor = Collapse 141 | 142 | 143 | /* COLLAPSIBLE DATA-API 144 | * ==================== */ 145 | 146 | $(function () { 147 | $('body').on('click.collapse.data-api', '[data-toggle=collapse]', function ( e ) { 148 | var $this = $(this), href 149 | , target = $this.attr('data-target') 150 | || e.preventDefault() 151 | || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 152 | , option = $(target).data('collapse') ? 'toggle' : $this.data() 153 | $(target).collapse(option) 154 | }) 155 | }) 156 | 157 | }(window.jQuery); -------------------------------------------------------------------------------- /public/js/bootstrap-dropdown.js: -------------------------------------------------------------------------------- 1 | /* ============================================================ 2 | * bootstrap-dropdown.js v2.0.4 3 | * http://twitter.github.com/bootstrap/javascript.html#dropdowns 4 | * ============================================================ 5 | * Copyright 2012 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 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* DROPDOWN CLASS DEFINITION 27 | * ========================= */ 28 | 29 | var toggle = '[data-toggle="dropdown"]' 30 | , Dropdown = function (element) { 31 | var $el = $(element).on('click.dropdown.data-api', this.toggle) 32 | $('html').on('click.dropdown.data-api', function () { 33 | $el.parent().removeClass('open') 34 | }) 35 | } 36 | 37 | Dropdown.prototype = { 38 | 39 | constructor: Dropdown 40 | 41 | , toggle: function (e) { 42 | var $this = $(this) 43 | , $parent 44 | , selector 45 | , isActive 46 | 47 | if ($this.is('.disabled, :disabled')) return 48 | 49 | selector = $this.attr('data-target') 50 | 51 | if (!selector) { 52 | selector = $this.attr('href') 53 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 54 | } 55 | 56 | $parent = $(selector) 57 | $parent.length || ($parent = $this.parent()) 58 | 59 | isActive = $parent.hasClass('open') 60 | 61 | clearMenus() 62 | 63 | if (!isActive) $parent.toggleClass('open') 64 | 65 | return false 66 | } 67 | 68 | } 69 | 70 | function clearMenus() { 71 | $(toggle).parent().removeClass('open') 72 | } 73 | 74 | 75 | /* DROPDOWN PLUGIN DEFINITION 76 | * ========================== */ 77 | 78 | $.fn.dropdown = function (option) { 79 | return this.each(function () { 80 | var $this = $(this) 81 | , data = $this.data('dropdown') 82 | if (!data) $this.data('dropdown', (data = new Dropdown(this))) 83 | if (typeof option == 'string') data[option].call($this) 84 | }) 85 | } 86 | 87 | $.fn.dropdown.Constructor = Dropdown 88 | 89 | 90 | /* APPLY TO STANDARD DROPDOWN ELEMENTS 91 | * =================================== */ 92 | 93 | $(function () { 94 | $('html').on('click.dropdown.data-api', clearMenus) 95 | $('body') 96 | .on('click.dropdown', '.dropdown form', function (e) { e.stopPropagation() }) 97 | .on('click.dropdown.data-api', toggle, Dropdown.prototype.toggle) 98 | }) 99 | 100 | }(window.jQuery); -------------------------------------------------------------------------------- /public/js/bootstrap-modal.js: -------------------------------------------------------------------------------- 1 | /* ========================================================= 2 | * bootstrap-modal.js v2.0.4 3 | * http://twitter.github.com/bootstrap/javascript.html#modals 4 | * ========================================================= 5 | * Copyright 2012 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 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* MODAL CLASS DEFINITION 27 | * ====================== */ 28 | 29 | var Modal = function (content, options) { 30 | this.options = options 31 | this.$element = $(content) 32 | .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this)) 33 | } 34 | 35 | Modal.prototype = { 36 | 37 | constructor: Modal 38 | 39 | , toggle: function () { 40 | return this[!this.isShown ? 'show' : 'hide']() 41 | } 42 | 43 | , show: function () { 44 | var that = this 45 | , e = $.Event('show') 46 | 47 | this.$element.trigger(e) 48 | 49 | if (this.isShown || e.isDefaultPrevented()) return 50 | 51 | $('body').addClass('modal-open') 52 | 53 | this.isShown = true 54 | 55 | escape.call(this) 56 | backdrop.call(this, function () { 57 | var transition = $.support.transition && that.$element.hasClass('fade') 58 | 59 | if (!that.$element.parent().length) { 60 | that.$element.appendTo(document.body) //don't move modals dom position 61 | } 62 | 63 | that.$element 64 | .show() 65 | 66 | if (transition) { 67 | that.$element[0].offsetWidth // force reflow 68 | } 69 | 70 | that.$element.addClass('in') 71 | 72 | transition ? 73 | that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) : 74 | that.$element.trigger('shown') 75 | 76 | }) 77 | } 78 | 79 | , hide: function (e) { 80 | e && e.preventDefault() 81 | 82 | var that = this 83 | 84 | e = $.Event('hide') 85 | 86 | this.$element.trigger(e) 87 | 88 | if (!this.isShown || e.isDefaultPrevented()) return 89 | 90 | this.isShown = false 91 | 92 | $('body').removeClass('modal-open') 93 | 94 | escape.call(this) 95 | 96 | this.$element.removeClass('in') 97 | 98 | $.support.transition && this.$element.hasClass('fade') ? 99 | hideWithTransition.call(this) : 100 | hideModal.call(this) 101 | } 102 | 103 | } 104 | 105 | 106 | /* MODAL PRIVATE METHODS 107 | * ===================== */ 108 | 109 | function hideWithTransition() { 110 | var that = this 111 | , timeout = setTimeout(function () { 112 | that.$element.off($.support.transition.end) 113 | hideModal.call(that) 114 | }, 500) 115 | 116 | this.$element.one($.support.transition.end, function () { 117 | clearTimeout(timeout) 118 | hideModal.call(that) 119 | }) 120 | } 121 | 122 | function hideModal(that) { 123 | this.$element 124 | .hide() 125 | .trigger('hidden') 126 | 127 | backdrop.call(this) 128 | } 129 | 130 | function backdrop(callback) { 131 | var that = this 132 | , animate = this.$element.hasClass('fade') ? 'fade' : '' 133 | 134 | if (this.isShown && this.options.backdrop) { 135 | var doAnimate = $.support.transition && animate 136 | 137 | this.$backdrop = $('