├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.markdown ├── Rakefile ├── SECURITY.md ├── bin └── riemann-dash ├── example ├── config.rb └── config.ru ├── lib └── riemann │ ├── dash.rb │ └── dash │ ├── app.rb │ ├── browser_config.rb │ ├── browser_config │ ├── file.rb │ └── s3.rb │ ├── config.rb │ ├── controller │ ├── css.rb │ └── index.rb │ ├── public │ ├── clock.js │ ├── dash.js │ ├── eventPane.js │ ├── favicon.ico │ ├── format.js │ ├── keys.js │ ├── persistence.js │ ├── profile.js │ ├── sounds │ │ ├── beep.wav │ │ └── geiger.wav │ ├── strings.js │ ├── subs.js │ ├── toolbar.js │ ├── util.js │ ├── vendor │ │ ├── PriorityQueue.js │ │ ├── backbone.js │ │ ├── flot │ │ │ ├── jquery.colorhelpers.js │ │ │ ├── jquery.colorhelpers.min.js │ │ │ ├── jquery.flot.canvas.js │ │ │ ├── jquery.flot.canvas.min.js │ │ │ ├── jquery.flot.categories.js │ │ │ ├── jquery.flot.categories.min.js │ │ │ ├── jquery.flot.crosshair.js │ │ │ ├── jquery.flot.crosshair.min.js │ │ │ ├── jquery.flot.errorbars.js │ │ │ ├── jquery.flot.errorbars.min.js │ │ │ ├── jquery.flot.fillbetween.js │ │ │ ├── jquery.flot.fillbetween.min.js │ │ │ ├── jquery.flot.image.js │ │ │ ├── jquery.flot.image.min.js │ │ │ ├── jquery.flot.js │ │ │ ├── jquery.flot.min.js │ │ │ ├── jquery.flot.navigate.js │ │ │ ├── jquery.flot.navigate.min.js │ │ │ ├── jquery.flot.pie.js │ │ │ ├── jquery.flot.pie.min.js │ │ │ ├── jquery.flot.resize.js │ │ │ ├── jquery.flot.resize.min.js │ │ │ ├── jquery.flot.selection.js │ │ │ ├── jquery.flot.selection.min.js │ │ │ ├── jquery.flot.stack.js │ │ │ ├── jquery.flot.stack.min.js │ │ │ ├── jquery.flot.symbol.js │ │ │ ├── jquery.flot.symbol.min.js │ │ │ ├── jquery.flot.threshold.js │ │ │ ├── jquery.flot.threshold.min.js │ │ │ ├── jquery.flot.time.js │ │ │ ├── jquery.flot.time.min.js │ │ │ ├── jquery.flot.tooltip.js │ │ │ └── jquery.flot.tooltip.min.js │ │ ├── gauge.min.js │ │ ├── jquery.gauge.js │ │ ├── jquery │ │ │ ├── jquery-1.9.1.min.js │ │ │ ├── jquery-1.9.1.min.map │ │ │ ├── jquery-ui-1.10.2.custom.min.js │ │ │ ├── jquery.quickfit.js │ │ │ └── jquery.simplemodal.1.4.4.min.js │ │ ├── lodash.min.js │ │ ├── smoothie.js │ │ └── toastr │ │ │ ├── toastr.css │ │ │ └── toastr.js │ ├── view.js │ ├── views │ │ ├── dial.js │ │ ├── flot.js │ │ ├── gauge.js │ │ ├── geiger.js │ │ ├── grid.js │ │ ├── help.js │ │ ├── iframe.js │ │ ├── list.js │ │ ├── log.js │ │ ├── timeseries.js │ │ └── title.js │ └── x.png │ ├── rack │ └── static.rb │ ├── version.rb │ └── views │ ├── css.scss │ ├── index.erb │ └── layout.erb ├── riemann-dash.gemspec ├── sh ├── c ├── env.rb └── test └── test ├── browser_config_test.rb ├── config_test.rb ├── fixtures ├── config │ ├── basic_config.rb │ └── ws_config.rb └── ws_config │ ├── dummy_config.json │ └── pretty_printed_config.json └── test_helper.rb /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | test: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | ruby-version: 18 | - "2.7" 19 | - "3.0" 20 | - "3.1" 21 | - "3.2" 22 | - "3.3" 23 | fail-fast: false 24 | steps: 25 | - uses: actions/checkout@v2 26 | - name: Setup Ruby 27 | uses: ruby/setup-ruby@v1 28 | with: 29 | ruby-version: ${{ matrix.ruby-version }} 30 | bundler-cache: true 31 | - name: Run the test suite 32 | run: bundle exec rake test 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pkg/ 2 | ._* 3 | .sass-cache/ 4 | *~ 5 | .DS_Store 6 | .*.swp 7 | *.log 8 | /test/tmp/ 9 | /config 10 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec 6 | 7 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | riemann-dash (0.2.14) 5 | erubi (~> 1.9.0) 6 | multi_json (= 1.3.6) 7 | sass (>= 3.1.14) 8 | sinatra (>= 1.4.5, < 2.3.0) 9 | webrick 10 | 11 | GEM 12 | remote: https://rubygems.org/ 13 | specs: 14 | activesupport (7.1.3.4) 15 | base64 16 | bigdecimal 17 | concurrent-ruby (~> 1.0, >= 1.0.2) 18 | connection_pool (>= 2.2.5) 19 | drb 20 | i18n (>= 1.6, < 2) 21 | minitest (>= 5.1) 22 | mutex_m 23 | tzinfo (~> 2.0) 24 | addressable (2.8.7) 25 | public_suffix (>= 2.0.2, < 7.0) 26 | async (2.12.1) 27 | console (~> 1.25, >= 1.25.2) 28 | fiber-annotation 29 | io-event (~> 1.6, >= 1.6.5) 30 | async-http (0.69.0) 31 | async (>= 2.10.2) 32 | async-pool (~> 0.7) 33 | io-endpoint (~> 0.11) 34 | io-stream (~> 0.4) 35 | protocol-http (~> 0.26) 36 | protocol-http1 (~> 0.19) 37 | protocol-http2 (~> 0.18) 38 | traces (>= 0.10) 39 | async-http-faraday (0.14.0) 40 | async-http (~> 0.42) 41 | faraday 42 | async-pool (0.7.0) 43 | async (>= 1.25) 44 | base64 (0.2.0) 45 | bigdecimal (3.1.8) 46 | concurrent-ruby (1.3.3) 47 | connection_pool (2.4.1) 48 | console (1.25.2) 49 | fiber-annotation 50 | fiber-local (~> 1.1) 51 | json 52 | drb (2.2.1) 53 | erubi (1.9.0) 54 | faraday (2.9.2) 55 | faraday-net_http (>= 2.0, < 3.2) 56 | faraday-http-cache (2.5.1) 57 | faraday (>= 0.8) 58 | faraday-net_http (3.1.0) 59 | net-http 60 | ffi (1.17.0) 61 | ffi (1.17.0-aarch64-linux-gnu) 62 | ffi (1.17.0-aarch64-linux-musl) 63 | ffi (1.17.0-arm-linux-gnu) 64 | ffi (1.17.0-arm-linux-musl) 65 | ffi (1.17.0-arm64-darwin) 66 | ffi (1.17.0-x86-linux-gnu) 67 | ffi (1.17.0-x86-linux-musl) 68 | ffi (1.17.0-x86_64-darwin) 69 | ffi (1.17.0-x86_64-linux-gnu) 70 | ffi (1.17.0-x86_64-linux-musl) 71 | fiber-annotation (0.2.0) 72 | fiber-local (1.1.0) 73 | fiber-storage 74 | fiber-storage (0.1.2) 75 | github_changelog_generator (1.16.4) 76 | activesupport 77 | async (>= 1.25.0) 78 | async-http-faraday 79 | faraday-http-cache 80 | multi_json 81 | octokit (~> 4.6) 82 | rainbow (>= 2.2.1) 83 | rake (>= 10.0) 84 | i18n (1.14.5) 85 | concurrent-ruby (~> 1.0) 86 | io-endpoint (0.11.0) 87 | io-event (1.6.5) 88 | io-stream (0.4.0) 89 | json (2.7.2) 90 | minitest (5.24.1) 91 | multi_json (1.3.6) 92 | mustermann (2.0.2) 93 | ruby2_keywords (~> 0.0.1) 94 | mutex_m (0.2.0) 95 | net-http (0.4.1) 96 | uri 97 | octokit (4.25.1) 98 | faraday (>= 1, < 3) 99 | sawyer (~> 0.9) 100 | protocol-hpack (1.4.3) 101 | protocol-http (0.26.6) 102 | protocol-http1 (0.19.1) 103 | protocol-http (~> 0.22) 104 | protocol-http2 (0.18.0) 105 | protocol-hpack (~> 1.4) 106 | protocol-http (~> 0.18) 107 | public_suffix (6.0.0) 108 | rack (2.2.9) 109 | rack-protection (2.2.4) 110 | rack 111 | rainbow (3.1.1) 112 | rake (13.2.1) 113 | rb-fsevent (0.11.2) 114 | rb-inotify (0.11.1) 115 | ffi (~> 1.0) 116 | ruby2_keywords (0.0.5) 117 | sass (3.7.4) 118 | sass-listen (~> 4.0.0) 119 | sass-listen (4.0.0) 120 | rb-fsevent (~> 0.9, >= 0.9.4) 121 | rb-inotify (~> 0.9, >= 0.9.7) 122 | sawyer (0.9.2) 123 | addressable (>= 2.3.5) 124 | faraday (>= 0.17.3, < 3) 125 | sinatra (2.2.4) 126 | mustermann (~> 2.0) 127 | rack (~> 2.2) 128 | rack-protection (= 2.2.4) 129 | tilt (~> 2.0) 130 | tilt (2.4.0) 131 | traces (0.11.1) 132 | tzinfo (2.0.6) 133 | concurrent-ruby (~> 1.0) 134 | uri (0.13.0) 135 | webrick (1.8.1) 136 | 137 | PLATFORMS 138 | aarch64-linux-gnu 139 | aarch64-linux-musl 140 | amd64-freebsd-14 141 | arm-linux-gnu 142 | arm-linux-musl 143 | arm64-darwin 144 | ruby 145 | x86-linux-gnu 146 | x86-linux-musl 147 | x86_64-darwin 148 | x86_64-linux-gnu 149 | x86_64-linux-musl 150 | 151 | DEPENDENCIES 152 | github_changelog_generator 153 | minitest 154 | rake 155 | riemann-dash! 156 | 157 | BUNDLED WITH 158 | 2.5.13 159 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2011 Kyle Kingsbury 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Riemann-Dash 2 | ============ 3 | 4 | A javascript, websockets-powered dashboard for Riemann. 5 | 6 | Get started 7 | ========== 8 | 9 | ``` bash 10 | $ gem install riemann-dash 11 | $ riemann-dash 12 | ``` 13 | 14 | Then open http://localhost:4567 in a browser. Riemann-dash will connect to the 15 | local host (relative to your browser) by default, and show you a small manual. 16 | Change the IP address in the top right field to point to your Riemann server's 17 | websocket port. 18 | 19 | Configuring 20 | =========== 21 | 22 | Riemann-dash takes an optional config file, which you can specify as the first 23 | command-line argument. If none is given, it looks for a file in the local 24 | directory: config.rb. That file can override any configuration options on the 25 | Dash class, and hence, all Sinatra configuration. You'll find a few usage 26 | examples in "example/config.rb". 27 | 28 | ``` ruby 29 | set :port, 6000 # HTTP server on port 6000 30 | set :bind, "1.2.3.4" # Bind to a different interface 31 | config[:ws_config] = 'custom/config.json' # Specify custom workspace config 32 | ``` 33 | 34 | You can also specify the default config file to be used by setting the 35 | `RIEMANN_DASH_CONFIG` environment variable. If set, this value will override 36 | the default config file location of `config.rb` when no config file is passed 37 | on the command line. 38 | 39 | Putting in production 40 | ===================== 41 | 42 | If you expect more than a couple of simultaneous users, you should consider 43 | running Riemann-dash in a proper application server. The easiest way is to 44 | install thin or puma. Riemann-dash will automatically use one of them if they 45 | are present. You'll need the C/C++ compiler, as well as the ruby and openssl 46 | libraries and headers installed. 47 | 48 | ``` bash 49 | $ gem install riemann-dash thin 50 | $ riemann-dash 51 | ``` 52 | 53 | Riemann-dash can also run in a web server supporting the Rack interface. An 54 | example rackup app is found in "example/config.ru". 55 | 56 | Development 57 | =========== 58 | 59 | $ git clone git://github.com/riemann/riemann-dash.git 60 | $ cd riemann-dash 61 | $ bundle 62 | 63 | Testing 64 | ======= 65 | # run tests 66 | $ sh/test 67 | 68 | Releasing 69 | ========== 70 | $ rake build 71 | $ rake release 72 | 73 | REPL 74 | ==== 75 | $ sh/c 76 | > irb :001 > Riemann::Dash::VERSION 77 | > => "0.2.2" 78 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require 'github_changelog_generator/task' 3 | 4 | GitHubChangelogGenerator::RakeTask.new :changelog do |config| 5 | config.since_tag = '0.1.14' 6 | config.future_release = '0.2.0' 7 | end 8 | 9 | task :default => :test 10 | require 'rake/testtask' 11 | Rake::TestTask.new do |t| 12 | t.libs.push "lib" 13 | t.test_files = FileList['test/**/*_test.rb'] 14 | t.verbose = false 15 | end 16 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Riemann Security and Disclosure Information 2 | This page describes Riemann security and disclosure information. 3 | 4 | ## Supported Versions 5 | 6 | The currently supported version of Riemann for security-patching purposes is always the latest version. 7 | 8 | ## Security Announcements 9 | 10 | Will be made on the [Riemann mailing list](https://groups.google.com/g/riemann-users?pli=1). 11 | 12 | ## Report a Vulnerability 13 | 14 | We're extremely grateful for security researchers and users that report vulnerabilities to Riemann. All reports are thoroughly investigated by the maintainers. 15 | 16 | To make a report, you should email the private security@riemann.io list with the details. 17 | 18 | ## When Should I Report a Vulnerability? 19 | 20 | * You think you discovered a potential security vulnerability in Riemann. 21 | * You are unsure how a vulnerability affects Riemann. 22 | * You think you discovered a vulnerability in another project that Riemann depends on 23 | 24 | For projects with their own vulnerability reporting and disclosure process, please report it directly there. 25 | 26 | ## When Should I NOT Report a Vulnerability? 27 | 28 | * You need help tuning Riemann components for security 29 | * You need help applying security related updates 30 | * Your issue is not security related 31 | 32 | ## Security Vulnerability Response 33 | 34 | Each report is acknowledged and analyzed within 5 working days. 35 | 36 | Any vulnerability information shared stays within Riemann project and will not be disseminated to other projects unless it is necessary to get the issue fixed. 37 | 38 | As the security issue moves from triage, to identified fix, to release planning we will keep the reporter updated. 39 | 40 | ## Public Disclosure Timing 41 | 42 | A public disclosure date is negotiated by the Riemann maintainers nd the bug submitter. We prefer to fully disclose the bug as soon as possible once a user mitigation is available. It is reasonable to delay disclosure when the bug or the fix is not yet fully understood, the solution is not well-tested, or for vendor coordination. The timeframe for disclosure is from immediate (especially if it's already publicly known) to a few weeks. For a vulnerability with a straightforward mitigation, we expect report date to disclosure date to be on the order of 7 days. The Riemann maintainers hold the final say when setting a disclosure date. 43 | -------------------------------------------------------------------------------- /bin/riemann-dash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))) 4 | require 'riemann/dash' 5 | 6 | Riemann::Dash::App.load ARGV.first 7 | Riemann::Dash::App.run! 8 | -------------------------------------------------------------------------------- /example/config.rb: -------------------------------------------------------------------------------- 1 | # Example configuration file for riemann-dash. 2 | 3 | # Serve HTTP traffic on this port 4 | set :port, 4567 5 | 6 | # Answer queries sent to this IP address 7 | set :bind, "0.0.0.0" 8 | 9 | riemann_base = '.' 10 | riemann_src = "#{riemann_base}/lib/riemann/dash" 11 | 12 | # Add custom controllers in controller/ 13 | config.store[:controllers] = ["#{riemann_src}/controller"] 14 | 15 | # Use the local view directory instead of the default 16 | config.store[:views] = "#{riemann_src}/views" 17 | 18 | # Specify a custom path to your workspace config.json 19 | config.store[:ws_config] = "#{riemann_base}/config/config.json" 20 | 21 | # Serve static files from this directory 22 | config.store[:public] = "#{riemann_src}/public" 23 | 24 | # Save workspace configuration to Amazon S3 (you'll need to have the "fog" 25 | # gem installed) 26 | # config.store[:ws_config] = 's3://my-bucket/config.json' 27 | # config.store[:s3_config] = {:aws_access_key_id => "123ABC", :aws_secret_access_key => "789XYZ"} 28 | -------------------------------------------------------------------------------- /example/config.ru: -------------------------------------------------------------------------------- 1 | # Example Rack handler. 2 | 3 | # To run riemann-dash in a Rack-compatible web server such as nginx/apache 4 | # with phusion passenger, you can use this rackup app. Refer to your Ruby 5 | # application server's documentation to find out how to configure it to load 6 | # this file. 7 | 8 | # Uncomment the following line if you installed riemann-dash outside of ruby's 9 | # load path (ruby -e 'puts $LOAD_PATH' to check). 10 | #$LOAD_PATH.unshift('/path/to/riemann-dash/lib') 11 | 12 | require 'riemann/dash' 13 | 14 | Riemann::Dash::App.load '/path/to/config.rb' 15 | run Riemann::Dash::App 16 | 17 | -------------------------------------------------------------------------------- /lib/riemann/dash.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinatra/base' 3 | require 'riemann/dash/version' 4 | require 'riemann/dash/browser_config' 5 | require 'riemann/dash/config' 6 | require 'riemann/dash/app' 7 | -------------------------------------------------------------------------------- /lib/riemann/dash/app.rb: -------------------------------------------------------------------------------- 1 | module Riemann 2 | module Dash 3 | class App < Sinatra::Base 4 | # A little dashboard sinatra application. 5 | 6 | require 'yaml' 7 | require 'find' 8 | require 'tilt/erubi' 9 | require 'erubi' 10 | require 'sass' 11 | 12 | def self.config 13 | Riemann::Dash::Config.instance 14 | end 15 | 16 | def config 17 | self.class.config 18 | end 19 | 20 | def self.load(filename) 21 | filename ||= ENV['RIEMANN_DASH_CONFIG'] || 'config.rb' 22 | unless config.load_config(filename) 23 | # Configuration failed; load a default view. 24 | puts "No configuration loaded; using defaults." 25 | end 26 | 27 | config.load_controllers 28 | config.setup_views 29 | config.setup_public_dir 30 | config.setup_storage_backend 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/riemann/dash/browser_config.rb: -------------------------------------------------------------------------------- 1 | module Riemann::Dash::BrowserConfig 2 | 3 | def self.backend 4 | @backend 5 | end 6 | 7 | def self.backend=(backend) 8 | @backend = backend 9 | end 10 | 11 | # Merge two configs together 12 | def self.merge_configs(a, b) 13 | a.merge 'server' => (a['server'] or b['server']), 14 | 'server_type' => (a['server_type'] or b['server_type']), 15 | 'workspaces' => merge_workspaces(a['workspaces'], b['workspaces']) 16 | end 17 | 18 | def self.read 19 | backend.read 20 | end 21 | 22 | def self.update(update) 23 | backend.update(update) 24 | end 25 | 26 | # TODO: this is gonna take significant restructuring of the dashboard itself, 27 | # but we should move to http://arxiv.org/abs/1201.1784 or equivalent CRDTs. 28 | 29 | # Given a function to extract a key from an element, and a list of elements, 30 | # returns a map of keys to elements. Keys are assumed unique. 31 | def self.index_by(keyfn, list) 32 | list.reduce({}) do |index, element| 33 | index[keyfn.call(element)] = element 34 | index 35 | end 36 | end 37 | 38 | # Merges two lists, given a key function which determines equivalent 39 | # elements, and a merge function to combine equivalent elements. 40 | def self.merge_lists(keyfn, mergefn, as, bs) 41 | asi = index_by keyfn, as 42 | bsi = index_by keyfn, bs 43 | ids = (as + bs).map(&keyfn).uniq.map do |key| 44 | mergefn.call asi[key], bsi[key] 45 | end 46 | end 47 | 48 | # Merge two workspaces together 49 | def self.merge_workspace(a, b) 50 | # TODO: workspace versions 51 | return a unless b 52 | return b unless a 53 | if (a['view']['version'] || 0) < (b['view']['version'] || 0) 54 | b 55 | else 56 | a 57 | end 58 | end 59 | 60 | # Merge a list of workspaces together 61 | def self.merge_workspaces(as, bs) 62 | return as unless bs 63 | return bs unless as 64 | 65 | merge_lists(lambda { |x| x['name'] }, 66 | method(:merge_workspace), 67 | as, 68 | bs) 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/riemann/dash/browser_config/file.rb: -------------------------------------------------------------------------------- 1 | class Riemann::Dash::BrowserConfig::File 2 | require 'multi_json' 3 | require 'fileutils' 4 | 5 | def initialize(path) 6 | @path = path 7 | end 8 | 9 | def read 10 | if ::File.exist? @path 11 | ::File.open(@path, 'r') do |f| 12 | f.flock ::File::LOCK_SH 13 | f.read 14 | end 15 | else 16 | MultiJson.encode({}) 17 | end 18 | end 19 | 20 | def update(update) 21 | update = MultiJson.decode update 22 | 23 | # Read old config 24 | old = MultiJson.decode read 25 | 26 | new = Riemann::Dash::BrowserConfig.merge_configs update, old 27 | 28 | # Save new config 29 | FileUtils.mkdir_p ::File.dirname(@path) 30 | begin 31 | ::File.open(@path, ::File::RDWR|::File::CREAT, 0644) do |f| 32 | f.flock ::File::LOCK_EX 33 | f.write(MultiJson.encode(new, :pretty => true)) 34 | f.flush 35 | f.truncate f.pos 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/riemann/dash/browser_config/s3.rb: -------------------------------------------------------------------------------- 1 | class Riemann::Dash::BrowserConfig::S3 2 | require 'multi_json' 3 | require 'fog' 4 | 5 | def initialize(bucket, path, config = {}) 6 | @bucket = bucket 7 | @path = path 8 | @config = config 9 | 10 | @storage = Fog::Storage::AWS.new(config) 11 | end 12 | 13 | def read 14 | begin 15 | @storage.get_object(@bucket, @path).body 16 | rescue Excon::Errors::NotFound 17 | MultiJson.encode({}) 18 | end 19 | end 20 | 21 | def update(update) 22 | update = MultiJson.decode update 23 | 24 | # Read old config 25 | old = MultiJson.decode read 26 | 27 | new = Riemann::Dash::BrowserConfig.merge_configs update, old 28 | @storage.put_object @bucket, @path, MultiJson.encode(new, :pretty => true) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/riemann/dash/config.rb: -------------------------------------------------------------------------------- 1 | class Riemann::Dash::Config 2 | require 'uri' 3 | 4 | attr_accessor :config_path 5 | attr_accessor :store 6 | 7 | def initialize 8 | self.store = {} 9 | setup_default_values 10 | end 11 | 12 | def self.instance 13 | @instance ||= Riemann::Dash::Config.new 14 | end 15 | 16 | def self.reset! 17 | @instance = nil 18 | end 19 | 20 | def setup_default_values 21 | store.merge!({ 22 | :controllers => [File.join(File.dirname(__FILE__), 'controller')], 23 | :views => File.join(File.dirname(__FILE__), 'views'), 24 | :ws_config => File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'config', 'config.json')), 25 | :public => File.join(File.dirname(__FILE__), 'public') 26 | }) 27 | end 28 | 29 | def ws_config_file 30 | store[:ws_config] 31 | end 32 | 33 | # backwards compatible forwarder to store-ivar 34 | def [](k) 35 | store[k] 36 | end 37 | 38 | def []=(k,v) 39 | store[k] = v 40 | end 41 | 42 | 43 | # Executes the configuration file. 44 | def load_config(path) 45 | self.config_path = path 46 | begin 47 | Riemann::Dash::App.instance_eval File.read(config_path) 48 | true 49 | rescue Errno::ENOENT 50 | false 51 | end 52 | end 53 | 54 | def load_controllers 55 | store[:controllers].each { |d| load_controllers_from(d) } 56 | end 57 | 58 | def setup_views 59 | Riemann::Dash::App.set :views, File.expand_path(store[:views]) 60 | end 61 | 62 | def setup_public_dir 63 | require 'riemann/dash/rack/static' 64 | Riemann::Dash::App.use Riemann::Dash::Static, :root => store[:public] 65 | end 66 | 67 | def setup_storage_backend 68 | uri = URI.parse(ws_config_file) 69 | backend = case uri.scheme 70 | when "s3" 71 | begin 72 | require 'riemann/dash/browser_config/s3' 73 | Riemann::Dash::BrowserConfig::S3.new(uri.host, uri.path.sub(/^\//, ''), store[:s3_config]) 74 | rescue LoadError 75 | raise Exception.new 'Fog library required to save to S3. Run: "gem install fog"' 76 | end 77 | when nil, "file" 78 | require 'riemann/dash/browser_config/file' 79 | Riemann::Dash::BrowserConfig::File.new(uri.path) 80 | else 81 | raise Exception.new "Unknown backend for #{ws_config_file}" 82 | end 83 | Riemann::Dash::BrowserConfig.backend = backend 84 | end 85 | 86 | # Load controllers. 87 | def load_controllers_from(dir) 88 | sorted_controller_list(dir).each do |r| 89 | require r 90 | end 91 | end 92 | 93 | # Controllers can be regular old one-file-per-class, but 94 | # if you prefer a little more modularity, this method will allow you to 95 | # define all controller methods in their own files. For example, get 96 | # "/posts/*/edit" can live in controller/posts/_/edit.rb. The sorting 97 | # system provided here requires files in the correct order to handle 98 | # wildcards appropriately. 99 | def sorted_controller_list(dir) 100 | rbs = [] 101 | Find.find( 102 | File.expand_path(dir) 103 | ) do |path| 104 | rbs << path if path =~ /\.rb$/ 105 | end 106 | 107 | # Sort paths with _ last, becase those are wildcards. 108 | rbs.sort! do |a, b| 109 | as = a.split File::SEPARATOR 110 | bs = b.split File::SEPARATOR 111 | 112 | # Compare common subpaths 113 | l = [as.size, bs.size].min 114 | catch :x do 115 | (0...l).each do |i| 116 | a, b = as[i], bs[i] 117 | if a[/^_/] and not b[/^_/] 118 | throw :x, 1 119 | elsif b[/^_/] and not a[/^_/] 120 | throw :x, -1 121 | elsif ord = (a <=> b) and ord != 0 122 | throw :x, ord 123 | end 124 | end 125 | 126 | # All subpaths are identical; sort longest first 127 | if as.size > bs.size 128 | throw :x, -1 129 | elsif as.size < bs.size 130 | throw :x, -1 131 | else 132 | throw :x, 0 133 | end 134 | end 135 | end 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /lib/riemann/dash/controller/css.rb: -------------------------------------------------------------------------------- 1 | class Riemann::Dash::App 2 | get '/css' do 3 | scss :css, :layout => false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/riemann/dash/controller/index.rb: -------------------------------------------------------------------------------- 1 | class Riemann::Dash::App 2 | get '/' do 3 | erb :index, :layout => false 4 | end 5 | 6 | get '/config', :provides => 'json' do 7 | content_type "application/json" 8 | Riemann::Dash::BrowserConfig.read 9 | end 10 | 11 | post '/config' do 12 | # Read update 13 | request.body.rewind 14 | Riemann::Dash::BrowserConfig.update request.body.read 15 | 16 | # Return current config 17 | content_type "application/json" 18 | Riemann::Dash::BrowserConfig.read 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/clock.js: -------------------------------------------------------------------------------- 1 | // Global clock 2 | var clock = (function() { 3 | // We keep track of two clocks here. The stream clock is the most recent time 4 | // from the streaming system. The official clock is what time we *think* it 5 | // is. 6 | var stream_clock = 0; 7 | var clock = new Date(); 8 | 9 | // If we *stop* receiving updates from the stream clock, we still want time 10 | // to advance--but gradually, we want to reconverge on the local system 11 | // clock. The last stream clock update is used to track how long it's been 12 | // since we last advanced the stream clock. 13 | var last_stream_clock_update = new Date(0); 14 | 15 | // Over this many milliseconds, we want to converge on the current time. 16 | var convergence_time = 60000; 17 | 18 | // Callback_i is used to uniquely identify each callback so we can 19 | // unsubscribe to clock updates. 20 | var callback_i = 0; 21 | 22 | // A map of callback indices to callback functions. 23 | var callbacks = {}; 24 | 25 | // Advance the clock to a new time. 26 | var advance = function(t) { 27 | if (stream_clock < t) { 28 | // This is the highest value ever received for the stream clock. 29 | stream_clock = t; 30 | // console.log("New stream clock:", stream_clock); 31 | last_stream_clock_update = new Date(); 32 | } 33 | } 34 | 35 | // Create a new subscription. Returns a subscription key used to unsubscribe. 36 | var subscribe = function(f) { 37 | callback_i = callback_i + 1; 38 | callbacks[callback_i] = f; 39 | return callback_i; 40 | } 41 | 42 | // Unsubscribes a given subscription, by ID. 43 | var unsubscribe = function(i) { 44 | delete callbacks[i]; 45 | } 46 | 47 | // Automatically advance clock. 48 | setInterval(function() { 49 | // What time is the local system? 50 | var now = new Date(); 51 | 52 | // What time does the stream clock think it is? 53 | var stream_now = stream_clock + (now - last_stream_clock_update); 54 | 55 | // What fraction of the convergence window has elapsed? 56 | var convergence_fraction = 57 | Math.min(1, (now - last_stream_clock_update) / convergence_time); 58 | 59 | // console.log("Convergence:", convergence_fraction); 60 | // console.log("Clock offset:", stream_now - now); 61 | 62 | // The effective clock is the current stream time, plus the delta from the 63 | // stream time to the local time, multiplied by the convergence fraction. 64 | clock = stream_now + ((now - stream_now) * convergence_fraction); 65 | 66 | $.each(callbacks, function(k, f) { 67 | f(clock); 68 | }); 69 | 70 | t2 = new Date(); 71 | subs.load1(now, t2); 72 | subs.load5(now, t2); 73 | }, 1000); 74 | 75 | return { 76 | 'clock': clock, 77 | 'stream_clock': stream_clock, 78 | 'advance': advance, 79 | 'subscribe': subscribe, 80 | 'unsubscribe': unsubscribe 81 | } 82 | })(); 83 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/eventPane.js: -------------------------------------------------------------------------------- 1 | // Pane at the bottom of the dashboard for displaying events in context. 2 | 3 | var eventPane = (function() { 4 | var el = $('#event-pane'); 5 | var fixedFields = ['host', 'service', 'time', 'state', 'metric', 'ttl', 'description', 'tags']; 6 | var fixedTemplate = 7 | _.template( 8 | '
' + 9 | '{{-host}}' + 10 | '{{-service}}' + 11 | '{{-state}}' + 12 | '{{-metric}}' + 13 | '' + 14 | '{{-ttl}}' + 15 | '{{-tags}}' + 16 | '
' + 17 | '
{{-description}}
'); 18 | 19 | var rowTemplate = 20 | _.template('{{-field}}{{-value}}'); 21 | 22 | // Hide the pane 23 | var hide = function() { 24 | if (el.hasClass("active")) { 25 | el.empty(); 26 | el.removeClass("active"); 27 | } 28 | }; 29 | 30 | // Show an event in the pane 31 | var show = function(event) { 32 | hide(); 33 | 34 | if (! el.hasClass("active")) { 35 | el.addClass("active"); 36 | } 37 | 38 | el.append( 39 | fixedTemplate( 40 | _.defaults( 41 | util.merge(event, {time: new Date(event.time)}), 42 | {host: "nil", 43 | service: "nil", 44 | state: "nil", 45 | metric: "nil", 46 | ttl: "nil", 47 | tags: "nil", 48 | description: "nil"}))); 49 | 50 | var table = '' 51 | 52 | // Remaining fields 53 | _.each(event, function(value, field) { 54 | if (! _.contains(fixedFields, field)) { 55 | table += rowTemplate({field: field, value: value}); 56 | } 57 | }); 58 | 59 | table += '
'; 60 | 61 | el.append(table); 62 | }; 63 | 64 | // Hide on escape. 65 | keys.bind(27, hide); 66 | 67 | return {show: show, 68 | hide: hide}; 69 | })(); 70 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/riemann/riemann-dash/2d479c6655dbe8edb97c7cb2fa5820ae681ae881/lib/riemann/dash/public/favicon.ico -------------------------------------------------------------------------------- /lib/riemann/dash/public/format.js: -------------------------------------------------------------------------------- 1 | var format = (function() { 2 | 3 | var formatFloat = function(number, precision, commas) { 4 | if (number == null) { 5 | return null; 6 | } 7 | precision = precision || 2; 8 | var base = Math.pow(10, precision); 9 | var val; 10 | if (Math.round(number) == number) 11 | val = number; 12 | else 13 | val = number.toFixed(precision); 14 | 15 | if(!commas) { 16 | return val; 17 | } 18 | 19 | var parts = (val + '').split("."); 20 | parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); 21 | return parts.join("."); 22 | } 23 | 24 | var metricTemplate = _.template('
{{metric}}
'); 25 | var metric = function(e, max) { 26 | var max = (max || 1); 27 | var data = { 28 | 'state': e.state, 29 | 'percent': (e.metric / max * 100), 30 | 'metric': formatFloat(e.metric) 31 | } 32 | return metricTemplate(data); 33 | }; 34 | 35 | return { 36 | 'float': formatFloat, 37 | 'metric': metric 38 | } 39 | })(); 40 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/keys.js: -------------------------------------------------------------------------------- 1 | var keys = (function() { 2 | var active = true; 3 | 4 | var bindings = {}; 5 | 6 | // Disable bindings. 7 | var disable = function() { 8 | active = false; 9 | } 10 | 11 | // Enable bindings. 12 | var enable = function() { 13 | active = true; 14 | } 15 | 16 | // Bind a key. 17 | var bind = function(code, fn) { 18 | if (bindings[code] === undefined) { 19 | bindings[code] = []; 20 | } 21 | bindings[code].push(fn); 22 | } 23 | 24 | // React to key presses. 25 | $(document).bind('keydown', function(ev) { 26 | if (active === false) { 27 | return; 28 | } 29 | 30 | var fns = bindings[ev.which]; 31 | if (fns !== undefined) { 32 | fns.forEach(function(fn) { fn(ev); }); 33 | // ev.preventDefault(); 34 | } 35 | }); 36 | 37 | return { 38 | active: function() { return active; }, 39 | bindings: function() { return bindings; }, 40 | bind: bind, 41 | enable: enable, 42 | disable: disable 43 | } 44 | })(); 45 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/persistence.js: -------------------------------------------------------------------------------- 1 | // Provides persistent storage for dashboard configuration. 2 | var persistence = (function() { 3 | // Saves configuration to persistent store. Calls success() or error() when 4 | // complete. 5 | var save = function(config, success, error) { 6 | jQuery.ajax('config', { 7 | type: 'POST', 8 | success: success, 9 | error: error, 10 | contentType: 'application/json', 11 | data: JSON.stringify(config), 12 | dataType: 'json' 13 | }); 14 | }; 15 | 16 | // Returns configuration from persistent store. 17 | var load = function(success, error) { 18 | jQuery.ajax('config', { 19 | type: 'GET', 20 | success: success, 21 | error: error, 22 | dataType: 'json' 23 | }); 24 | }; 25 | 26 | return { 27 | save: save, 28 | load: load 29 | } 30 | })(); 31 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/profile.js: -------------------------------------------------------------------------------- 1 | var profile = (function() { 2 | // Instrumentation for how much time we spend doing things. With 0 args, 3 | // returns current busy fraction. With a start and stop time in milliseconds, 4 | // updates the current busy fraction. 5 | 6 | // Returns a load meter with a sampling period in ms. 7 | var load = function(period) { 8 | // Start of the sample interval 9 | var interval = 0; 10 | 11 | // Fraction of time busy 12 | var load = 0; 13 | var acc = 0; 14 | 15 | return function(t1, t2) { 16 | if (t1 === undefined) { 17 | return load; 18 | } 19 | 20 | if (interval < t2) { 21 | interval = (Math.floor(t2 / period) * period) + period; 22 | load = acc / period; 23 | acc = 0; 24 | } 25 | 26 | acc += (t2 - t1); 27 | } 28 | }; 29 | 30 | return { 31 | load: load 32 | }; 33 | })(); 34 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/sounds/beep.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/riemann/riemann-dash/2d479c6655dbe8edb97c7cb2fa5820ae681ae881/lib/riemann/dash/public/sounds/beep.wav -------------------------------------------------------------------------------- /lib/riemann/dash/public/sounds/geiger.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/riemann/riemann-dash/2d479c6655dbe8edb97c7cb2fa5820ae681ae881/lib/riemann/dash/public/sounds/geiger.wav -------------------------------------------------------------------------------- /lib/riemann/dash/public/strings.js: -------------------------------------------------------------------------------- 1 | var strings = (function() { 2 | var longestCommonPrefix = function(strings) { 3 | strings = _.filter(strings, _.isString); 4 | 5 | if (strings.length === 0) { 6 | return ''; 7 | } 8 | 9 | var prefix = ''; 10 | var maxlen = _.min(_.map(strings, function(s) { return s.length })); 11 | var i; 12 | var j; 13 | var c; 14 | 15 | for (i = 0; i < maxlen; i++) { 16 | c = strings[0].charAt(i); 17 | for (j = 0; j < strings.length; j++) { 18 | if (strings[j].charAt(i) !== c) { 19 | return prefix; 20 | } 21 | } 22 | prefix = prefix + c; 23 | } 24 | 25 | return prefix; 26 | } 27 | 28 | // Like longestCommonPrefix, but only breaks at whitespace 29 | var commonPrefix = function(strings) { 30 | var prefix = longestCommonPrefix(strings); 31 | if (strings[0] && strings[0] === prefix) { 32 | // All strings are the same 33 | return prefix; 34 | } 35 | 36 | var regex = /(^.*[\s\.]+)/; 37 | var match = regex.exec(prefix); 38 | if (match) { 39 | return match[1]; 40 | } 41 | return ''; 42 | } 43 | 44 | // Shortens a list of strings by removing common prefixes. 45 | var shorten = function(prefixFn, strings) { 46 | var prefix = prefixFn(strings); 47 | return _.map(strings, function(s) { 48 | if (s && s.length !== prefix.length) { 49 | return s.substring(prefix.length); 50 | } 51 | return s; 52 | }); 53 | }; 54 | 55 | return { 56 | commonPrefix: commonPrefix, 57 | longestCommonPrefix: longestCommonPrefix, 58 | shorten: shorten 59 | } 60 | })(); 61 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/subs.js: -------------------------------------------------------------------------------- 1 | var subs = (function() { 2 | 3 | // What server shall we connect to by default? 4 | var server; 5 | 6 | // What type of connection should we emit ? 7 | var server_type; 8 | 9 | // Subscription ID counter. 10 | var id_counter = -1; 11 | 12 | // Subscriptions 13 | var subs = {}; 14 | 15 | // Switch to turn on/off event processing 16 | var active = true; 17 | 18 | // Error queue for notification 19 | var errorQueue = []; 20 | 21 | // Instrumentation 22 | var load1 = profile.load(1000); 23 | var load5 = profile.load(5000); 24 | 25 | // Get a new subscription ID. 26 | var newId = function() { 27 | return id_counter += 1; 28 | } 29 | 30 | // Close a subscription's websocket channel. 31 | var close = function(sub) { 32 | return sub.close(); 33 | } 34 | 35 | // Closes a subscription and deletes it from the subscription manager. 36 | var unsubscribe = function(sub) { 37 | clock.unsubscribe(sub.clockSub); 38 | delete subs[sub.id]; 39 | return sub.close(); 40 | } 41 | 42 | // Unsubscribe from all subscriptions. 43 | var unsubscribeAll = function() { 44 | _.each(subs, unsubscribe); 45 | } 46 | 47 | // Open a subscription's websocket channel. 48 | var open = function(sub) { 49 | return sub.open(); 50 | } 51 | 52 | // Emit expired events from the prioqueue. 53 | var expire = function(sub, now) { 54 | prioqueue = subs[sub.id].prioqueue; 55 | if (! prioqueue) { 56 | return; 57 | } 58 | 59 | while (bottom = prioqueue.bottomPriority()) { 60 | expiry = new Date(bottom); 61 | if (now < expiry) { 62 | return; 63 | } 64 | expired = prioqueue.shift(); 65 | expired.time = expiry; 66 | expired.state = 'expired'; 67 | sub.f(expired); 68 | } 69 | } 70 | 71 | var Subscription = Backbone.Model.extend({ 72 | 73 | initialize: function(id, query, f) { 74 | this.id = id; 75 | this.query = query; 76 | this.f = f; 77 | this.prioqueue = new PriorityQueue(); 78 | this.clockSub = false; 79 | }, 80 | 81 | isOpen: function() { 82 | if (server_type == "ws") { 83 | return this.ws && (this.ws.readyState != WebSocket.CLOSED) 84 | } else { 85 | return this.ws && (this.ws.readyState != EventSource.CLOSED) 86 | } 87 | }, 88 | isClosed: function() { return !this.isOpen() }, 89 | 90 | url: function() { 91 | var queryString = "query=" + encodeURIComponent(this.query); 92 | var loc = window.location, ws_uri; 93 | 94 | if (server_type == "sse") { 95 | return loc.protocol + "//" + server + "/index?" + queryString; 96 | } else { 97 | ws_uri = (loc.protocol == "https:") ? "wss://" : "ws://"; 98 | return ws_uri + server + "/index?subscribe=true&" + queryString; 99 | } 100 | }, 101 | 102 | open: function() { 103 | if (this.isOpen()) return this; 104 | 105 | console.log("will open url: " + this.url()); 106 | 107 | var ws; 108 | if (server_type == "sse") { 109 | ws = this.ws = new EventSource(this.url()); 110 | } else { 111 | ws = this.ws = new WebSocket(this.url()); 112 | } 113 | 114 | ws.onopen = _.bind(function() { 115 | console.log("Socket opened", this.query); 116 | }, this); 117 | 118 | ws.onclose = _.bind(function(e) { 119 | console.log("Socket closed", this.query); 120 | this.ws = null; 121 | }, this); 122 | 123 | ws.onerror = _.bind(function(e) { 124 | console.log("Socket error", this.query); 125 | errorQueue.push(e); 126 | this.close(); 127 | }, this); 128 | 129 | ws.onmessage = _.bind(function(e) { 130 | t1 = Date.now(); 131 | if (active) { 132 | var event = JSON.parse(e.data); 133 | event.time = Date.parse(event.time); 134 | clock.advance(event.time); 135 | 136 | // Update local index. 137 | if (event.state !== "expired") { // only expired events have no TTL 138 | // TODO: get a prioqueue supporting delete so we can delete expired 139 | // events. 140 | this.prioqueue.update( 141 | {host: event.host, service: event.service}, 142 | event.time + ((event.ttl || 60) * 1000) // convert TTL to ms 143 | ); 144 | this.f(event); 145 | } 146 | } 147 | var t2 = Date.now(); 148 | load1(t1, t2); 149 | load5(t1, t2); 150 | }, this); 151 | 152 | return this; 153 | }, 154 | 155 | close: function() { 156 | if (this.ws) { 157 | this.ws.close(); 158 | this.ws = void 0; 159 | } 160 | return this; 161 | } 162 | }); 163 | 164 | // Add a subscription. Returns a subscription object. Subscriptions are 165 | // opened immediately. 166 | var subscribe = function(query, f) { 167 | var sub = new Subscription(newId(), query, f).open(); 168 | subs[sub.id] = sub; 169 | 170 | sub.clockSub = clock.subscribe(function(now) { 171 | expire(sub, now); 172 | }); 173 | 174 | return sub; 175 | } 176 | 177 | // Reconnect all inactive subs. 178 | var converge = function() { 179 | var closed = _.filter(subs, function(sub) { 180 | return sub.isClosed(); 181 | }); 182 | if (_.isEmpty(closed)) { 183 | // Done here. 184 | return; 185 | } 186 | 187 | // Reopen 188 | _.each(closed, function(sub) { 189 | open(sub); 190 | }); 191 | } 192 | 193 | var notifyErrors = function() { 194 | var errorString; 195 | if (errorQueue.length == 0) { 196 | return; 197 | } else if (errorQueue.length == 1) { 198 | errorString = "error"; 199 | } else { 200 | errorString = "errors"; 201 | } 202 | toastr.warning(errorQueue.length + " socket " + errorString + "; check the server field above."); 203 | errorQueue.length = 0; 204 | } 205 | 206 | // Periodically notify of errors. 207 | window.setInterval(notifyErrors, 100); 208 | 209 | // Periodically converge. 210 | setInterval(converge, 6000); 211 | 212 | // When terminating, close all connections. 213 | $(window).unload(unsubscribeAll); 214 | 215 | return { 216 | subscribe: subscribe, 217 | unsubscribe: unsubscribe, 218 | unsubscribeAll: unsubscribeAll, 219 | converge: converge, 220 | load1: load1, 221 | load5: load5, 222 | subs: function() { return subs; }, 223 | enable: function() { active = true; console.log("Subs enabled."); }, 224 | disable: function() { active = false; console.log("Subs disabled."); }, 225 | toggle: function() { 226 | active = ! active; 227 | if (active) { 228 | console.log("Subs enabled."); 229 | } else { 230 | console.log("Subs disabled."); 231 | } 232 | }, 233 | server: function(s) { 234 | if (s === undefined) { 235 | return server; 236 | } else { 237 | server = s; 238 | return s; 239 | } 240 | }, 241 | server_type: function(s) { 242 | if (s === undefined) { 243 | return server_type; 244 | } else { 245 | server_type = s; 246 | return s; 247 | } 248 | } 249 | }; 250 | })(); 251 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/toolbar.js: -------------------------------------------------------------------------------- 1 | var toolbar = (function() { 2 | // Build UI 3 | var toolbar = $('#toolbar'); 4 | var form = $('
'); 5 | toolbar.append(form); 6 | 7 | // Method to adjust the position of the view div 8 | var sizeAdjust = function() { 9 | $('#view').css('top', toolbar.height() + "px"); 10 | }; 11 | $( window ).resize(function() { 12 | sizeAdjust(); 13 | }); 14 | 15 | var pager = $('
'); 16 | var load = $('
Load
'); 17 | var server = $(''); 18 | var server_type = $('
'); 19 | var server_type_selector = $("input[name=server_type]"); 20 | var server_type_sse_selector = $("input#ws"); 21 | var server_type_ws_selector = $("input#sse"); 22 | form.append(pager); 23 | form.append(server_type); 24 | form.append(server); 25 | form.append(load); 26 | form.submit(function(e) { 27 | return false; 28 | }); 29 | 30 | // Load ///////////////////////////////////////////////////////////////////// 31 | 32 | window.setInterval(function() { 33 | load.find('span').text("Load " + 34 | format.float(subs.load1()) + ', ' + 35 | format.float(subs.load5())); 36 | load.find(".load1").animate({width: (subs.load1() * 100) + "%"}, 200); 37 | load.find(".load5").animate({width: (subs.load5() * 100) + "%"}, 1000); 38 | }, 1000); 39 | 40 | // Server /////////////////////////////////////////////////////////////////// 41 | 42 | // Callbacks 43 | var onServerChangeCallbacks = []; 44 | var onServerTypeChangeCallbacks = []; 45 | 46 | // React to server being set. 47 | var onServerChange = function(callback) { 48 | onServerChangeCallbacks.push(callback); 49 | } 50 | 51 | var onServerTypeChange = function(callback) { 52 | onServerTypeChangeCallbacks.push(callback); 53 | } 54 | 55 | // When server is set, call callbacks. 56 | server.change(function() { 57 | onServerChangeCallbacks.forEach(function(f) { 58 | f(server.val()); 59 | }); 60 | server.blur(); 61 | }); 62 | 63 | // When server_type is set, call callbacks. 64 | $('input#ws').change(function(e) { 65 | onServerTypeChangeCallbacks.forEach(function(f) { 66 | f($("input[name=server_type]:checked").val()); 67 | }); 68 | server.blur(); 69 | }); 70 | 71 | $('input#sse').change(function(e) { 72 | onServerTypeChangeCallbacks.forEach(function(f) { 73 | f($("input[name=server_type]:checked").val()); 74 | }); 75 | server.blur(); 76 | }); 77 | 78 | // Suppress keybindings 79 | server.focus(keys.disable); 80 | server.blur(keys.enable); 81 | 82 | // Pager //////////////////////////////////////////////////////////////////// 83 | 84 | var onWorkspaceChangeCallbacks = []; 85 | var onWorkspaceReorderCallbacks = []; 86 | var onWorkspaceSwitchCallbacks = []; 87 | var onWorkspaceAddCallbacks = []; 88 | var onWorkspaceDeleteCallbacks = []; 89 | var onWorkspaceChange = function(callback) { 90 | onWorkspaceChangeCallbacks.push(callback); 91 | }; 92 | var onWorkspaceReorder = function(callback) { 93 | onWorkspaceReorderCallbacks.push(callback); 94 | } 95 | var onWorkspaceSwitch = function(callback) { 96 | onWorkspaceSwitchCallbacks.push(callback); 97 | } 98 | var onWorkspaceAdd = function(callback) { 99 | onWorkspaceAddCallbacks.push(callback); 100 | } 101 | var onWorkspaceDelete = function(callback) { 102 | onWorkspaceDeleteCallbacks.push(callback); 103 | } 104 | 105 | // Set workspaces. 106 | var workspaces = function(workspaces) { 107 | pager.empty(); 108 | 109 | // Workspaces 110 | var workspaceList = $('
    '); 111 | pager.append(workspaceList); 112 | 113 | workspaces.forEach(function(workspace) { 114 | workspaceList.append(workspaceTile(workspace)); 115 | }); 116 | 117 | // Reordering 118 | workspaceList.sortable({ 119 | axis: "x", 120 | containment: pager, 121 | delay: 20, 122 | distance: 4, 123 | tolerance: "intersect", 124 | update: function() { 125 | console.log("hi"); 126 | var ids = workspaceList.find('li').map(function() { 127 | return $(this).data('workspaceId'); 128 | }); 129 | console.log("New ids are: ", ids); 130 | onWorkspaceReorderCallbacks.forEach(function(f) { 131 | f(ids); 132 | }); 133 | } 134 | }); 135 | 136 | // New button 137 | var add = $('
    +
    '); 138 | add.click(function() { 139 | onWorkspaceAddCallbacks.forEach(function(f) { 140 | f(); 141 | }); 142 | }); 143 | 144 | pager.append(add); 145 | sizeAdjust(); 146 | }; 147 | 148 | // Returns a tile for a workspace. 149 | var workspaceTile = function(workspace) { 150 | var tile = $('
  1. '); 151 | tile.text(workspace.name); 152 | tile.data('workspaceId', workspace.id); 153 | // tile.disableTextSelect(); 154 | 155 | // Switch to this workspace. 156 | tile.click(function() { 157 | if (! tile.hasClass("current")) { 158 | onWorkspaceSwitchCallbacks.forEach(function(f) { 159 | f(workspace); 160 | }); 161 | } 162 | }); 163 | 164 | // Edit this workspace name. 165 | tile.dblclick(function() { 166 | namer = workspaceNamer(workspace); 167 | keys.disable(); 168 | tile.replaceWith(namer); 169 | namer.focus(); 170 | }); 171 | 172 | // Delete 173 | var del = $('
    ×
    '); 174 | del.click(function() { 175 | onWorkspaceDeleteCallbacks.forEach(function(f) { 176 | f(workspace); 177 | }); 178 | }); 179 | tile.append(del); 180 | 181 | return tile; 182 | } 183 | 184 | // A box to rename a workspace. 185 | var workspaceNamer = function(workspace) { 186 | var field = $(''); 187 | field.val(workspace.name); 188 | 189 | // Change the workspace, firing callbacks and replacing the pager tile. 190 | var submit = function(w2) { 191 | onWorkspaceChangeCallbacks.forEach(function(f) { 192 | f(workspace, w2); 193 | }); 194 | 195 | keys.enable(); 196 | } 197 | 198 | // When the input changes, change the workspace. 199 | field.change(function() { 200 | var newWorkspace = _.clone(workspace); 201 | newWorkspace.name = field.val(); 202 | submit(newWorkspace); 203 | }); 204 | 205 | // When we leave focus, revert. 206 | field.blur(function() { submit(workspace) }); 207 | field.keydown(function(e) { 208 | if (e.which === 13) { 209 | field.change(); 210 | } else if (e.which === 27) { 211 | submit(workspace); 212 | } 213 | }); 214 | 215 | return field; 216 | } 217 | 218 | // Focus a workspace. 219 | var workspace = function(workspace) { 220 | console.log("Switching to workspace", workspace); 221 | pager.find('li').removeClass('current'); 222 | if (workspace === null) { 223 | return; 224 | } 225 | 226 | pager.find('li').each(function(i, el) { 227 | if ($(el).data('workspaceId') === workspace.id) { 228 | $(el).addClass('current'); 229 | } 230 | }); 231 | } 232 | 233 | return { 234 | server: function(s) { 235 | if (s === undefined) { 236 | return server.val(); 237 | } else { 238 | server.val(s); 239 | return s; 240 | } 241 | }, 242 | server_type: function(s) { 243 | if (s === undefined) { 244 | return $("input[name=server_type]:checked").val(); 245 | } else { 246 | console.log("handling server_type: " + s); 247 | if (s == "sse") { 248 | $("input[name=server_type]#sse").attr("checked",true); 249 | } else if (s == "ws") { 250 | $("input[name=server_type]#ws").attr("checked",true); 251 | } 252 | return s; 253 | } 254 | }, 255 | 256 | onServerChange: onServerChange, 257 | onServerTypeChange: onServerTypeChange, 258 | onWorkspaceChange: onWorkspaceChange, 259 | onWorkspaceReorder: onWorkspaceReorder, 260 | onWorkspaceSwitch: onWorkspaceSwitch, 261 | onWorkspaceAdd: onWorkspaceAdd, 262 | onWorkspaceDelete: onWorkspaceDelete, 263 | workspaces: workspaces, 264 | workspace: workspace 265 | } 266 | })(); 267 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/util.js: -------------------------------------------------------------------------------- 1 | var util = (function() { 2 | // Takes the value of x as a string, or if x is undefined/null, a special 3 | // marker string. Used because JS maps can't contain nil. 4 | var nullableKey = function(x) { 5 | return x || "\uffff"; 6 | }; 7 | 8 | return { 9 | nullableKey: nullableKey, 10 | 11 | // A string key uniquely identifying an event by host and service. 12 | eventKey: function(e) { 13 | return nullableKey(e.host) + "\ufffe" + nullableKey(e.service); 14 | }, 15 | 16 | // Takes a string and returns a function that extracts a value from an 17 | // event. 18 | extract_fn: function(str) { 19 | // When null/undefined, stay null/undefined. 20 | if (! str) { 21 | return str; 22 | } 23 | 24 | // Probably the worst hack ever. I'm not documenting this because it's so 25 | // evil--though tremendously useful. 26 | if (str.match(/^fn /)) { 27 | // Grab the rest of the string, turn it into an anonymous fn taking a 28 | // single arg `e`. 29 | return Function.apply(null, ['e', str.substring(3)]); 30 | } 31 | 32 | // Property access 33 | return function(e) { 34 | return e[str]; 35 | }; 36 | }, 37 | 38 | // Takes a string and returns either: 39 | // - a function which extracts a maximum value from an event. 40 | // - a number to be used as the constant maximum. 41 | max_fn: function(str) { 42 | if ((!str) || str === "all") { 43 | // Always the same value: global maxima 44 | return function(e) { return "all"; }; 45 | } 46 | if (isNaN(parseFloat(str))) { 47 | // Not a number. Extract a field. 48 | return function(e) { return e[str]; }; 49 | } 50 | // Return a constant number. 51 | return parseFloat(str); 52 | }, 53 | 54 | // Merge two maps nondestructively. 55 | merge: function(m1, m2) { 56 | return _.extend({}, m1, m2) 57 | }, 58 | 59 | // Wraps a function in another, which calls f at most once every period 60 | // milliseconds. Tries to minimize latency. 61 | slur: function(period, f) { 62 | var lastRun = new Date(); 63 | lastRun.setYear(0); 64 | var queued = false; 65 | var execute = function(context, args) { 66 | var t1 = new Date(); 67 | f.apply(context, args); 68 | lastRun = new Date(); 69 | subs.load1(t1, lastRun); 70 | subs.load5(t1, lastRun); 71 | queued = false; 72 | }; 73 | 74 | return function() { 75 | // If queued, do nothing 76 | if (queued) { 77 | return; 78 | } 79 | 80 | var dt = (new Date()) - lastRun; 81 | if (period <= dt) { 82 | // We're free to go 83 | execute(this, arguments); 84 | } 85 | else { 86 | // Too soon, enqueue a new job. 87 | queued = true; 88 | window.setTimeout(execute, period - dt, this, arguments); 89 | } 90 | } 91 | }, 92 | 93 | // Unique-ish IDs as a length sized string of hex 94 | uniqueId: function(length) { 95 | var id = '', hex = '0123456789abcdef'; 96 | _(length || 40).times(function() { id += hex[_.random(15)]; }); 97 | return id; 98 | } 99 | }; 100 | })(); 101 | 102 | $(function() { 103 | // Allow disabling text selection. 104 | $.extend($.fn.disableTextSelect = function() { 105 | return this.each(function(){ 106 | if($.browser.mozilla){//Firefox 107 | $(this).css('MozUserSelect','none'); 108 | }else if($.browser.msie){//IE 109 | $(this).bind('selectstart',function(){return false;}); 110 | }else{//Opera, etc. 111 | $(this).mousedown(function(){return false;}); 112 | } 113 | }); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/vendor/PriorityQueue.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Object to be appended in the priority queue. 3 | * 4 | * @class 5 | * @param mixed v 6 | * @param integer p 7 | * @author Augusto Pascutti 8 | */ 9 | var QueueItem = function(v, p) { 10 | this.value = v; 11 | this.priority = p; 12 | this.value_JSON = JSON.stringify(v); 13 | }; 14 | 15 | /** 16 | * Priority queue class. 17 | * 18 | * @class 19 | * @param Function[optional] c Compare function to be used 20 | * @param integer[optional] m Max number of elements to hold 21 | * @author Augusto Pascutti 22 | */ 23 | var PriorityQueue = function(c, m) { this.init(c, m) }; 24 | PriorityQueue.prototype = { 25 | _queue: [], 26 | _compare: undefined, 27 | _size: 0, 28 | 29 | /** 30 | * Priority queue class constructor. 31 | * 32 | * @class 33 | * @param Function[optional] compare_function Compare function to be used 34 | * @param integer[optional] maximum_size Max number of elements to hold 35 | */ 36 | init: function(compare_function, maximum_size) { 37 | this._compare = compare_function || undefined; 38 | this._size = maximum_size || 0 ; 39 | this.reset(); 40 | }, 41 | 42 | /** 43 | * Pushes something to the priority queue. 44 | * 45 | * @param mixed value 46 | * @param integer priority 47 | * @return void 48 | */ 49 | push: function(value, priority) { 50 | this._queue.push(new QueueItem(value, priority)); 51 | this._queue.sort(this.compare()); 52 | this._maitain(); 53 | }, 54 | 55 | 56 | /** 57 | * Update priority of something in the queue. Will silently add it if it 58 | * doesn't already exist. 59 | * 60 | * @param mixed value 61 | * @param integer priority 62 | * @return void 63 | */ 64 | update: function(value, priority) { 65 | known = false; 66 | idx = 0; 67 | 68 | var value_JSON = JSON.stringify(value); 69 | this._queue.forEach(function() { 70 | if (this._queue[idx].value_JSON === value_JSON) { 71 | this._queue[idx].priority = priority; 72 | known = true; 73 | return; 74 | } 75 | idx++; 76 | }, this); 77 | 78 | if (!known) { 79 | this._queue.push(new QueueItem(value, priority)); 80 | } 81 | this._queue.sort(this.compare()); 82 | this._maitain(); 83 | }, 84 | 85 | /** 86 | * Removes the most important item and return its value. 87 | * 88 | * @return mixed 89 | */ 90 | pop: function() { 91 | item = this._queue.shift(); 92 | this._maitain(); 93 | return (item) ? item.value : undefined; 94 | }, 95 | 96 | /** 97 | * Returns most important item value from this queue, without removing it. 98 | * 99 | * @return mixed 100 | */ 101 | top: function() { 102 | item = this._queue[0]; 103 | return (item) ? item.value : undefined; 104 | }, 105 | 106 | /** 107 | * Returns most important item priority from this queue, without removing it. 108 | * 109 | * @return mixed 110 | */ 111 | topPriority: function() { 112 | item = this._queue[0]; 113 | return (item) ? item.priority : undefined; 114 | }, 115 | 116 | /** 117 | * Removes the less important item and return its value. 118 | * 119 | * @return mixed 120 | */ 121 | shift: function() { 122 | item = this._queue.pop(); 123 | this._maitain(); 124 | return (item) ? item.value : undefined; 125 | }, 126 | 127 | /** 128 | * Returns the less important item value, without removing it. 129 | * 130 | * @return mixed 131 | */ 132 | bottom: function() { 133 | idx = this.length-1; 134 | item = this._queue[idx]; 135 | return (item) ? item.value : undefined; 136 | }, 137 | 138 | /** 139 | * Returns the less important item priority, without removing it. 140 | * 141 | * @return mixed 142 | */ 143 | bottomPriority: function() { 144 | idx = this.length-1; 145 | item = this._queue[idx]; 146 | return (item) ? item.priority : undefined; 147 | }, 148 | 149 | /** 150 | * Returns the ordered queue as an Array. 151 | * 152 | * @return Array 153 | */ 154 | getArray: function() { 155 | return this._queue || new Array(); 156 | }, 157 | 158 | /** 159 | * Resets the queue. 160 | * 161 | * @return void 162 | */ 163 | reset: function() { 164 | this._queue = []; 165 | this._maitain(); 166 | }, 167 | 168 | /** 169 | * Returns the compare function. 170 | * If no compare function is set, defines a default one. 171 | * 172 | * @return Function 173 | */ 174 | compare: function() { 175 | if (!this._compare) { 176 | this._compare = function(a,b) { 177 | return b.priority - a.priority; 178 | }; 179 | } 180 | return this._compare; 181 | }, 182 | 183 | /** 184 | * Defines a fixed size to the queue. 185 | * Zero for no limit and any other number to set it as the highest number 186 | * of items allowed in this queue. 187 | * 188 | * @param integer i 189 | * @return void 190 | */ 191 | size: function(i) { 192 | this._size = i; 193 | }, 194 | 195 | /** 196 | * Keeps the size of the queue by removing the less important item of it and length 197 | * information atribute. 198 | * 199 | * @return void 200 | */ 201 | _maitain: function() { 202 | this.length = this._queue.length; 203 | if ( this._size == 0 ) return; 204 | while (this._size < this.length) { 205 | this.shift(); 206 | } 207 | }, 208 | }; 209 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/vendor/flot/jquery.colorhelpers.js: -------------------------------------------------------------------------------- 1 | /* Plugin for jQuery for working with colors. 2 | * 3 | * Version 1.1. 4 | * 5 | * Inspiration from jQuery color animation plugin by John Resig. 6 | * 7 | * Released under the MIT license by Ole Laursen, October 2009. 8 | * 9 | * Examples: 10 | * 11 | * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() 12 | * var c = $.color.extract($("#mydiv"), 'background-color'); 13 | * console.log(c.r, c.g, c.b, c.a); 14 | * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" 15 | * 16 | * Note that .scale() and .add() return the same modified object 17 | * instead of making a new one. 18 | * 19 | * V. 1.1: Fix error handling so e.g. parsing an empty string does 20 | * produce a color rather than just crashing. 21 | */ 22 | 23 | (function($) { 24 | $.color = {}; 25 | 26 | // construct color object with some convenient chainable helpers 27 | $.color.make = function (r, g, b, a) { 28 | var o = {}; 29 | o.r = r || 0; 30 | o.g = g || 0; 31 | o.b = b || 0; 32 | o.a = a != null ? a : 1; 33 | 34 | o.add = function (c, d) { 35 | for (var i = 0; i < c.length; ++i) 36 | o[c.charAt(i)] += d; 37 | return o.normalize(); 38 | }; 39 | 40 | o.scale = function (c, f) { 41 | for (var i = 0; i < c.length; ++i) 42 | o[c.charAt(i)] *= f; 43 | return o.normalize(); 44 | }; 45 | 46 | o.toString = function () { 47 | if (o.a >= 1.0) { 48 | return "rgb("+[o.r, o.g, o.b].join(",")+")"; 49 | } else { 50 | return "rgba("+[o.r, o.g, o.b, o.a].join(",")+")"; 51 | } 52 | }; 53 | 54 | o.normalize = function () { 55 | function clamp(min, value, max) { 56 | return value < min ? min: (value > max ? max: value); 57 | } 58 | 59 | o.r = clamp(0, parseInt(o.r), 255); 60 | o.g = clamp(0, parseInt(o.g), 255); 61 | o.b = clamp(0, parseInt(o.b), 255); 62 | o.a = clamp(0, o.a, 1); 63 | return o; 64 | }; 65 | 66 | o.clone = function () { 67 | return $.color.make(o.r, o.b, o.g, o.a); 68 | }; 69 | 70 | return o.normalize(); 71 | } 72 | 73 | // extract CSS color property from element, going up in the DOM 74 | // if it's "transparent" 75 | $.color.extract = function (elem, css) { 76 | var c; 77 | do { 78 | c = elem.css(css).toLowerCase(); 79 | // keep going until we find an element that has color, or 80 | // we hit the body 81 | if (c != '' && c != 'transparent') 82 | break; 83 | elem = elem.parent(); 84 | } while (!$.nodeName(elem.get(0), "body")); 85 | 86 | // catch Safari's way of signalling transparent 87 | if (c == "rgba(0, 0, 0, 0)") 88 | c = "transparent"; 89 | 90 | return $.color.parse(c); 91 | } 92 | 93 | // parse CSS color string (like "rgb(10, 32, 43)" or "#fff"), 94 | // returns color object, if parsing failed, you get black (0, 0, 95 | // 0) out 96 | $.color.parse = function (str) { 97 | var res, m = $.color.make; 98 | 99 | // Look for rgb(num,num,num) 100 | if (res = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str)) 101 | return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10)); 102 | 103 | // Look for rgba(num,num,num,num) 104 | if (res = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)) 105 | return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10), parseFloat(res[4])); 106 | 107 | // Look for rgb(num%,num%,num%) 108 | if (res = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str)) 109 | return m(parseFloat(res[1])*2.55, parseFloat(res[2])*2.55, parseFloat(res[3])*2.55); 110 | 111 | // Look for rgba(num%,num%,num%,num) 112 | if (res = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)) 113 | return m(parseFloat(res[1])*2.55, parseFloat(res[2])*2.55, parseFloat(res[3])*2.55, parseFloat(res[4])); 114 | 115 | // Look for #a0b1c2 116 | if (res = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str)) 117 | return m(parseInt(res[1], 16), parseInt(res[2], 16), parseInt(res[3], 16)); 118 | 119 | // Look for #fff 120 | if (res = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str)) 121 | return m(parseInt(res[1]+res[1], 16), parseInt(res[2]+res[2], 16), parseInt(res[3]+res[3], 16)); 122 | 123 | // Otherwise, we're most likely dealing with a named color 124 | var name = $.trim(str).toLowerCase(); 125 | if (name == "transparent") 126 | return m(255, 255, 255, 0); 127 | else { 128 | // default to black 129 | res = lookupColors[name] || [0, 0, 0]; 130 | return m(res[0], res[1], res[2]); 131 | } 132 | } 133 | 134 | var lookupColors = { 135 | aqua:[0,255,255], 136 | azure:[240,255,255], 137 | beige:[245,245,220], 138 | black:[0,0,0], 139 | blue:[0,0,255], 140 | brown:[165,42,42], 141 | cyan:[0,255,255], 142 | darkblue:[0,0,139], 143 | darkcyan:[0,139,139], 144 | darkgrey:[169,169,169], 145 | darkgreen:[0,100,0], 146 | darkkhaki:[189,183,107], 147 | darkmagenta:[139,0,139], 148 | darkolivegreen:[85,107,47], 149 | darkorange:[255,140,0], 150 | darkorchid:[153,50,204], 151 | darkred:[139,0,0], 152 | darksalmon:[233,150,122], 153 | darkviolet:[148,0,211], 154 | fuchsia:[255,0,255], 155 | gold:[255,215,0], 156 | green:[0,128,0], 157 | indigo:[75,0,130], 158 | khaki:[240,230,140], 159 | lightblue:[173,216,230], 160 | lightcyan:[224,255,255], 161 | lightgreen:[144,238,144], 162 | lightgrey:[211,211,211], 163 | lightpink:[255,182,193], 164 | lightyellow:[255,255,224], 165 | lime:[0,255,0], 166 | magenta:[255,0,255], 167 | maroon:[128,0,0], 168 | navy:[0,0,128], 169 | olive:[128,128,0], 170 | orange:[255,165,0], 171 | pink:[255,192,203], 172 | purple:[128,0,128], 173 | violet:[128,0,128], 174 | red:[255,0,0], 175 | silver:[192,192,192], 176 | white:[255,255,255], 177 | yellow:[255,255,0] 178 | }; 179 | })(jQuery); 180 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/vendor/flot/jquery.colorhelpers.min.js: -------------------------------------------------------------------------------- 1 | /* Plugin for jQuery for working with colors. 2 | * 3 | * Version 1.1. 4 | * 5 | * Inspiration from jQuery color animation plugin by John Resig. 6 | * 7 | * Released under the MIT license by Ole Laursen, October 2009. 8 | * 9 | * Examples: 10 | * 11 | * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() 12 | * var c = $.color.extract($("#mydiv"), 'background-color'); 13 | * console.log(c.r, c.g, c.b, c.a); 14 | * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" 15 | * 16 | * Note that .scale() and .add() return the same modified object 17 | * instead of making a new one. 18 | * 19 | * V. 1.1: Fix error handling so e.g. parsing an empty string does 20 | * produce a color rather than just crashing. 21 | */(function(e){e.color={},e.color.make=function(t,n,r,i){var s={};return s.r=t||0,s.g=n||0,s.b=r||0,s.a=i!=null?i:1,s.add=function(e,t){for(var n=0;n=1?"rgb("+[s.r,s.g,s.b].join(",")+")":"rgba("+[s.r,s.g,s.b,s.a].join(",")+")"},s.normalize=function(){function e(e,t,n){return tn?n:t}return s.r=e(0,parseInt(s.r),255),s.g=e(0,parseInt(s.g),255),s.b=e(0,parseInt(s.b),255),s.a=e(0,s.a,1),s},s.clone=function(){return e.color.make(s.r,s.b,s.g,s.a)},s.normalize()},e.color.extract=function(t,n){var r;do{r=t.css(n).toLowerCase();if(r!=""&&r!="transparent")break;t=t.parent()}while(!e.nodeName(t.get(0),"body"));return r=="rgba(0, 0, 0, 0)"&&(r="transparent"),e.color.parse(r)},e.color.parse=function(n){var r,i=e.color.make;if(r=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(n))return i(parseInt(r[1],10),parseInt(r[2],10),parseInt(r[3],10));if(r=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(n))return i(parseInt(r[1],10),parseInt(r[2],10),parseInt(r[3],10),parseFloat(r[4]));if(r=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(n))return i(parseFloat(r[1])*2.55,parseFloat(r[2])*2.55,parseFloat(r[3])*2.55);if(r=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(n))return i(parseFloat(r[1])*2.55,parseFloat(r[2])*2.55,parseFloat(r[3])*2.55,parseFloat(r[4]));if(r=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(n))return i(parseInt(r[1],16),parseInt(r[2],16),parseInt(r[3],16));if(r=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(n))return i(parseInt(r[1]+r[1],16),parseInt(r[2]+r[2],16),parseInt(r[3]+r[3],16));var s=e.trim(n).toLowerCase();return s=="transparent"?i(255,255,255,0):(r=t[s]||[0,0,0],i(r[0],r[1],r[2]))};var t={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery); -------------------------------------------------------------------------------- /lib/riemann/dash/public/vendor/flot/jquery.flot.canvas.min.js: -------------------------------------------------------------------------------- 1 | /* Flot plugin for drawing all elements of a plot on the canvas. 2 | 3 | Copyright (c) 2007-2013 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | Flot normally produces certain elements, like axis labels and the legend, using 7 | HTML elements. This permits greater interactivity and customization, and often 8 | looks better, due to cross-browser canvas text inconsistencies and limitations. 9 | 10 | It can also be desirable to render the plot entirely in canvas, particularly 11 | if the goal is to save it as an image, or if Flot is being used in a context 12 | where the HTML DOM does not exist, as is the case within Node.js. This plugin 13 | switches out Flot's standard drawing operations for canvas-only replacements. 14 | 15 | Currently the plugin supports only axis labels, but it will eventually allow 16 | every element of the plot to be rendered directly to canvas. 17 | 18 | The plugin supports these options: 19 | 20 | { 21 | canvas: boolean 22 | } 23 | 24 | The "canvas" option controls whether full canvas drawing is enabled, making it 25 | possible to toggle on and off. This is useful when a plot uses HTML text in the 26 | browser, but needs to redraw with canvas text when exporting as an image. 27 | 28 | */(function(e){function o(t,o){var u=o.Canvas;n==null&&(r=u.prototype.getTextInfo,i=u.prototype.addText,n=u.prototype.render),u.prototype.render=function(){if(!t.getOptions().canvas)return n.call(this);var e=this.context,r=this._textCache;e.save(),e.textBaseline="middle";for(var i in r)if(s.call(r,i)){var o=r[i];for(var u in o)if(s.call(o,u)){var a=o[u],f=!0;for(var l in a)if(s.call(a,l)){var c=a[l],h=c.positions,p=c.lines;f&&(e.fillStyle=c.font.color,e.font=c.font.definition,f=!1);for(var d=0,v;v=h[d];d++)if(v.active)for(var m=0,g;g=v.lines[m];m++)e.fillText(p[m].text,g[0],g[1]);else h.splice(d--,1);h.length==0&&delete a[l]}}}e.restore()},u.prototype.getTextInfo=function(n,i,s,o,u){if(!t.getOptions().canvas)return r.call(this,n,i,s,o,u);var a,f,l,c;i=""+i,typeof s=="object"?a=s.style+" "+s.variant+" "+s.weight+" "+s.size+"px "+s.family:a=s,f=this._textCache[n],f==null&&(f=this._textCache[n]={}),l=f[a],l==null&&(l=f[a]={}),c=l[i];if(c==null){var h=this.context;if(typeof s!="object"){var p=e("
     
    ").css("position","absolute").addClass(typeof s=="string"?s:null).appendTo(this.getTextLayer(n));s={lineHeight:p.height(),style:p.css("font-style"),variant:p.css("font-variant"),weight:p.css("font-weight"),family:p.css("font-family"),color:p.css("color")},s.size=p.css("line-height",1).height(),p.remove()}a=s.style+" "+s.variant+" "+s.weight+" "+s.size+"px "+s.family,c=l[i]={width:0,height:0,positions:[],lines:[],font:{definition:a,color:s.color}},h.save(),h.font=a;var d=(i+"").replace(/
    |\r\n|\r/g,"\n").split("\n");for(var v=0;v index) 102 | index = categories[v]; 103 | 104 | return index + 1; 105 | } 106 | 107 | function categoriesTickGenerator(axis) { 108 | var res = []; 109 | for (var label in axis.categories) { 110 | var v = axis.categories[label]; 111 | if (v >= axis.min && v <= axis.max) 112 | res.push([v, label]); 113 | } 114 | 115 | res.sort(function (a, b) { return a[0] - b[0]; }); 116 | 117 | return res; 118 | } 119 | 120 | function setupCategoriesForAxis(series, axis, datapoints) { 121 | if (series[axis].options.mode != "categories") 122 | return; 123 | 124 | if (!series[axis].categories) { 125 | // parse options 126 | var c = {}, o = series[axis].options.categories || {}; 127 | if ($.isArray(o)) { 128 | for (var i = 0; i < o.length; ++i) 129 | c[o[i]] = i; 130 | } 131 | else { 132 | for (var v in o) 133 | c[v] = o[v]; 134 | } 135 | 136 | series[axis].categories = c; 137 | } 138 | 139 | // fix ticks 140 | if (!series[axis].options.ticks) 141 | series[axis].options.ticks = categoriesTickGenerator; 142 | 143 | transformPointsOnAxis(datapoints, axis, series[axis].categories); 144 | } 145 | 146 | function transformPointsOnAxis(datapoints, axis, categories) { 147 | // go through the points, transforming them 148 | var points = datapoints.points, 149 | ps = datapoints.pointsize, 150 | format = datapoints.format, 151 | formatColumn = axis.charAt(0), 152 | index = getNextIndex(categories); 153 | 154 | for (var i = 0; i < points.length; i += ps) { 155 | if (points[i] == null) 156 | continue; 157 | 158 | for (var m = 0; m < ps; ++m) { 159 | var val = points[i + m]; 160 | 161 | if (val == null || !format[m][formatColumn]) 162 | continue; 163 | 164 | if (!(val in categories)) { 165 | categories[val] = index; 166 | ++index; 167 | } 168 | 169 | points[i + m] = categories[val]; 170 | } 171 | } 172 | } 173 | 174 | function processDatapoints(plot, series, datapoints) { 175 | setupCategoriesForAxis(series, "xaxis", datapoints); 176 | setupCategoriesForAxis(series, "yaxis", datapoints); 177 | } 178 | 179 | function init(plot) { 180 | plot.hooks.processRawData.push(processRawData); 181 | plot.hooks.processDatapoints.push(processDatapoints); 182 | } 183 | 184 | $.plot.plugins.push({ 185 | init: init, 186 | options: options, 187 | name: 'categories', 188 | version: '1.0' 189 | }); 190 | })(jQuery); 191 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/vendor/flot/jquery.flot.categories.min.js: -------------------------------------------------------------------------------- 1 | /* Flot plugin for plotting textual data or categories. 2 | 3 | Copyright (c) 2007-2013 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | Consider a dataset like [["February", 34], ["March", 20], ...]. This plugin 7 | allows you to plot such a dataset directly. 8 | 9 | To enable it, you must specify mode: "categories" on the axis with the textual 10 | labels, e.g. 11 | 12 | $.plot("#placeholder", data, { xaxis: { mode: "categories" } }); 13 | 14 | By default, the labels are ordered as they are met in the data series. If you 15 | need a different ordering, you can specify "categories" on the axis options 16 | and list the categories there: 17 | 18 | xaxis: { 19 | mode: "categories", 20 | categories: ["February", "March", "April"] 21 | } 22 | 23 | If you need to customize the distances between the categories, you can specify 24 | "categories" as an object mapping labels to values 25 | 26 | xaxis: { 27 | mode: "categories", 28 | categories: { "February": 1, "March": 3, "April": 4 } 29 | } 30 | 31 | If you don't specify all categories, the remaining categories will be numbered 32 | from the max value plus 1 (with a spacing of 1 between each). 33 | 34 | Internally, the plugin works by transforming the input data through an auto- 35 | generated mapping where the first category becomes 0, the second 1, etc. 36 | Hence, a point like ["February", 34] becomes [0, 34] internally in Flot (this 37 | is visible in hover and click events that return numbers rather than the 38 | category labels). The plugin also overrides the tick generator to spit out the 39 | categories as ticks instead of the values. 40 | 41 | If you need to map a value back to its label, the mapping is always accessible 42 | as "categories" on the axis object, e.g. plot.getAxes().xaxis.categories. 43 | 44 | */(function(e){function n(e,t,n,r){var i=t.xaxis.options.mode=="categories",s=t.yaxis.options.mode=="categories";if(!i&&!s)return;var o=r.format;if(!o){var u=t;o=[],o.push({x:!0,number:!0,required:!0}),o.push({y:!0,number:!0,required:!0});if(u.bars.show||u.lines.show&&u.lines.fill){var a=!!(u.bars.show&&u.bars.zero||u.lines.show&&u.lines.zero);o.push({y:!0,number:!0,required:!1,defaultValue:0,autoscale:a}),u.bars.horizontal&&(delete o[o.length-1].y,o[o.length-1].x=!0)}r.format=o}for(var f=0;ft&&(t=e[n]);return t+1}function i(e){var t=[];for(var n in e.categories){var r=e.categories[n];r>=e.min&&r<=e.max&&t.push([r,n])}return t.sort(function(e,t){return e[0]-t[0]}),t}function s(t,n,r){if(t[n].options.mode!="categories")return;if(!t[n].categories){var s={},u=t[n].options.categories||{};if(e.isArray(u))for(var a=0;au[1].max||yu[0].max)continue;if(f[v].err=="y")if(g>u[0].max||gu[1].max)continue;var E=!0,S=!0;b>m[1]&&(E=!1,b=m[1]),w0&&T>0){var N=T/2;t.lineWidth=N,t.strokeStyle="rgba(0,0,0,0.1)",s(t,f[v],g,y,b,w,E,S,a,N+N/2,m),t.strokeStyle="rgba(0,0,0,0.2)",s(t,f[v],g,y,b,w,E,S,a,N/2,m)}t.strokeStyle=f[v].color?f[v].color:n.color,t.lineWidth=x,s(t,f[v],g,y,b,w,E,S,a,0,m)}}}}function s(t,n,r,i,s,u,a,f,l,c,h){i+=c,s+=c,u+=c,n.err=="x"?(s>r+l?o(t,[[s,i],[Math.max(r+l,h[0]),i]]):a=!1,ui+l?o(t,[[r,Math.max(i+l,h[1])],[r,u]]):f=!1),l=n.radius!=null?n.radius:l,a&&(n.upperCap=="-"?n.err=="x"?o(t,[[s,i-l],[s,i+l]]):o(t,[[r-l,s],[r+l,s]]):e.isFunction(n.upperCap)&&(n.err=="x"?n.upperCap(t,s,i,l):n.upperCap(t,r,s,l))),f&&(n.lowerCap=="-"?n.err=="x"?o(t,[[u,i-l],[u,i+l]]):o(t,[[r-l,u],[r+l,u]]):e.isFunction(n.lowerCap)&&(n.err=="x"?n.lowerCap(t,u,i,l):n.lowerCap(t,r,u,l)))}function o(e,t){e.beginPath(),e.moveTo(t[0][0],t[0][1]);for(var n=1;n= allseries.length ) { 54 | return null; 55 | } 56 | return allseries[ s.fillBetween ]; 57 | } 58 | 59 | return null; 60 | } 61 | 62 | function computeFillBottoms( plot, s, datapoints ) { 63 | 64 | if ( s.fillBetween == null ) { 65 | return; 66 | } 67 | 68 | var other = findBottomSeries( s, plot.getData() ); 69 | 70 | if ( !other ) { 71 | return; 72 | } 73 | 74 | var ps = datapoints.pointsize, 75 | points = datapoints.points, 76 | otherps = other.datapoints.pointsize, 77 | otherpoints = other.datapoints.points, 78 | newpoints = [], 79 | px, py, intery, qx, qy, bottom, 80 | withlines = s.lines.show, 81 | withbottom = ps > 2 && datapoints.format[2].y, 82 | withsteps = withlines && s.lines.steps, 83 | fromgap = true, 84 | i = 0, 85 | j = 0, 86 | l, m; 87 | 88 | while ( true ) { 89 | 90 | if ( i >= points.length ) { 91 | break; 92 | } 93 | 94 | l = newpoints.length; 95 | 96 | if ( points[ i ] == null ) { 97 | 98 | // copy gaps 99 | 100 | for ( m = 0; m < ps; ++m ) { 101 | newpoints.push( points[ i + m ] ); 102 | } 103 | 104 | i += ps; 105 | 106 | } else if ( j >= otherpoints.length ) { 107 | 108 | // for lines, we can't use the rest of the points 109 | 110 | if ( !withlines ) { 111 | for ( m = 0; m < ps; ++m ) { 112 | newpoints.push( points[ i + m ] ); 113 | } 114 | } 115 | 116 | i += ps; 117 | 118 | } else if ( otherpoints[ j ] == null ) { 119 | 120 | // oops, got a gap 121 | 122 | for ( m = 0; m < ps; ++m ) { 123 | newpoints.push( null ); 124 | } 125 | 126 | fromgap = true; 127 | j += otherps; 128 | 129 | } else { 130 | 131 | // cases where we actually got two points 132 | 133 | px = points[ i ]; 134 | py = points[ i + 1 ]; 135 | qx = otherpoints[ j ]; 136 | qy = otherpoints[ j + 1 ]; 137 | bottom = 0; 138 | 139 | if ( px === qx ) { 140 | 141 | for ( m = 0; m < ps; ++m ) { 142 | newpoints.push( points[ i + m ] ); 143 | } 144 | 145 | //newpoints[ l + 1 ] += qy; 146 | bottom = qy; 147 | 148 | i += ps; 149 | j += otherps; 150 | 151 | } else if ( px > qx ) { 152 | 153 | // we got past point below, might need to 154 | // insert interpolated extra point 155 | 156 | if ( withlines && i > 0 && points[ i - ps ] != null ) { 157 | intery = py + ( points[ i - ps + 1 ] - py ) * ( qx - px ) / ( points[ i - ps ] - px ); 158 | newpoints.push( qx ); 159 | newpoints.push( intery ); 160 | for ( m = 2; m < ps; ++m ) { 161 | newpoints.push( points[ i + m ] ); 162 | } 163 | bottom = qy; 164 | } 165 | 166 | j += otherps; 167 | 168 | } else { // px < qx 169 | 170 | // if we come from a gap, we just skip this point 171 | 172 | if ( fromgap && withlines ) { 173 | i += ps; 174 | continue; 175 | } 176 | 177 | for ( m = 0; m < ps; ++m ) { 178 | newpoints.push( points[ i + m ] ); 179 | } 180 | 181 | // we might be able to interpolate a point below, 182 | // this can give us a better y 183 | 184 | if ( withlines && j > 0 && otherpoints[ j - otherps ] != null ) { 185 | bottom = qy + ( otherpoints[ j - otherps + 1 ] - qy ) * ( px - qx ) / ( otherpoints[ j - otherps ] - qx ); 186 | } 187 | 188 | //newpoints[l + 1] += bottom; 189 | 190 | i += ps; 191 | } 192 | 193 | fromgap = false; 194 | 195 | if ( l !== newpoints.length && withbottom ) { 196 | newpoints[ l + 2 ] = bottom; 197 | } 198 | } 199 | 200 | // maintain the line steps invariant 201 | 202 | if ( withsteps && l !== newpoints.length && l > 0 && 203 | newpoints[ l ] !== null && 204 | newpoints[ l ] !== newpoints[ l - ps ] && 205 | newpoints[ l + 1 ] !== newpoints[ l - ps + 1 ] ) { 206 | for (m = 0; m < ps; ++m) { 207 | newpoints[ l + ps + m ] = newpoints[ l + m ]; 208 | } 209 | newpoints[ l + 1 ] = newpoints[ l - ps + 1 ]; 210 | } 211 | } 212 | 213 | datapoints.points = newpoints; 214 | } 215 | 216 | plot.hooks.processDatapoints.push( computeFillBottoms ); 217 | } 218 | 219 | $.plot.plugins.push({ 220 | init: init, 221 | options: options, 222 | name: "fillbetween", 223 | version: "1.0" 224 | }); 225 | 226 | })(jQuery); 227 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/vendor/flot/jquery.flot.fillbetween.min.js: -------------------------------------------------------------------------------- 1 | /* Flot plugin for computing bottoms for filled line and bar charts. 2 | 3 | Copyright (c) 2007-2013 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | The case: you've got two series that you want to fill the area between. In Flot 7 | terms, you need to use one as the fill bottom of the other. You can specify the 8 | bottom of each data point as the third coordinate manually, or you can use this 9 | plugin to compute it for you. 10 | 11 | In order to name the other series, you need to give it an id, like this: 12 | 13 | var dataset = [ 14 | { data: [ ... ], id: "foo" } , // use default bottom 15 | { data: [ ... ], fillBetween: "foo" }, // use first dataset as bottom 16 | ]; 17 | 18 | $.plot($("#placeholder"), dataset, { lines: { show: true, fill: true }}); 19 | 20 | As a convenience, if the id given is a number that doesn't appear as an id in 21 | the series, it is interpreted as the index in the array instead (so fillBetween: 22 | 0 can also mean the first series). 23 | 24 | Internally, the plugin modifies the datapoints in each series. For line series, 25 | extra data points might be inserted through interpolation. Note that at points 26 | where the bottom line is not defined (due to a null point or start/end of line), 27 | the current line will show a gap too. The algorithm comes from the 28 | jquery.flot.stack.js plugin, possibly some code could be shared. 29 | 30 | */(function(e){function n(e){function t(e,t){var n;for(n=0;n=t.length?null:t[e.fillBetween]:null}function n(e,n,r){if(n.fillBetween==null)return;var i=t(n,e.getData());if(!i)return;var s=r.pointsize,o=r.points,u=i.datapoints.pointsize,a=i.datapoints.points,f=[],l,c,h,p,d,v,m=n.lines.show,g=s>2&&r.format[2].y,y=m&&n.lines.steps,b=!0,w=0,E=0,S,x;for(;;){if(w>=o.length)break;S=f.length;if(o[w]==null){for(x=0;x=a.length){if(!m)for(x=0;xp){if(m&&w>0&&o[w-s]!=null){h=c+(o[w-s+1]-c)*(p-l)/(o[w-s]-l),f.push(p),f.push(h);for(x=2;x0&&a[E-u]!=null&&(v=d+(a[E-u+1]-d)*(l-p)/(a[E-u]-p)),w+=s}b=!1,S!==f.length&&g&&(f[S+2]=v)}if(y&&S!==f.length&&S>0&&f[S]!==null&&f[S]!==f[S-s]&&f[S+1]!==f[S-s+1]){for(x=0;x').load(handler).error(handler).attr('src', url); 115 | }); 116 | }; 117 | 118 | function drawSeries(plot, ctx, series) { 119 | var plotOffset = plot.getPlotOffset(); 120 | 121 | if (!series.images || !series.images.show) 122 | return; 123 | 124 | var points = series.datapoints.points, 125 | ps = series.datapoints.pointsize; 126 | 127 | for (var i = 0; i < points.length; i += ps) { 128 | var img = points[i], 129 | x1 = points[i + 1], y1 = points[i + 2], 130 | x2 = points[i + 3], y2 = points[i + 4], 131 | xaxis = series.xaxis, yaxis = series.yaxis, 132 | tmp; 133 | 134 | // actually we should check img.complete, but it 135 | // appears to be a somewhat unreliable indicator in 136 | // IE6 (false even after load event) 137 | if (!img || img.width <= 0 || img.height <= 0) 138 | continue; 139 | 140 | if (x1 > x2) { 141 | tmp = x2; 142 | x2 = x1; 143 | x1 = tmp; 144 | } 145 | if (y1 > y2) { 146 | tmp = y2; 147 | y2 = y1; 148 | y1 = tmp; 149 | } 150 | 151 | // if the anchor is at the center of the pixel, expand the 152 | // image by 1/2 pixel in each direction 153 | if (series.images.anchor == "center") { 154 | tmp = 0.5 * (x2-x1) / (img.width - 1); 155 | x1 -= tmp; 156 | x2 += tmp; 157 | tmp = 0.5 * (y2-y1) / (img.height - 1); 158 | y1 -= tmp; 159 | y2 += tmp; 160 | } 161 | 162 | // clip 163 | if (x1 == x2 || y1 == y2 || 164 | x1 >= xaxis.max || x2 <= xaxis.min || 165 | y1 >= yaxis.max || y2 <= yaxis.min) 166 | continue; 167 | 168 | var sx1 = 0, sy1 = 0, sx2 = img.width, sy2 = img.height; 169 | if (x1 < xaxis.min) { 170 | sx1 += (sx2 - sx1) * (xaxis.min - x1) / (x2 - x1); 171 | x1 = xaxis.min; 172 | } 173 | 174 | if (x2 > xaxis.max) { 175 | sx2 += (sx2 - sx1) * (xaxis.max - x2) / (x2 - x1); 176 | x2 = xaxis.max; 177 | } 178 | 179 | if (y1 < yaxis.min) { 180 | sy2 += (sy1 - sy2) * (yaxis.min - y1) / (y2 - y1); 181 | y1 = yaxis.min; 182 | } 183 | 184 | if (y2 > yaxis.max) { 185 | sy1 += (sy1 - sy2) * (yaxis.max - y2) / (y2 - y1); 186 | y2 = yaxis.max; 187 | } 188 | 189 | x1 = xaxis.p2c(x1); 190 | x2 = xaxis.p2c(x2); 191 | y1 = yaxis.p2c(y1); 192 | y2 = yaxis.p2c(y2); 193 | 194 | // the transformation may have swapped us 195 | if (x1 > x2) { 196 | tmp = x2; 197 | x2 = x1; 198 | x1 = tmp; 199 | } 200 | if (y1 > y2) { 201 | tmp = y2; 202 | y2 = y1; 203 | y1 = tmp; 204 | } 205 | 206 | tmp = ctx.globalAlpha; 207 | ctx.globalAlpha *= series.images.alpha; 208 | ctx.drawImage(img, 209 | sx1, sy1, sx2 - sx1, sy2 - sy1, 210 | x1 + plotOffset.left, y1 + plotOffset.top, 211 | x2 - x1, y2 - y1); 212 | ctx.globalAlpha = tmp; 213 | } 214 | } 215 | 216 | function processRawData(plot, series, data, datapoints) { 217 | if (!series.images.show) 218 | return; 219 | 220 | // format is Image, x1, y1, x2, y2 (opposite corners) 221 | datapoints.format = [ 222 | { required: true }, 223 | { x: true, number: true, required: true }, 224 | { y: true, number: true, required: true }, 225 | { x: true, number: true, required: true }, 226 | { y: true, number: true, required: true } 227 | ]; 228 | } 229 | 230 | function init(plot) { 231 | plot.hooks.processRawData.push(processRawData); 232 | plot.hooks.drawSeries.push(drawSeries); 233 | } 234 | 235 | $.plot.plugins.push({ 236 | init: init, 237 | options: options, 238 | name: 'image', 239 | version: '1.1' 240 | }); 241 | })(jQuery); 242 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/vendor/flot/jquery.flot.image.min.js: -------------------------------------------------------------------------------- 1 | /* Flot plugin for plotting images. 2 | 3 | Copyright (c) 2007-2013 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | The data syntax is [ [ image, x1, y1, x2, y2 ], ... ] where (x1, y1) and 7 | (x2, y2) are where you intend the two opposite corners of the image to end up 8 | in the plot. Image must be a fully loaded Javascript image (you can make one 9 | with new Image()). If the image is not complete, it's skipped when plotting. 10 | 11 | There are two helpers included for retrieving images. The easiest work the way 12 | that you put in URLs instead of images in the data, like this: 13 | 14 | [ "myimage.png", 0, 0, 10, 10 ] 15 | 16 | Then call $.plot.image.loadData( data, options, callback ) where data and 17 | options are the same as you pass in to $.plot. This loads the images, replaces 18 | the URLs in the data with the corresponding images and calls "callback" when 19 | all images are loaded (or failed loading). In the callback, you can then call 20 | $.plot with the data set. See the included example. 21 | 22 | A more low-level helper, $.plot.image.load(urls, callback) is also included. 23 | Given a list of URLs, it calls callback with an object mapping from URL to 24 | Image object when all images are loaded or have failed loading. 25 | 26 | The plugin supports these options: 27 | 28 | series: { 29 | images: { 30 | show: boolean 31 | anchor: "corner" or "center" 32 | alpha: [ 0, 1 ] 33 | } 34 | } 35 | 36 | They can be specified for a specific series: 37 | 38 | $.plot( $("#placeholder"), [{ 39 | data: [ ... ], 40 | images: { ... } 41 | ]) 42 | 43 | Note that because the data format is different from usual data points, you 44 | can't use images with anything else in a specific data series. 45 | 46 | Setting "anchor" to "center" causes the pixels in the image to be anchored at 47 | the corner pixel centers inside of at the pixel corners, effectively letting 48 | half a pixel stick out to each side in the plot. 49 | 50 | A possible future direction could be support for tiling for large images (like 51 | Google Maps). 52 | 53 | */(function(e){function n(e,t,n){var r=e.getPlotOffset();if(!n.images||!n.images.show)return;var i=n.datapoints.points,s=n.datapoints.pointsize;for(var o=0;ol&&(d=l,l=a,a=d),f>c&&(d=c,c=f,f=d),n.images.anchor=="center"&&(d=.5*(l-a)/(u.width-1),a-=d,l+=d,d=.5*(c-f)/(u.height-1),f-=d,c+=d);if(a==l||f==c||a>=h.max||l<=h.min||f>=p.max||c<=p.min)continue;var v=0,m=0,g=u.width,y=u.height;ah.max&&(g+=(g-v)*(h.max-l)/(l-a),l=h.max),fp.max&&(m+=(m-y)*(p.max-c)/(c-f),c=p.max),a=h.p2c(a),l=h.p2c(l),f=p.p2c(f),c=p.p2c(c),a>l&&(d=l,l=a,a=d),f>c&&(d=c,c=f,f=d),d=t.globalAlpha,t.globalAlpha*=n.images.alpha,t.drawImage(u,v,m,g-v,y-m,a+r.left,f+r.top,l-a,c-f),t.globalAlpha=d}}function r(e,t,n,r){if(!t.images.show)return;r.format=[{required:!0},{x:!0,number:!0,required:!0},{y:!0,number:!0,required:!0},{x:!0,number:!0,required:!0},{y:!0,number:!0,required:!0}]}function i(e){e.hooks.processRawData.push(r),e.hooks.drawSeries.push(n)}var t={series:{images:{show:!1,alpha:1,anchor:"corner"}}};e.plot.image={},e.plot.image.loadDataImages=function(t,n,r){var i=[],s=[],o=n.series.images.show;e.each(t,function(t,n){if(!o&&!n.images.show)return;n.data&&(n=n.data),e.each(n,function(e,t){typeof t[0]=="string"&&(i.push(t[0]),s.push(t))})}),e.plot.image.load(i,function(t){e.each(s,function(e,n){var r=n[0];t[r]&&(n[0]=t[r])}),r()})},e.plot.image.load=function(t,n){var r=t.length,i={};r==0&&n({}),e.each(t,function(t,s){var o=function(){--r,i[s]=this,r==0&&n(i)};e("").load(o).error(o).attr("src",s)})},e.plot.plugins.push({init:i,options:t,name:"image",version:"1.1"})})(jQuery); -------------------------------------------------------------------------------- /lib/riemann/dash/public/vendor/flot/jquery.flot.resize.js: -------------------------------------------------------------------------------- 1 | /* Flot plugin for automatically redrawing plots as the placeholder resizes. 2 | 3 | Copyright (c) 2007-2013 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | It works by listening for changes on the placeholder div (through the jQuery 7 | resize event plugin) - if the size changes, it will redraw the plot. 8 | 9 | There are no options. If you need to disable the plugin for some plots, you 10 | can just fix the size of their placeholders. 11 | 12 | */ 13 | 14 | /* Inline dependency: 15 | * jQuery resize event - v1.1 - 3/14/2010 16 | * http://benalman.com/projects/jquery-resize-plugin/ 17 | * 18 | * Copyright (c) 2010 "Cowboy" Ben Alman 19 | * Dual licensed under the MIT and GPL licenses. 20 | * http://benalman.com/about/license/ 21 | */ 22 | 23 | (function($,h,c){var a=$([]),e=$.resize=$.extend($.resize,{}),i,k="setTimeout",j="resize",d=j+"-special-event",b="delay",f="throttleWindow";e[b]=250;e[f]=true;$.event.special[j]={setup:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.add(l);$.data(this,d,{w:l.width(),h:l.height()});if(a.length===1){g()}},teardown:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.not(l);l.removeData(d);if(!a.length){clearTimeout(i)}},add:function(l){if(!e[f]&&this[k]){return false}var n;function m(s,o,p){var q=$(this),r=$.data(this,d);r.w=o!==c?o:q.width();r.h=p!==c?p:q.height();n.apply(this,arguments)}if($.isFunction(l)){n=l;return m}else{n=l.handler;l.handler=m}}};function g(){i=h[k](function(){a.each(function(){var n=$(this),m=n.width(),l=n.height(),o=$.data(this,d);if(m!==o.w||l!==o.h){n.trigger(j,[o.w=m,o.h=l])}});g()},e[b])}})(jQuery,this); 24 | 25 | (function ($) { 26 | var options = { }; // no options 27 | 28 | function init(plot) { 29 | function onResize() { 30 | var placeholder = plot.getPlaceholder(); 31 | 32 | // somebody might have hidden us and we can't plot 33 | // when we don't have the dimensions 34 | if (placeholder.width() == 0 || placeholder.height() == 0) 35 | return; 36 | 37 | plot.resize(); 38 | plot.setupGrid(); 39 | plot.draw(); 40 | } 41 | 42 | function bindEvents(plot, eventHolder) { 43 | plot.getPlaceholder().resize(onResize); 44 | } 45 | 46 | function shutdown(plot, eventHolder) { 47 | plot.getPlaceholder().unbind("resize", onResize); 48 | } 49 | 50 | plot.hooks.bindEvents.push(bindEvents); 51 | plot.hooks.shutdown.push(shutdown); 52 | } 53 | 54 | $.plot.plugins.push({ 55 | init: init, 56 | options: options, 57 | name: 'resize', 58 | version: '1.0' 59 | }); 60 | })(jQuery); 61 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/vendor/flot/jquery.flot.resize.min.js: -------------------------------------------------------------------------------- 1 | /* Flot plugin for automatically redrawing plots as the placeholder resizes. 2 | 3 | Copyright (c) 2007-2013 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | It works by listening for changes on the placeholder div (through the jQuery 7 | resize event plugin) - if the size changes, it will redraw the plot. 8 | 9 | There are no options. If you need to disable the plugin for some plots, you 10 | can just fix the size of their placeholders. 11 | 12 | *//* Inline dependency: 13 | * jQuery resize event - v1.1 - 3/14/2010 14 | * http://benalman.com/projects/jquery-resize-plugin/ 15 | * 16 | * Copyright (c) 2010 "Cowboy" Ben Alman 17 | * Dual licensed under the MIT and GPL licenses. 18 | * http://benalman.com/about/license/ 19 | */(function(e,t,n){function c(){s=t[o](function(){r.each(function(){var t=e(this),n=t.width(),r=t.height(),i=e.data(this,a);(n!==i.w||r!==i.h)&&t.trigger(u,[i.w=n,i.h=r])}),c()},i[f])}var r=e([]),i=e.resize=e.extend(e.resize,{}),s,o="setTimeout",u="resize",a=u+"-special-event",f="delay",l="throttleWindow";i[f]=250,i[l]=!0,e.event.special[u]={setup:function(){if(!i[l]&&this[o])return!1;var t=e(this);r=r.add(t),e.data(this,a,{w:t.width(),h:t.height()}),r.length===1&&c()},teardown:function(){if(!i[l]&&this[o])return!1;var t=e(this);r=r.not(t),t.removeData(a),r.length||clearTimeout(s)},add:function(t){function s(t,i,s){var o=e(this),u=e.data(this,a);u.w=i!==n?i:o.width(),u.h=s!==n?s:o.height(),r.apply(this,arguments)}if(!i[l]&&this[o])return!1;var r;if(e.isFunction(t))return r=t,s;r=t.handler,t.handler=s}}})(jQuery,this),function(e){function n(e){function t(){var t=e.getPlaceholder();if(t.width()==0||t.height()==0)return;e.resize(),e.setupGrid(),e.draw()}function n(e,n){e.getPlaceholder().resize(t)}function r(e,n){e.getPlaceholder().unbind("resize",t)}e.hooks.bindEvents.push(n),e.hooks.shutdown.push(r)}var t={};e.plot.plugins.push({init:n,options:t,name:"resize",version:"1.0"})}(jQuery); -------------------------------------------------------------------------------- /lib/riemann/dash/public/vendor/flot/jquery.flot.selection.min.js: -------------------------------------------------------------------------------- 1 | /* Flot plugin for selecting regions of a plot. 2 | 3 | Copyright (c) 2007-2013 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | The plugin supports these options: 7 | 8 | selection: { 9 | mode: null or "x" or "y" or "xy", 10 | color: color, 11 | shape: "round" or "miter" or "bevel", 12 | minSize: number of pixels 13 | } 14 | 15 | Selection support is enabled by setting the mode to one of "x", "y" or "xy". 16 | In "x" mode, the user will only be able to specify the x range, similarly for 17 | "y" mode. For "xy", the selection becomes a rectangle where both ranges can be 18 | specified. "color" is color of the selection (if you need to change the color 19 | later on, you can get to it with plot.getOptions().selection.color). "shape" 20 | is the shape of the corners of the selection. 21 | 22 | "minSize" is the minimum size a selection can be in pixels. This value can 23 | be customized to determine the smallest size a selection can be and still 24 | have the selection rectangle be displayed. When customizing this value, the 25 | fact that it refers to pixels, not axis units must be taken into account. 26 | Thus, for example, if there is a bar graph in time mode with BarWidth set to 1 27 | minute, setting "minSize" to 1 will not make the minimum selection size 1 28 | minute, but rather 1 pixel. Note also that setting "minSize" to 0 will prevent 29 | "plotunselected" events from being fired when the user clicks the mouse without 30 | dragging. 31 | 32 | When selection support is enabled, a "plotselected" event will be emitted on 33 | the DOM element you passed into the plot function. The event handler gets a 34 | parameter with the ranges selected on the axes, like this: 35 | 36 | placeholder.bind( "plotselected", function( event, ranges ) { 37 | alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to) 38 | // similar for yaxis - with multiple axes, the extra ones are in 39 | // x2axis, x3axis, ... 40 | }); 41 | 42 | The "plotselected" event is only fired when the user has finished making the 43 | selection. A "plotselecting" event is fired during the process with the same 44 | parameters as the "plotselected" event, in case you want to know what's 45 | happening while it's happening, 46 | 47 | A "plotunselected" event with no arguments is emitted when the user clicks the 48 | mouse to remove the selection. As stated above, setting "minSize" to 0 will 49 | destroy this behavior. 50 | 51 | The plugin allso adds the following methods to the plot object: 52 | 53 | - setSelection( ranges, preventEvent ) 54 | 55 | Set the selection rectangle. The passed in ranges is on the same form as 56 | returned in the "plotselected" event. If the selection mode is "x", you 57 | should put in either an xaxis range, if the mode is "y" you need to put in 58 | an yaxis range and both xaxis and yaxis if the selection mode is "xy", like 59 | this: 60 | 61 | setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } }); 62 | 63 | setSelection will trigger the "plotselected" event when called. If you don't 64 | want that to happen, e.g. if you're inside a "plotselected" handler, pass 65 | true as the second parameter. If you are using multiple axes, you can 66 | specify the ranges on any of those, e.g. as x2axis/x3axis/... instead of 67 | xaxis, the plugin picks the first one it sees. 68 | 69 | - clearSelection( preventEvent ) 70 | 71 | Clear the selection rectangle. Pass in true to avoid getting a 72 | "plotunselected" event. 73 | 74 | - getSelection() 75 | 76 | Returns the current selection in the same format as the "plotselected" 77 | event. If there's currently no selection, the function returns null. 78 | 79 | */(function(e){function t(t){function s(e){n.active&&(h(e),t.getPlaceholder().trigger("plotselecting",[a()]))}function o(t){if(t.which!=1)return;document.body.focus(),document.onselectstart!==undefined&&r.onselectstart==null&&(r.onselectstart=document.onselectstart,document.onselectstart=function(){return!1}),document.ondrag!==undefined&&r.ondrag==null&&(r.ondrag=document.ondrag,document.ondrag=function(){return!1}),c(n.first,t),n.active=!0,i=function(e){u(e)},e(document).one("mouseup",i)}function u(e){return i=null,document.onselectstart!==undefined&&(document.onselectstart=r.onselectstart),document.ondrag!==undefined&&(document.ondrag=r.ondrag),n.active=!1,h(e),m()?f():(t.getPlaceholder().trigger("plotunselected",[]),t.getPlaceholder().trigger("plotselecting",[null])),!1}function a(){if(!m())return null;if(!n.show)return null;var r={},i=n.first,s=n.second;return e.each(t.getAxes(),function(e,t){if(t.used){var n=t.c2p(i[t.direction]),o=t.c2p(s[t.direction]);r[e]={from:Math.min(n,o),to:Math.max(n,o)}}}),r}function f(){var e=a();t.getPlaceholder().trigger("plotselected",[e]),e.xaxis&&e.yaxis&&t.getPlaceholder().trigger("selected",[{x1:e.xaxis.from,y1:e.yaxis.from,x2:e.xaxis.to,y2:e.yaxis.to}])}function l(e,t,n){return tn?n:t}function c(e,r){var i=t.getOptions(),s=t.getPlaceholder().offset(),o=t.getPlotOffset();e.x=l(0,r.pageX-s.left-o.left,t.width()),e.y=l(0,r.pageY-s.top-o.top,t.height()),i.selection.mode=="y"&&(e.x=e==n.first?0:t.width()),i.selection.mode=="x"&&(e.y=e==n.first?0:t.height())}function h(e){if(e.pageX==null)return;c(n.second,e),m()?(n.show=!0,t.triggerRedrawOverlay()):p(!0)}function p(e){n.show&&(n.show=!1,t.triggerRedrawOverlay(),e||t.getPlaceholder().trigger("plotunselected",[]))}function d(e,n){var r,i,s,o,u=t.getAxes();for(var a in u){r=u[a];if(r.direction==n){o=n+r.n+"axis",!e[o]&&r.n==1&&(o=n+"axis");if(e[o]){i=e[o].from,s=e[o].to;break}}}e[o]||(r=n=="x"?t.getXAxes()[0]:t.getYAxes()[0],i=e[n+"1"],s=e[n+"2"]);if(i!=null&&s!=null&&i>s){var f=i;i=s,s=f}return{from:i,to:s,axis:r}}function v(e,r){var i,s,o=t.getOptions();o.selection.mode=="y"?(n.first.x=0,n.second.x=t.width()):(s=d(e,"x"),n.first.x=s.axis.p2c(s.from),n.second.x=s.axis.p2c(s.to)),o.selection.mode=="x"?(n.first.y=0,n.second.y=t.height()):(s=d(e,"y"),n.first.y=s.axis.p2c(s.from),n.second.y=s.axis.p2c(s.to)),n.show=!0,t.triggerRedrawOverlay(),!r&&m()&&f()}function m(){var e=t.getOptions().selection.minSize;return Math.abs(n.second.x-n.first.x)>=e&&Math.abs(n.second.y-n.first.y)>=e}var n={first:{x:-1,y:-1},second:{x:-1,y:-1},show:!1,active:!1},r={},i=null;t.clearSelection=p,t.setSelection=v,t.getSelection=a,t.hooks.bindEvents.push(function(e,t){var n=e.getOptions();n.selection.mode!=null&&(t.mousemove(s),t.mousedown(o))}),t.hooks.drawOverlay.push(function(t,r){if(n.show&&m()){var i=t.getPlotOffset(),s=t.getOptions();r.save(),r.translate(i.left,i.top);var o=e.color.parse(s.selection.color);r.strokeStyle=o.scale("a",.8).toString(),r.lineWidth=1,r.lineJoin=s.selection.shape,r.fillStyle=o.scale("a",.4).toString();var u=Math.min(n.first.x,n.second.x)+.5,a=Math.min(n.first.y,n.second.y)+.5,f=Math.abs(n.second.x-n.first.x)-1,l=Math.abs(n.second.y-n.first.y)-1;r.fillRect(u,a,f,l),r.strokeRect(u,a,f,l),r.restore()}}),t.hooks.shutdown.push(function(t,n){n.unbind("mousemove",s),n.unbind("mousedown",o),i&&e(document).unbind("mouseup",i)})}e.plot.plugins.push({init:t,options:{selection:{mode:null,color:"#e8cfac",shape:"round",minSize:5}},name:"selection",version:"1.1"})})(jQuery); -------------------------------------------------------------------------------- /lib/riemann/dash/public/vendor/flot/jquery.flot.stack.js: -------------------------------------------------------------------------------- 1 | /* Flot plugin for stacking data sets rather than overlyaing them. 2 | 3 | Copyright (c) 2007-2013 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | The plugin assumes the data is sorted on x (or y if stacking horizontally). 7 | For line charts, it is assumed that if a line has an undefined gap (from a 8 | null point), then the line above it should have the same gap - insert zeros 9 | instead of "null" if you want another behaviour. This also holds for the start 10 | and end of the chart. Note that stacking a mix of positive and negative values 11 | in most instances doesn't make sense (so it looks weird). 12 | 13 | Two or more series are stacked when their "stack" attribute is set to the same 14 | key (which can be any number or string or just "true"). To specify the default 15 | stack, you can set the stack option like this: 16 | 17 | series: { 18 | stack: null/false, true, or a key (number/string) 19 | } 20 | 21 | You can also specify it for a single series, like this: 22 | 23 | $.plot( $("#placeholder"), [{ 24 | data: [ ... ], 25 | stack: true 26 | }]) 27 | 28 | The stacking order is determined by the order of the data series in the array 29 | (later series end up on top of the previous). 30 | 31 | Internally, the plugin modifies the datapoints in each series, adding an 32 | offset to the y value. For line series, extra data points are inserted through 33 | interpolation. If there's a second y value, it's also adjusted (e.g for bar 34 | charts or filled areas). 35 | 36 | */ 37 | 38 | (function ($) { 39 | var options = { 40 | series: { stack: null } // or number/string 41 | }; 42 | 43 | function init(plot) { 44 | function findMatchingSeries(s, allseries) { 45 | var res = null; 46 | for (var i = 0; i < allseries.length; ++i) { 47 | if (s == allseries[i]) 48 | break; 49 | 50 | if (allseries[i].stack == s.stack) 51 | res = allseries[i]; 52 | } 53 | 54 | return res; 55 | } 56 | 57 | function stackData(plot, s, datapoints) { 58 | if (s.stack == null || s.stack === false) 59 | return; 60 | 61 | var other = findMatchingSeries(s, plot.getData()); 62 | if (!other) 63 | return; 64 | 65 | var ps = datapoints.pointsize, 66 | points = datapoints.points, 67 | otherps = other.datapoints.pointsize, 68 | otherpoints = other.datapoints.points, 69 | newpoints = [], 70 | px, py, intery, qx, qy, bottom, 71 | withlines = s.lines.show, 72 | horizontal = s.bars.horizontal, 73 | withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y), 74 | withsteps = withlines && s.lines.steps, 75 | fromgap = true, 76 | keyOffset = horizontal ? 1 : 0, 77 | accumulateOffset = horizontal ? 0 : 1, 78 | i = 0, j = 0, l, m; 79 | 80 | while (true) { 81 | if (i >= points.length) 82 | break; 83 | 84 | l = newpoints.length; 85 | 86 | if (points[i] == null) { 87 | // copy gaps 88 | for (m = 0; m < ps; ++m) 89 | newpoints.push(points[i + m]); 90 | i += ps; 91 | } 92 | else if (j >= otherpoints.length) { 93 | // for lines, we can't use the rest of the points 94 | if (!withlines) { 95 | for (m = 0; m < ps; ++m) 96 | newpoints.push(points[i + m]); 97 | } 98 | i += ps; 99 | } 100 | else if (otherpoints[j] == null) { 101 | // oops, got a gap 102 | for (m = 0; m < ps; ++m) 103 | newpoints.push(null); 104 | fromgap = true; 105 | j += otherps; 106 | } 107 | else { 108 | // cases where we actually got two points 109 | px = points[i + keyOffset]; 110 | py = points[i + accumulateOffset]; 111 | qx = otherpoints[j + keyOffset]; 112 | qy = otherpoints[j + accumulateOffset]; 113 | bottom = 0; 114 | 115 | if (px == qx) { 116 | for (m = 0; m < ps; ++m) 117 | newpoints.push(points[i + m]); 118 | 119 | newpoints[l + accumulateOffset] += qy; 120 | bottom = qy; 121 | 122 | i += ps; 123 | j += otherps; 124 | } 125 | else if (px > qx) { 126 | // we got past point below, might need to 127 | // insert interpolated extra point 128 | if (withlines && i > 0 && points[i - ps] != null) { 129 | intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px); 130 | newpoints.push(qx); 131 | newpoints.push(intery + qy); 132 | for (m = 2; m < ps; ++m) 133 | newpoints.push(points[i + m]); 134 | bottom = qy; 135 | } 136 | 137 | j += otherps; 138 | } 139 | else { // px < qx 140 | if (fromgap && withlines) { 141 | // if we come from a gap, we just skip this point 142 | i += ps; 143 | continue; 144 | } 145 | 146 | for (m = 0; m < ps; ++m) 147 | newpoints.push(points[i + m]); 148 | 149 | // we might be able to interpolate a point below, 150 | // this can give us a better y 151 | if (withlines && j > 0 && otherpoints[j - otherps] != null) 152 | bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx); 153 | 154 | newpoints[l + accumulateOffset] += bottom; 155 | 156 | i += ps; 157 | } 158 | 159 | fromgap = false; 160 | 161 | if (l != newpoints.length && withbottom) 162 | newpoints[l + 2] += bottom; 163 | } 164 | 165 | // maintain the line steps invariant 166 | if (withsteps && l != newpoints.length && l > 0 167 | && newpoints[l] != null 168 | && newpoints[l] != newpoints[l - ps] 169 | && newpoints[l + 1] != newpoints[l - ps + 1]) { 170 | for (m = 0; m < ps; ++m) 171 | newpoints[l + ps + m] = newpoints[l + m]; 172 | newpoints[l + 1] = newpoints[l - ps + 1]; 173 | } 174 | } 175 | 176 | datapoints.points = newpoints; 177 | } 178 | 179 | plot.hooks.processDatapoints.push(stackData); 180 | } 181 | 182 | $.plot.plugins.push({ 183 | init: init, 184 | options: options, 185 | name: 'stack', 186 | version: '1.2' 187 | }); 188 | })(jQuery); 189 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/vendor/flot/jquery.flot.stack.min.js: -------------------------------------------------------------------------------- 1 | /* Flot plugin for stacking data sets rather than overlyaing them. 2 | 3 | Copyright (c) 2007-2013 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | The plugin assumes the data is sorted on x (or y if stacking horizontally). 7 | For line charts, it is assumed that if a line has an undefined gap (from a 8 | null point), then the line above it should have the same gap - insert zeros 9 | instead of "null" if you want another behaviour. This also holds for the start 10 | and end of the chart. Note that stacking a mix of positive and negative values 11 | in most instances doesn't make sense (so it looks weird). 12 | 13 | Two or more series are stacked when their "stack" attribute is set to the same 14 | key (which can be any number or string or just "true"). To specify the default 15 | stack, you can set the stack option like this: 16 | 17 | series: { 18 | stack: null/false, true, or a key (number/string) 19 | } 20 | 21 | You can also specify it for a single series, like this: 22 | 23 | $.plot( $("#placeholder"), [{ 24 | data: [ ... ], 25 | stack: true 26 | }]) 27 | 28 | The stacking order is determined by the order of the data series in the array 29 | (later series end up on top of the previous). 30 | 31 | Internally, the plugin modifies the datapoints in each series, adding an 32 | offset to the y value. For line series, extra data points are inserted through 33 | interpolation. If there's a second y value, it's also adjusted (e.g for bar 34 | charts or filled areas). 35 | 36 | */(function(e){function n(e){function t(e,t){var n=null;for(var r=0;r2&&(g?r.format[2].x:r.format[2].y),b=m&&n.lines.steps,w=!0,E=g?1:0,S=g?0:1,x=0,T=0,N,C;for(;;){if(x>=o.length)break;N=f.length;if(o[x]==null){for(C=0;C=a.length){if(!m)for(C=0;Cp){if(m&&x>0&&o[x-s]!=null){h=c+(o[x-s+S]-c)*(p-l)/(o[x-s+E]-l),f.push(p),f.push(h+d);for(C=2;C0&&a[T-u]!=null&&(v=d+(a[T-u+S]-d)*(l-p)/(a[T-u+E]-p)),f[N+S]+=v,x+=s}w=!1,N!=f.length&&y&&(f[N+2]+=v)}if(b&&N!=f.length&&N>0&&f[N]!=null&&f[N]!=f[N-s]&&f[N+1]!=f[N-s+1]){for(C=0;C s = r * sqrt(pi)/2 24 | var size = radius * Math.sqrt(Math.PI) / 2; 25 | ctx.rect(x - size, y - size, size + size, size + size); 26 | }, 27 | diamond: function (ctx, x, y, radius, shadow) { 28 | // pi * r^2 = 2s^2 => s = r * sqrt(pi/2) 29 | var size = radius * Math.sqrt(Math.PI / 2); 30 | ctx.moveTo(x - size, y); 31 | ctx.lineTo(x, y - size); 32 | ctx.lineTo(x + size, y); 33 | ctx.lineTo(x, y + size); 34 | ctx.lineTo(x - size, y); 35 | }, 36 | triangle: function (ctx, x, y, radius, shadow) { 37 | // pi * r^2 = 1/2 * s^2 * sin (pi / 3) => s = r * sqrt(2 * pi / sin(pi / 3)) 38 | var size = radius * Math.sqrt(2 * Math.PI / Math.sin(Math.PI / 3)); 39 | var height = size * Math.sin(Math.PI / 3); 40 | ctx.moveTo(x - size/2, y + height/2); 41 | ctx.lineTo(x + size/2, y + height/2); 42 | if (!shadow) { 43 | ctx.lineTo(x, y - height/2); 44 | ctx.lineTo(x - size/2, y + height/2); 45 | } 46 | }, 47 | cross: function (ctx, x, y, radius, shadow) { 48 | // pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2 49 | var size = radius * Math.sqrt(Math.PI) / 2; 50 | ctx.moveTo(x - size, y - size); 51 | ctx.lineTo(x + size, y + size); 52 | ctx.moveTo(x - size, y + size); 53 | ctx.lineTo(x + size, y - size); 54 | } 55 | }; 56 | 57 | var s = series.points.symbol; 58 | if (handlers[s]) 59 | series.points.symbol = handlers[s]; 60 | } 61 | 62 | function init(plot) { 63 | plot.hooks.processDatapoints.push(processRawData); 64 | } 65 | 66 | $.plot.plugins.push({ 67 | init: init, 68 | name: 'symbols', 69 | version: '1.0' 70 | }); 71 | })(jQuery); 72 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/vendor/flot/jquery.flot.symbol.min.js: -------------------------------------------------------------------------------- 1 | /* Flot plugin that adds some extra symbols for plotting points. 2 | 3 | Copyright (c) 2007-2013 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | The symbols are accessed as strings through the standard symbol options: 7 | 8 | series: { 9 | points: { 10 | symbol: "square" // or "diamond", "triangle", "cross" 11 | } 12 | } 13 | 14 | */(function(e){function t(e,t,n){var r={square:function(e,t,n,r,i){var s=r*Math.sqrt(Math.PI)/2;e.rect(t-s,n-s,s+s,s+s)},diamond:function(e,t,n,r,i){var s=r*Math.sqrt(Math.PI/2);e.moveTo(t-s,n),e.lineTo(t,n-s),e.lineTo(t+s,n),e.lineTo(t,n+s),e.lineTo(t-s,n)},triangle:function(e,t,n,r,i){var s=r*Math.sqrt(2*Math.PI/Math.sin(Math.PI/3)),o=s*Math.sin(Math.PI/3);e.moveTo(t-s/2,n+o/2),e.lineTo(t+s/2,n+o/2),i||(e.lineTo(t,n-o/2),e.lineTo(t-s/2,n+o/2))},cross:function(e,t,n,r,i){var s=r*Math.sqrt(Math.PI)/2;e.moveTo(t-s,n-s),e.lineTo(t+s,n+s),e.moveTo(t-s,n+s),e.lineTo(t+s,n-s)}},i=t.points.symbol;r[i]&&(t.points.symbol=r[i])}function n(e){e.hooks.processDatapoints.push(t)}e.plot.plugins.push({init:n,name:"symbols",version:"1.0"})})(jQuery); -------------------------------------------------------------------------------- /lib/riemann/dash/public/vendor/flot/jquery.flot.threshold.js: -------------------------------------------------------------------------------- 1 | /* Flot plugin for thresholding data. 2 | 3 | Copyright (c) 2007-2013 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | The plugin supports these options: 7 | 8 | series: { 9 | threshold: { 10 | below: number 11 | color: colorspec 12 | } 13 | } 14 | 15 | It can also be applied to a single series, like this: 16 | 17 | $.plot( $("#placeholder"), [{ 18 | data: [ ... ], 19 | threshold: { ... } 20 | }]) 21 | 22 | An array can be passed for multiple thresholding, like this: 23 | 24 | threshold: [{ 25 | below: number1 26 | color: color1 27 | },{ 28 | below: number2 29 | color: color2 30 | }] 31 | 32 | These multiple threshold objects can be passed in any order since they are 33 | sorted by the processing function. 34 | 35 | The data points below "below" are drawn with the specified color. This makes 36 | it easy to mark points below 0, e.g. for budget data. 37 | 38 | Internally, the plugin works by splitting the data into two series, above and 39 | below the threshold. The extra series below the threshold will have its label 40 | cleared and the special "originSeries" attribute set to the original series. 41 | You may need to check for this in hover events. 42 | 43 | */ 44 | 45 | (function ($) { 46 | var options = { 47 | series: { threshold: null } // or { below: number, color: color spec} 48 | }; 49 | 50 | function init(plot) { 51 | function thresholdData(plot, s, datapoints, below, color) { 52 | var ps = datapoints.pointsize, i, x, y, p, prevp, 53 | thresholded = $.extend({}, s); // note: shallow copy 54 | 55 | thresholded.datapoints = { points: [], pointsize: ps, format: datapoints.format }; 56 | thresholded.label = null; 57 | thresholded.color = color; 58 | thresholded.threshold = null; 59 | thresholded.originSeries = s; 60 | thresholded.data = []; 61 | 62 | var origpoints = datapoints.points, 63 | addCrossingPoints = s.lines.show; 64 | 65 | var threspoints = []; 66 | var newpoints = []; 67 | var m; 68 | 69 | for (i = 0; i < origpoints.length; i += ps) { 70 | x = origpoints[i]; 71 | y = origpoints[i + 1]; 72 | 73 | prevp = p; 74 | if (y < below) 75 | p = threspoints; 76 | else 77 | p = newpoints; 78 | 79 | if (addCrossingPoints && prevp != p && x != null 80 | && i > 0 && origpoints[i - ps] != null) { 81 | var interx = x + (below - y) * (x - origpoints[i - ps]) / (y - origpoints[i - ps + 1]); 82 | prevp.push(interx); 83 | prevp.push(below); 84 | for (m = 2; m < ps; ++m) 85 | prevp.push(origpoints[i + m]); 86 | 87 | p.push(null); // start new segment 88 | p.push(null); 89 | for (m = 2; m < ps; ++m) 90 | p.push(origpoints[i + m]); 91 | p.push(interx); 92 | p.push(below); 93 | for (m = 2; m < ps; ++m) 94 | p.push(origpoints[i + m]); 95 | } 96 | 97 | p.push(x); 98 | p.push(y); 99 | for (m = 2; m < ps; ++m) 100 | p.push(origpoints[i + m]); 101 | } 102 | 103 | datapoints.points = newpoints; 104 | thresholded.datapoints.points = threspoints; 105 | 106 | if (thresholded.datapoints.points.length > 0) { 107 | var origIndex = $.inArray(s, plot.getData()); 108 | // Insert newly-generated series right after original one (to prevent it from becoming top-most) 109 | plot.getData().splice(origIndex + 1, 0, thresholded); 110 | } 111 | 112 | // FIXME: there are probably some edge cases left in bars 113 | } 114 | 115 | function processThresholds(plot, s, datapoints) { 116 | if (!s.threshold) 117 | return; 118 | 119 | if (s.threshold instanceof Array) { 120 | s.threshold.sort(function(a, b) { 121 | return a.below - b.below; 122 | }); 123 | 124 | $(s.threshold).each(function(i, th) { 125 | thresholdData(plot, s, datapoints, th.below, th.color); 126 | }); 127 | } 128 | else { 129 | thresholdData(plot, s, datapoints, s.threshold.below, s.threshold.color); 130 | } 131 | } 132 | 133 | plot.hooks.processDatapoints.push(processThresholds); 134 | } 135 | 136 | $.plot.plugins.push({ 137 | init: init, 138 | options: options, 139 | name: 'threshold', 140 | version: '1.2' 141 | }); 142 | })(jQuery); 143 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/vendor/flot/jquery.flot.threshold.min.js: -------------------------------------------------------------------------------- 1 | /* Flot plugin for thresholding data. 2 | 3 | Copyright (c) 2007-2013 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | The plugin supports these options: 7 | 8 | series: { 9 | threshold: { 10 | below: number 11 | color: colorspec 12 | } 13 | } 14 | 15 | It can also be applied to a single series, like this: 16 | 17 | $.plot( $("#placeholder"), [{ 18 | data: [ ... ], 19 | threshold: { ... } 20 | }]) 21 | 22 | An array can be passed for multiple thresholding, like this: 23 | 24 | threshold: [{ 25 | below: number1 26 | color: color1 27 | },{ 28 | below: number2 29 | color: color2 30 | }] 31 | 32 | These multiple threshold objects can be passed in any order since they are 33 | sorted by the processing function. 34 | 35 | The data points below "below" are drawn with the specified color. This makes 36 | it easy to mark points below 0, e.g. for budget data. 37 | 38 | Internally, the plugin works by splitting the data into two series, above and 39 | below the threshold. The extra series below the threshold will have its label 40 | cleared and the special "originSeries" attribute set to the original series. 41 | You may need to check for this in hover events. 42 | 43 | */(function(e){function n(t){function n(t,n,r,i,s){var o=r.pointsize,u,a,f,l,c,h=e.extend({},n);h.datapoints={points:[],pointsize:o,format:r.format},h.label=null,h.color=s,h.threshold=null,h.originSeries=n,h.data=[];var p=r.points,d=n.lines.show,v=[],m=[],g;for(u=0;u0&&p[u-o]!=null){var y=a+(i-f)*(a-p[u-o])/(f-p[u-o+1]);c.push(y),c.push(i);for(g=2;g0){var b=e.inArray(n,t.getData());t.getData().splice(b+1,0,h)}}function r(t,r,i){if(!r.threshold)return;r.threshold instanceof Array?(r.threshold.sort(function(e,t){return e.below-t.below}),e(r.threshold).each(function(e,o){n(t,r,i,o.below,o.color)})):n(t,r,i,r.threshold.below,r.threshold.color)}t.hooks.processDatapoints.push(r)}var t={series:{threshold:null}};e.plot.plugins.push({init:n,options:t,name:"threshold",version:"1.2"})})(jQuery); -------------------------------------------------------------------------------- /lib/riemann/dash/public/vendor/flot/jquery.flot.time.min.js: -------------------------------------------------------------------------------- 1 | /* Pretty handling of time axes. 2 | 3 | Copyright (c) 2007-2013 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | Set axis.mode to "time" to enable. See the section "Time series data" in 7 | API.txt for details. 8 | 9 | */(function(e){function n(e,t){return t*Math.floor(e/t)}function r(e,t,n,r){if(typeof e.strftime=="function")return e.strftime(t);var i=function(e,t){return e=""+e,t=""+(t==null?"0":t),e.length==1?t+e:e},s=[],o=!1,u=e.getHours(),a=u<12;n==null&&(n=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]),r==null&&(r=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]);var f;u>12?f=u-12:u==0?f=12:f=u;for(var l=0;l=u)break;var h=l[c][0],p=l[c][1];if(p=="year"){if(i.minTickSize!=null&&i.minTickSize[1]=="year")h=Math.floor(i.minTickSize[0]);else{var d=Math.pow(10,Math.floor(Math.log(e.delta/o.year)/Math.LN10)),v=e.delta/o.year/d;v<1.5?h=1:v<3?h=2:v<7.5?h=5:h=10,h*=d}h<1&&(h=1)}e.tickSize=i.tickSize||[h,p];var m=e.tickSize[0];p=e.tickSize[1];var g=m*o[p];p=="second"?r.setSeconds(n(r.getSeconds(),m)):p=="minute"?r.setMinutes(n(r.getMinutes(),m)):p=="hour"?r.setHours(n(r.getHours(),m)):p=="month"?r.setMonth(n(r.getMonth(),m)):p=="quarter"?r.setMonth(3*n(r.getMonth()/3,m)):p=="year"&&r.setFullYear(n(r.getFullYear(),m)),r.setMilliseconds(0),g>=o.minute&&r.setSeconds(0),g>=o.hour&&r.setMinutes(0),g>=o.day&&r.setHours(0),g>=o.day*4&&r.setDate(1),g>=o.month*2&&r.setMonth(n(r.getMonth(),3)),g>=o.quarter*2&&r.setMonth(n(r.getMonth(),6)),g>=o.year&&r.setMonth(0);var y=0,b=Number.NaN,w;do{w=b,b=r.getTime(),t.push(b);if(p=="month"||p=="quarter")if(m<1){r.setDate(1);var E=r.getTime();r.setMonth(r.getMonth()+(p=="quarter"?3:1));var S=r.getTime();r.setTime(b+y*o.hour+(S-E)*m),y=r.getHours(),r.setHours(0)}else r.setMonth(r.getMonth()+m*(p=="quarter"?3:1));else p=="year"?r.setFullYear(r.getFullYear()+m):r.setTime(b+g)}while(b=Math.min(c,e)&&h.x<=Math.max(c,e)&&h.y>=Math.min(d,f)&&h.y<=Math.max(d,f))){var i=d-f,j=e-c,k=c*f-d*e;return Math.abs(i*a+j*b+k)/Math.sqrt(i*i+j*j)}var l=g(a,b,c,d),m=g(a,b,e,f);return l>m?m:l};if(e)b.showTooltip(e,f.tooltipOptions.snap?e:d);else if(f.plotOptions.series.lines.show&&f.tooltipOptions.lines===!0){var i=f.plotOptions.grid.mouseActiveRadius,j={distance:i+1},k=d;a.each(b.getData(),function(a,c){for(var e=0,i=-1,l=1;l=d.x&&(e=l-1,i=l);if(-1===i)return void b.hideTooltip();var m={x:c.data[e][0],y:c.data[e][1]},n={x:c.data[i][0],y:c.data[i][1]},o=h(c.xaxis.p2c(d.x),c.yaxis.p2c(d.y),c.xaxis.p2c(m.x),c.yaxis.p2c(m.y),c.xaxis.p2c(n.x),c.yaxis.p2c(n.y),!1);if(oh;h++)this.plotPlugins.push(a.plot.plugins[h].name);b.hooks.bindEvents.push(function(b,g){if(f.plotOptions=b.getOptions(),"boolean"==typeof f.plotOptions.tooltip&&(f.plotOptions.tooltipOpts.show=f.plotOptions.tooltip,f.plotOptions.tooltip=f.plotOptions.tooltipOpts,delete f.plotOptions.tooltipOpts),f.plotOptions.tooltip.show!==!1&&"undefined"!=typeof f.plotOptions.tooltip.show){f.tooltipOptions=f.plotOptions.tooltip,f.tooltipOptions.$compat?(f.wfunc="width",f.hfunc="height"):(f.wfunc="innerWidth",f.hfunc="innerHeight");f.getDomElement();a(b.getPlaceholder()).bind("plothover",e),f.tooltipOptions.clickTips&&a(b.getPlaceholder()).bind("plotclick",d),f.clickmode=!1,a(g).bind("mousemove",c)}}),b.hooks.shutdown.push(function(b,f){a(b.getPlaceholder()).unbind("plothover",e),a(b.getPlaceholder()).unbind("plotclick",d),b.removeTooltip(),a(f).unbind("mousemove",c)}),b.setTooltipPosition=function(b){var c=f.getDomElement(),d=c.outerWidth()+f.tooltipOptions.shifts.x,e=c.outerHeight()+f.tooltipOptions.shifts.y;b.x-a(window).scrollLeft()>a(window)[f.wfunc]()-d&&(b.x-=d),b.y-a(window).scrollTop()>a(window)[f.hfunc]()-e&&(b.y-=e),isNaN(b.x)?f.tipPosition.x=f.tipPosition.xPrev:(f.tipPosition.x=b.x,f.tipPosition.xPrev=b.x),isNaN(b.y)?f.tipPosition.y=f.tipPosition.yPrev:(f.tipPosition.y=b.y,f.tipPosition.yPrev=b.y)},b.showTooltip=function(a,c,d){var e=f.getDomElement(),g=f.stringFormat(f.tooltipOptions.content,a);""!==g&&(e.html(g),b.setTooltipPosition({x:c.pageX,y:c.pageY}),e.css({left:f.tipPosition.x+f.tooltipOptions.shifts.x,top:f.tipPosition.y+f.tooltipOptions.shifts.y}).show(),"function"==typeof f.tooltipOptions.onHover&&f.tooltipOptions.onHover(a,e))},b.hideTooltip=function(){f.getDomElement().hide().html("")},b.removeTooltip=function(){f.getDomElement().remove()}},c.prototype.getDomElement=function(){var b=a("."+this.tooltipOptions.cssClass);return 0===b.length&&(b=a("
    ").addClass(this.tooltipOptions.cssClass),b.appendTo("body").hide().css({position:"absolute"}),this.tooltipOptions.defaultTheme&&b.css({background:"#fff","z-index":"1040",padding:"0.4em 0.6em","border-radius":"0.5em","font-size":"0.8em",border:"1px solid #111",display:"none","white-space":"nowrap"})),b},c.prototype.stringFormat=function(a,b){var c,d,e,f,g,h=/%p\.{0,1}(\d{0,})/,i=/%s/,j=/%c/,k=/%lx/,l=/%ly/,m=/%x\.{0,1}(\d{0,})/,n=/%y\.{0,1}(\d{0,})/,o="%x",p="%y",q="%ct",r="%n";if("undefined"!=typeof b.series.threshold?(c=b.datapoint[0],d=b.datapoint[1],e=b.datapoint[2]):"undefined"!=typeof b.series.curvedLines?(c=b.datapoint[0],d=b.datapoint[1]):"undefined"!=typeof b.series.lines&&b.series.lines.steps?(c=b.series.datapoints.points[2*b.dataIndex],d=b.series.datapoints.points[2*b.dataIndex+1],e=""):(c=b.series.data[b.dataIndex][0],d=b.series.data[b.dataIndex][1],e=b.series.data[b.dataIndex][2]),null===b.series.label&&b.series.originSeries&&(b.series.label=b.series.originSeries.label),"function"==typeof a&&(a=a(b.series.label,c,d,b)),"boolean"==typeof a&&!a)return"";if(e&&(a=a.replace(q,e)),"undefined"!=typeof b.series.percent?f=b.series.percent:"undefined"!=typeof b.series.percents&&(f=b.series.percents[b.dataIndex]),"number"==typeof f&&(a=this.adjustValPrecision(h,a,f)),b.series.hasOwnProperty("pie")&&(g=b.series.data[0][1]),"number"==typeof g&&(a=a.replace(r,g)),a="undefined"!=typeof b.series.label?a.replace(i,b.series.label):a.replace(i,""),a="undefined"!=typeof b.series.color?a.replace(j,b.series.color):a.replace(j,""),a=this.hasAxisLabel("xaxis",b)?a.replace(k,b.series.xaxis.options.axisLabel):a.replace(k,""),a=this.hasAxisLabel("yaxis",b)?a.replace(l,b.series.yaxis.options.axisLabel):a.replace(l,""),this.isTimeMode("xaxis",b)&&this.isXDateFormat(b)&&(a=a.replace(m,this.timestampToDate(c,this.tooltipOptions.xDateFormat,b.series.xaxis.options))),this.isTimeMode("yaxis",b)&&this.isYDateFormat(b)&&(a=a.replace(n,this.timestampToDate(d,this.tooltipOptions.yDateFormat,b.series.yaxis.options))),"number"==typeof c&&(a=this.adjustValPrecision(m,a,c)),"number"==typeof d&&(a=this.adjustValPrecision(n,a,d)),"undefined"!=typeof b.series.xaxis.ticks){var s;s=this.hasRotatedXAxisTicks(b)?"rotatedTicks":"ticks";var t=b.dataIndex+b.seriesIndex;for(var u in b.series.xaxis[s])if(b.series.xaxis[s].hasOwnProperty(t)&&!this.isTimeMode("xaxis",b)){var v=this.isCategoriesMode("xaxis",b)?b.series.xaxis[s][t].label:b.series.xaxis[s][t].v;v===c&&(a=a.replace(m,b.series.xaxis[s][t].label.replace(/\$/g,"$$$$")))}}if("undefined"!=typeof b.series.yaxis.ticks)for(var w in b.series.yaxis.ticks)if(b.series.yaxis.ticks.hasOwnProperty(w)){var x=this.isCategoriesMode("yaxis",b)?b.series.yaxis.ticks[w].label:b.series.yaxis.ticks[w].v;x===d&&(a=a.replace(n,b.series.yaxis.ticks[w].label.replace(/\$/g,"$$$$")))}return"undefined"!=typeof b.series.xaxis.tickFormatter&&(a=a.replace(o,b.series.xaxis.tickFormatter(c,b.series.xaxis).replace(/\$/g,"$$"))),"undefined"!=typeof b.series.yaxis.tickFormatter&&(a=a.replace(p,b.series.yaxis.tickFormatter(d,b.series.yaxis).replace(/\$/g,"$$"))),a},c.prototype.isTimeMode=function(a,b){return"undefined"!=typeof b.series[a].options.mode&&"time"===b.series[a].options.mode},c.prototype.isXDateFormat=function(a){return"undefined"!=typeof this.tooltipOptions.xDateFormat&&null!==this.tooltipOptions.xDateFormat},c.prototype.isYDateFormat=function(a){return"undefined"!=typeof this.tooltipOptions.yDateFormat&&null!==this.tooltipOptions.yDateFormat},c.prototype.isCategoriesMode=function(a,b){return"undefined"!=typeof b.series[a].options.mode&&"categories"===b.series[a].options.mode},c.prototype.timestampToDate=function(b,c,d){var e=a.plot.dateGenerator(b,d);return a.plot.formatDate(e,c,this.tooltipOptions.monthNames,this.tooltipOptions.dayNames)},c.prototype.adjustValPrecision=function(a,b,c){var d,e=b.match(a);return null!==e&&""!==RegExp.$1&&(d=RegExp.$1,c=c.toFixed(d),b=b.replace(a,c)),b},c.prototype.hasAxisLabel=function(b,c){return-1!==a.inArray("axisLabels",this.plotPlugins)&&"undefined"!=typeof c.series[b].options.axisLabel&&c.series[b].options.axisLabel.length>0},c.prototype.hasRotatedXAxisTicks=function(b){return-1!==a.inArray("tickRotor",this.plotPlugins)&&"undefined"!=typeof b.series.xaxis.rotatedTicks};var d=function(a){new c(a)};a.plot.plugins.push({init:d,options:b,name:"tooltip",version:"0.8.5"})}(jQuery); -------------------------------------------------------------------------------- /lib/riemann/dash/public/vendor/jquery.gauge.js: -------------------------------------------------------------------------------- 1 | $.fn.gauge = function(opts) { 2 | this.each(function() { 3 | var $this = $(this), 4 | data = $this.data(); 5 | 6 | if (data.gauge) { 7 | data.gauge.stop(); 8 | delete data.gauge; 9 | } 10 | if (opts !== false) { 11 | data.gauge = new Gauge(this).setOptions(opts); 12 | } 13 | }); 14 | return this; 15 | }; 16 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/vendor/jquery/jquery.quickfit.js: -------------------------------------------------------------------------------- 1 | 2 | (function($) { 3 | var Quickfit, QuickfitHelper, defaults, pluginName; 4 | pluginName = 'quickfit'; 5 | defaults = { 6 | min: 8, 7 | max: 12, 8 | tolerance: 0.02, 9 | truncate: false, 10 | width: null, 11 | height: null, 12 | sample_number_of_letters: 10, 13 | sample_font_size: 12, 14 | font_height_scale: 0.85 15 | }; 16 | QuickfitHelper = (function() { 17 | var shared_instance; 18 | 19 | shared_instance = null; 20 | 21 | QuickfitHelper.instance = function(options) { 22 | if (!shared_instance) shared_instance = new QuickfitHelper(options); 23 | return shared_instance; 24 | }; 25 | 26 | function QuickfitHelper(options) { 27 | this.options = options; 28 | this.item = $(''); 29 | this.item.css({ 30 | position: 'absolute', 31 | left: '-1000px', 32 | top: '-1000px', 33 | 'font-size': "" + this.options.sample_font_size + "px" 34 | }); 35 | $('body').append(this.item); 36 | this.meassures = {}; 37 | } 38 | 39 | QuickfitHelper.prototype.get_meassure = function(letter) { 40 | var current_meassure; 41 | current_meassure = this.meassures[letter]; 42 | if (current_meassure === void 0) { 43 | current_meassure = this.set_meassure(letter); 44 | } 45 | return current_meassure; 46 | }; 47 | 48 | QuickfitHelper.prototype.set_meassure = function(letter) { 49 | var current_meassure, index, sample_letter, text, _ref; 50 | text = ''; 51 | sample_letter = letter === ' ' ? ' ' : letter; 52 | for (index = 0, _ref = this.options.sample_number_of_letters - 1; 0 <= _ref ? index <= _ref : index >= _ref; 0 <= _ref ? index++ : index--) { 53 | text += sample_letter; 54 | } 55 | this.item.html(text); 56 | current_meassure = this.item.width() / this.options.sample_number_of_letters / this.options.sample_font_size; 57 | this.meassures[letter] = current_meassure; 58 | return current_meassure; 59 | }; 60 | 61 | return QuickfitHelper; 62 | 63 | })(); 64 | Quickfit = (function() { 65 | 66 | function Quickfit(element, options) { 67 | this.element = element; 68 | this.options = $.extend({}, defaults, options); 69 | this.element = $(this.element); 70 | this._defaults = defaults; 71 | this._name = pluginName; 72 | this.quickfit_helper = QuickfitHelper.instance(this.options); 73 | } 74 | 75 | Quickfit.prototype.fit = function() { 76 | var element_width; 77 | if (!this.options.width) { 78 | element_width = this.element.width(); 79 | this.options.width = element_width - this.options.tolerance * element_width; 80 | } 81 | if (!this.options.height) { 82 | this.options.height = this.element.height(); 83 | } 84 | if (this.text = this.element.attr('data-quickfit')) { 85 | this.previously_truncated = true; 86 | } else { 87 | this.text = this.element.html(); 88 | } 89 | this.calculate_font_size(); 90 | if (this.options.truncate) this.truncate(); 91 | return this.element.css('font-size', "" + this.font_size + "px"); 92 | }; 93 | 94 | Quickfit.prototype.calculate_font_size = function() { 95 | var letter, text_width, _i, _len, _ref; 96 | text_width = 0; 97 | _ref = this.text; 98 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 99 | letter = _ref[_i]; 100 | text_width += this.quickfit_helper.get_meassure(letter); 101 | } 102 | var vertical_font_size = this.options.height * this.options.font_height_scale; 103 | this.target_font_size = parseInt(this.options.width / text_width); 104 | return this.font_size = Math.max( 105 | this.options.min, 106 | Math.min( 107 | vertical_font_size, 108 | this.options.max, 109 | this.target_font_size)); 110 | }; 111 | 112 | Quickfit.prototype.truncate = function() { 113 | var index, last_letter, letter, set_text, text_width; 114 | if (this.font_size > this.target_font_size) { 115 | set_text = ''; 116 | text_width = 3 * this.quickfit_helper.get_meassure('.') * this.font_size; 117 | index = 0; 118 | while (text_width < this.options.width && index < this.text.length) { 119 | letter = this.text[index++]; 120 | if (last_letter) set_text += last_letter; 121 | text_width += this.font_size * this.quickfit_helper.get_meassure(letter); 122 | last_letter = letter; 123 | } 124 | if (set_text.length + 1 === this.text.length) { 125 | set_text = this.text; 126 | } else { 127 | set_text += '...'; 128 | } 129 | this.text_was_truncated = true; 130 | return this.element.attr('data-quickfit', this.text).html(set_text); 131 | } else { 132 | if (this.previously_truncated) return this.element.html(this.text); 133 | } 134 | }; 135 | 136 | return Quickfit; 137 | 138 | })(); 139 | return $.fn.quickfit = function(options) { 140 | return this.each(function() { 141 | return new Quickfit(this, options).fit(); 142 | }); 143 | }; 144 | })(jQuery, window); 145 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/vendor/toastr/toastr.css: -------------------------------------------------------------------------------- 1 | .toast-title 2 | { 3 | font-weight: bold; 4 | } 5 | 6 | .toast-message 7 | { 8 | -ms-word-wrap: break-word; 9 | word-wrap: break-word; 10 | } 11 | 12 | .toast-message a, 13 | .toast-message label 14 | { 15 | color: #FFF; 16 | } 17 | 18 | .toast-message a:hover 19 | { 20 | color: #CCC; 21 | text-decoration: none; 22 | } 23 | 24 | .toast-top-left 25 | { 26 | left: 12px; 27 | top: 12px; 28 | } 29 | 30 | .toast-bottom-right 31 | { 32 | bottom: 12px; 33 | right: 12px; 34 | } 35 | 36 | .toast-bottom-left 37 | { 38 | bottom: 12px; 39 | left: 12px; 40 | } 41 | 42 | #toast-container 43 | { 44 | position: fixed; 45 | z-index: 9999; 46 | } 47 | 48 | #toast-container > div 49 | { 50 | background-position: 15px center; 51 | background-repeat: no-repeat; 52 | -moz-border-radius: 3px 3px 3px 3px; 53 | -webkit-border-radius: 3px 3px 3px 3px; 54 | border-radius: 3px 3px 3px 3px; 55 | -moz-box-shadow: 0 0 12px #999999; 56 | -webkit-box-shadow: 0 0 12px #999999; 57 | box-shadow: 0 0 12px #999999; 58 | color: #FFFFFF; 59 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)"; 60 | filter: alpha(opacity=80); 61 | margin: 0 0 6px; 62 | opacity: 0.8; 63 | padding: 15px 15px 15px 50px; 64 | width: 300px; 65 | } 66 | 67 | .toast 68 | { 69 | background-color: #030303; 70 | } 71 | 72 | .toast-success 73 | { 74 | background-color: #51A351; 75 | } 76 | 77 | .toast-error 78 | { 79 | background-color: #BD362F; 80 | } 81 | 82 | .toast-info 83 | { 84 | background-color: #2F96B4; 85 | } 86 | 87 | .toast-warning 88 | { 89 | background-color: #F89406; 90 | } 91 | 92 | .toast-top-right 93 | { 94 | right: 12px; 95 | top: 38px; 96 | } 97 | 98 | #toast-container > :hover 99 | { 100 | -moz-box-shadow: 0 0 12px #000000; 101 | -webkit-box-shadow: 0 0 12px #000000; 102 | box-shadow: 0 0 12px #000000; 103 | cursor: pointer; 104 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; 105 | filter: alpha(opacity=100); 106 | opacity: 1; 107 | } 108 | 109 | #toast-container > .toast-info 110 | { 111 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=") !important; 112 | } 113 | 114 | #toast-container > .toast-error 115 | { 116 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important; 117 | } 118 | 119 | #toast-container > .toast-success 120 | { 121 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==") !important; 122 | } 123 | 124 | #toast-container > .toast-warning 125 | { 126 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=") !important; 127 | } 128 | 129 | /*Responsive Design*/ 130 | 131 | @media all and (max-width: 240px) 132 | { 133 | #toast-container > div 134 | { 135 | padding: 8px 8px 8px 50px; 136 | width: 108px; 137 | } 138 | } 139 | 140 | @media all and (min-width: 241px) and (max-width: 320px) 141 | { 142 | #toast-container > div 143 | { 144 | padding: 8px 8px 8px 50px; 145 | width: 128px; 146 | } 147 | } 148 | 149 | @media all and (min-width: 321px) and (max-width: 480px) 150 | { 151 | #toast-container > div 152 | { 153 | padding: 8px 8px 8px 50px; 154 | width: 192px; 155 | } 156 | } 157 | 158 | @media all and (min-width: 481px) and (max-width: 768px) 159 | { 160 | #toast-container > div 161 | { 162 | padding: 15px 15px 15px 50px; 163 | width: 300px; 164 | } 165 | } 166 | 167 | @media all and (min-width: 769px) 168 | { 169 | #toast-container > div 170 | { 171 | padding: 15px 15px 15px 50px; 172 | width: 300px; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/views/dial.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var fitopts = {min: 6, max: 1000}; 3 | 4 | var Dial = function(json) { 5 | // Init 6 | view.View.call(this, json); 7 | this.clickFocusable = true; 8 | 9 | // Config 10 | this.query = json.query; 11 | this.title = json.title; 12 | this.commaSeparateThousands = json.commaSeparateThousands; 13 | this.max = json.max; 14 | 15 | // State 16 | this.currentEvent = null; 17 | 18 | // Gauge.js options 19 | this.opts = { 20 | limitMax: true, 21 | }; 22 | 23 | // HTML 24 | this.el.addClass('dial'); 25 | this.el.append( 26 | '
    ' + 27 | '' + 28 | '
    ?
    ' + 29 | '

    ' + 30 | '
    ' 31 | ); 32 | 33 | this.box = this.el.find('.box'); 34 | this.el.find('h2').text(this.title); 35 | 36 | // When clicked, display event 37 | var self = this; 38 | this.box.click(function() { eventPane.show(self.currentEvent) }); 39 | 40 | if (this.query) { 41 | var reflowed = false; 42 | var me = this; 43 | var value = this.el.find('.value'); 44 | var dial = this.el.find('canvas').gauge(me.opts); 45 | var max = null; 46 | 47 | if (me.max) { 48 | max = me.max 49 | dial.data('gauge').maxValue = max; 50 | } 51 | 52 | this.sub = subs.subscribe(this.query, function(e) { 53 | self.currentEvent = e; 54 | me.box.attr('class', 'box state ' + e.state); 55 | 56 | if (e.metric) { 57 | if (!me.max && e.metric > max) { 58 | // Update maximum to highest value encountered so far 59 | max = e.metric; 60 | dial.data('gauge').maxValue = max; 61 | } 62 | dial.data('gauge').set(e.metric); 63 | } 64 | value.text(format.float(e.metric, 2, me.commaSeparateThousands) + 65 | "/" + format.float(max, 2, me.commaSeparateThousands)); 66 | value.attr('title', e.description); 67 | 68 | // The first time, do a full-height reflow. 69 | if (reflowed) { 70 | value.quickfit(fitopts); 71 | } else { 72 | me.reflow(); 73 | reflowed = true; 74 | } 75 | }); 76 | } 77 | } 78 | 79 | view.inherit(view.View, Dial); 80 | view.Dial = Dial; 81 | view.types.Dial = Dial; 82 | 83 | Dial.prototype.json = function() { 84 | return $.extend(view.View.prototype.json.call(this), { 85 | type: 'Dial', 86 | title: this.title, 87 | query: this.query, 88 | commaSeparateThousands: this.commaSeparateThousands, 89 | max: this.max 90 | }); 91 | } 92 | 93 | var editTemplate = _.template( 94 | "" + 95 | "
    " + 96 | "" + 97 | '' + 98 | "" + 99 | "
    " + 100 | "" + 101 | "
    " ); 102 | 103 | Dial.prototype.editForm = function() { 104 | return editTemplate(this); 105 | } 106 | 107 | Dial.prototype.reflow = function() { 108 | // Size metric 109 | var value = this.el.find('.value'); 110 | value.quickfit({min: 6, max: 1000, font_height_scale: 1}); 111 | 112 | // Size title 113 | var title = this.el.find('h2'); 114 | title.quickfit(fitopts); 115 | 116 | // Size canvas, force 1/2 aspect ratio 117 | var canvas = this.el.find('canvas'); 118 | height = Math.floor(canvas.parent().height() - 10); 119 | width = Math.floor(canvas.parent().width() - 10); 120 | if (width > (2 * height)) { 121 | h = height; 122 | w = 2 * height; 123 | } else { 124 | h = width / 2; 125 | w = width; 126 | } 127 | canvas.height(h); 128 | canvas.width(w); 129 | canvas.css({ 130 | "margin-top": "-" + (h / 2) + "px", 131 | "margin-left": "-" + (w / 2) + "px"}); 132 | } 133 | 134 | Dial.prototype.delete = function() { 135 | if (this.sub) { 136 | subs.unsubscribe(this.sub); 137 | } 138 | view.View.prototype.delete.call(this); 139 | } 140 | })(); 141 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/views/gauge.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var fitopts = {min: 6, max: 1000}; 3 | 4 | var Gauge = function(json) { 5 | // Init 6 | view.View.call(this, json); 7 | this.clickFocusable = true; 8 | 9 | // Config 10 | this.query = json.query; 11 | this.title = json.title; 12 | this.commaSeparateThousands = json.commaSeparateThousands; 13 | 14 | // State 15 | this.currentEvent = null; 16 | 17 | // HTML 18 | this.el.addClass('gauge'); 19 | this.el.append( 20 | '
    ' + 21 | '
    ?
    ' + 22 | '

    ' + 23 | '
    ' 24 | ); 25 | 26 | this.box = this.el.find('.box'); 27 | this.el.find('h2').text(this.title); 28 | 29 | // When clicked, display event 30 | var self = this; 31 | this.box.click(function() { eventPane.show(self.currentEvent) }); 32 | 33 | if (this.query) { 34 | var reflowed = false; 35 | var me = this; 36 | var value = this.el.find('.value'); 37 | this.sub = subs.subscribe(this.query, function(e) { 38 | self.currentEvent = e; 39 | me.box.attr('class', 'box state ' + e.state); 40 | if (e.metric != undefined) { 41 | value.text(format.float(e.metric, 2, me.commaSeparateThousands)); 42 | } else if (e.state != undefined) { 43 | value.text(e.state) 44 | } 45 | value.attr('title', e.description); 46 | 47 | // The first time, do a full-height reflow. 48 | if (reflowed) { 49 | value.quickfit(fitopts); 50 | } else { 51 | me.reflow(); 52 | reflowed = true; 53 | } 54 | }); 55 | } 56 | } 57 | 58 | view.inherit(view.View, Gauge); 59 | view.Gauge = Gauge; 60 | view.types.Gauge = Gauge; 61 | 62 | Gauge.prototype.json = function() { 63 | return $.extend(view.View.prototype.json.call(this), { 64 | type: 'Gauge', 65 | title: this.title, 66 | query: this.query, 67 | commaSeparateThousands: this.commaSeparateThousands 68 | }); 69 | } 70 | 71 | var editTemplate = _.template( 72 | "" + 73 | "
    " + 74 | "" + 75 | '' + 76 | "" + 77 | "" ); 78 | 79 | Gauge.prototype.editForm = function() { 80 | return editTemplate(this); 81 | } 82 | 83 | Gauge.prototype.reflow = function() { 84 | // Size metric 85 | var value = this.el.find('.value'); 86 | value.quickfit({min: 6, max: 1000, font_height_scale: 1}); 87 | 88 | // Size title 89 | var title = this.el.find('h2'); 90 | title.quickfit(fitopts); 91 | } 92 | 93 | Gauge.prototype.delete = function() { 94 | if (this.sub) { 95 | subs.unsubscribe(this.sub); 96 | } 97 | view.View.prototype.delete.call(this); 98 | } 99 | })(); 100 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/views/geiger.js: -------------------------------------------------------------------------------- 1 | var GlobalAudioContext = new (window.AudioContext || window.webkitAudioContext)(); 2 | 3 | (function() { 4 | var Geiger = function(json) { 5 | // Init 6 | view.View.call(this, json); 7 | 8 | this.clickFocusable = true; 9 | this.selfid = Math.floor(Math.random() * 255); 10 | 11 | var self = this; 12 | 13 | // Config 14 | this.title = json.title; 15 | this.query = json.query; 16 | this.muted = json.muted || false; 17 | this.volume = parseFloat(json.volume || 0.2); 18 | this.sound = json.sound || 'sounds/geiger.wav'; 19 | this.muted = json.muted || false; 20 | 21 | // State 22 | this.currentEvent = null; 23 | this.shouldPlay = false; 24 | this.soundBuffer = null; 25 | 26 | // HTML 27 | this.el.append( 28 | '
    ' + 29 | '

    ' + 30 | '
    ' + 31 | '
    ' 32 | ); 33 | 34 | this.box = this.el.find('.box'); 35 | this.el.find('h2').text(this.title); 36 | 37 | this.mute_button = this.el.find('.sound-mute'); 38 | this.mute_button.text(this.muted ? 'Unmute' : 'Mute'); 39 | this.mute_button.click(function () { 40 | self.muted = !self.muted; 41 | self.mute_button.text(self.muted ? 'Unmute' : 'Mute'); 42 | }); 43 | 44 | if (!json.virtual) { 45 | // Virtual instance of the geiger counter should not produce any sounds. 46 | // Moreover, to save resources, I will not create audio nodes and will 47 | // not load data. 48 | this.gainer = GlobalAudioContext.createGain(); 49 | 50 | this.gainer.gain.value = Math.pow(this.volume, 2); 51 | this.gainer.connect(GlobalAudioContext.destination); 52 | 53 | this.compressor = GlobalAudioContext.createDynamicsCompressor(); 54 | this.compressor.threshold.value = 0; 55 | this.compressor.knee.value = 0; 56 | this.compressor.ratio.value = 20; 57 | this.compressor.attack.value = 0; 58 | this.compressor.release.value = 2; 59 | this.compressor.connect(this.gainer); 60 | 61 | this.request = new XMLHttpRequest(); 62 | this.request.open('GET', this.sound, true); 63 | this.request.responseType = 'arraybuffer'; 64 | 65 | this.request.onload = function () { 66 | if (this.status != 200) { 67 | toastr.warning ("Could not load sound " + self.sound + ", HTTP status " + this.status); 68 | } 69 | 70 | GlobalAudioContext.decodeAudioData(this.response, function (buffer) { 71 | self.soundBuffer = buffer; 72 | self.shouldPlay = true; 73 | }); 74 | }; 75 | 76 | this.request.onerror = function () { 77 | toastr.warning ("Could not load sound " + self.sound + ", general error"); 78 | } 79 | 80 | this.request.send(); 81 | 82 | if (this.query) { 83 | var me = this; 84 | this.sub = subs.subscribe(this.query, function(e) { 85 | self.playSound(); 86 | self.currentEvent = e; 87 | }); 88 | } 89 | } 90 | } 91 | 92 | view.inherit(view.View, Geiger); 93 | view.Geiger = Geiger; 94 | view.types.Geiger = Geiger; 95 | 96 | Geiger.prototype.json = function() { 97 | return $.extend(view.View.prototype.json.call(this), { 98 | type: 'Geiger', 99 | title: this.title, 100 | query: this.query, 101 | volume: this.volume, 102 | sound: this.sound, 103 | muted: this.muted 104 | }); 105 | } 106 | 107 | Geiger.prototype.playSound = function() { 108 | if (!this.shouldPlay || this.muted) { 109 | return; 110 | } 111 | 112 | source = GlobalAudioContext.createBufferSource(); 113 | source.buffer = this.soundBuffer; 114 | source.connect(this.compressor); 115 | source.start(); 116 | } 117 | 118 | 119 | var editTemplate = _.template( 120 | "" + 121 | "
    " + 122 | "" + 123 | '' + 124 | "" + 125 | "
    " + 126 | "" + 127 | "" ); 128 | 129 | Geiger.prototype.editForm = function() { 130 | return editTemplate(this); 131 | } 132 | 133 | Geiger.prototype.shutdownSound = function() { 134 | this.shouldPlay = false; 135 | this.gainer.disconnect(); 136 | this.compressor.disconnect(); 137 | this.gainer = null; 138 | this.compressor = null; 139 | 140 | this.soundBuffer = null; 141 | } 142 | 143 | Geiger.prototype.delete = function() { 144 | if (this.sub) { 145 | subs.unsubscribe(this.sub); 146 | } 147 | 148 | this.shutdownSound(); 149 | return view.View.prototype.delete.call(this); 150 | } 151 | })(); 152 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/views/help.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var Help = function(json) { 3 | view.View.call(this, json); 4 | this.clickFocusable = true; 5 | this.el.addClass("help"); 6 | this.el.append('
    ' + 7 | "

    Welcome to Riemann-Dash.

    " + 8 | "

    Need a refresher on the query language? See the query tests for examples, or read the spec.

    " + 9 | "

    Double-click a workspace to rename it.

    " + 10 | "

    Press Control/Meta+click to select a view (Option+Command+click on a Mac). Escape unfocuses. Use the arrow keys to move a view. Use Control+arrow to split a view in the given direction.

    " + 11 | "

    To edit a view, hit e. Use enter, or click 'apply', to apply your changes. Escape cancels.

    " + 12 | "

    To save your changes to the server, press s. To display the configuration, press w.

    " + 13 | "

    You can refresh the page, or press r to reload.

    " + 14 | "

    Make views bigger and smaller with the +/- keys. Pageup selects the parent of the current view. To delete a view, use the delete key or press d.

    " + 15 | "

    Switch between workspaces with alt-1, alt-2, etc.

    " + 16 | "

    View is an empty space. Title is an editable text title. Fullscreen and Balloon are top-level container views; you probably won't use them. HStack and VStack are the horizontal and vertical container views; they're implicitly created by splits, but you can create them yourself for fine control. Gauge shows a single event. Grid shows a table of events. Timeseries and Flot show metrics over time--Timeseries is deprecated; Flot will probably replace it.

    " + 17 | "

    My sincere apologies for layout jankiness. There are a few gross bugs and incapabilities in the current hstack/vstack system; if things get really bad, you can always edit ws/config.json on the server. The control scheme will probably change; I appreciate your ideas and patches.

    " + 18 | '
    ' 19 | ); 20 | } 21 | 22 | view.inherit(view.View, Help); 23 | view.Help = Help; 24 | view.types.Help = Help; 25 | 26 | Help.prototype.json = function() { 27 | return { 28 | type: 'Help', 29 | title: this.title 30 | }; 31 | } 32 | })(); 33 | -------------------------------------------------------------------------------- /lib/riemann/dash/public/views/iframe.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var iframe = function(json) { 3 | view.View.call(this, json); 4 | this.title = json.title; 5 | this.url = json.url; 6 | this.clickFocusable = true; 7 | this.el.addClass("iframe"); 8 | this.h2 = $('

    '); 9 | this.el.append(this.h2); 10 | this.h2.text(this.title); 11 | this.iframe = $('