├── .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 | '
{{-description}}'); 18 | 19 | var rowTemplate = 20 | _.template('
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 | 'host | ' + 17 | 'service | ' + 18 | 'state | ' + 19 | 'metric | ' + 20 | 'description | ' + 21 | '
---|