├── .rspec ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Procfile ├── README.md ├── Rakefile ├── config.ru ├── dashboards └── .gitignore ├── lib └── tasseo │ ├── public │ ├── c │ │ └── style.css │ ├── i │ │ ├── spin-night.gif │ │ ├── spin.gif │ │ ├── tasseo.png │ │ ├── toggle-night.png │ │ └── toggle-number.png │ └── j │ │ ├── crypto-min.js │ │ ├── d3.v2.min.js │ │ ├── rickshaw.min.js │ │ └── tasseo.js │ ├── views │ └── index.haml │ └── web.rb └── spec ├── spec_helper.rb └── web_spec.rb /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format progress 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gem "foreman" 4 | gem "sinatra" 5 | gem "thin", "~> 1.5.0" 6 | gem "rack", ">= 2.1.4" 7 | gem "rack-ssl-enforcer" 8 | gem "haml" 9 | gem "json", ">= 2.3.0" 10 | gem 'sinatra_auth_github' 11 | 12 | group :development do 13 | gem 'rack-test' 14 | gem 'rake' 15 | gem 'rspec' 16 | end 17 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | activesupport (7.0.1) 5 | concurrent-ruby (~> 1.0, >= 1.0.2) 6 | i18n (>= 1.6, < 2) 7 | minitest (>= 5.1) 8 | tzinfo (~> 2.0) 9 | addressable (2.8.0) 10 | public_suffix (>= 2.0.2, < 5.0) 11 | concurrent-ruby (1.1.9) 12 | daemons (1.4.1) 13 | diff-lcs (1.5.0) 14 | eventmachine (1.2.7) 15 | faraday (1.9.3) 16 | faraday-em_http (~> 1.0) 17 | faraday-em_synchrony (~> 1.0) 18 | faraday-excon (~> 1.1) 19 | faraday-httpclient (~> 1.0) 20 | faraday-multipart (~> 1.0) 21 | faraday-net_http (~> 1.0) 22 | faraday-net_http_persistent (~> 1.0) 23 | faraday-patron (~> 1.0) 24 | faraday-rack (~> 1.0) 25 | faraday-retry (~> 1.0) 26 | ruby2_keywords (>= 0.0.4) 27 | faraday-em_http (1.0.0) 28 | faraday-em_synchrony (1.0.0) 29 | faraday-excon (1.1.0) 30 | faraday-httpclient (1.0.1) 31 | faraday-multipart (1.0.3) 32 | multipart-post (>= 1.2, < 3) 33 | faraday-net_http (1.0.1) 34 | faraday-net_http_persistent (1.2.0) 35 | faraday-patron (1.0.0) 36 | faraday-rack (1.0.0) 37 | faraday-retry (1.0.3) 38 | foreman (0.87.2) 39 | haml (5.2.2) 40 | temple (>= 0.8.0) 41 | tilt 42 | i18n (1.8.11) 43 | concurrent-ruby (~> 1.0) 44 | json (2.6.1) 45 | minitest (5.15.0) 46 | multipart-post (2.1.1) 47 | mustermann (1.1.1) 48 | ruby2_keywords (~> 0.0.1) 49 | octokit (4.22.0) 50 | faraday (>= 0.9) 51 | sawyer (~> 0.8.0, >= 0.5.3) 52 | public_suffix (4.0.6) 53 | rack (2.2.3.1) 54 | rack-protection (2.2.0) 55 | rack 56 | rack-ssl-enforcer (0.2.9) 57 | rack-test (1.1.0) 58 | rack (>= 1.0, < 3) 59 | rake (13.0.6) 60 | rspec (3.10.0) 61 | rspec-core (~> 3.10.0) 62 | rspec-expectations (~> 3.10.0) 63 | rspec-mocks (~> 3.10.0) 64 | rspec-core (3.10.1) 65 | rspec-support (~> 3.10.0) 66 | rspec-expectations (3.10.2) 67 | diff-lcs (>= 1.2.0, < 2.0) 68 | rspec-support (~> 3.10.0) 69 | rspec-mocks (3.10.2) 70 | diff-lcs (>= 1.2.0, < 2.0) 71 | rspec-support (~> 3.10.0) 72 | rspec-support (3.10.3) 73 | ruby2_keywords (0.0.5) 74 | sawyer (0.8.2) 75 | addressable (>= 2.3.5) 76 | faraday (> 0.8, < 2.0) 77 | sinatra (2.2.0) 78 | mustermann (~> 1.0) 79 | rack (~> 2.2) 80 | rack-protection (= 2.2.0) 81 | tilt (~> 2.0) 82 | sinatra_auth_github (2.0.0) 83 | sinatra (~> 2.0) 84 | warden-github (~> 1.3) 85 | temple (0.8.2) 86 | thin (1.5.1) 87 | daemons (>= 1.0.9) 88 | eventmachine (>= 0.12.6) 89 | rack (>= 1.0.0) 90 | tilt (2.0.10) 91 | tzinfo (2.0.4) 92 | concurrent-ruby (~> 1.0) 93 | warden (1.2.9) 94 | rack (>= 2.0.9) 95 | warden-github (1.3.2) 96 | activesupport (> 3.0) 97 | octokit (> 2.1.0) 98 | warden (> 1.0) 99 | 100 | PLATFORMS 101 | ruby 102 | 103 | DEPENDENCIES 104 | foreman 105 | haml 106 | json (>= 2.3.0) 107 | rack (>= 2.1.4) 108 | rack-ssl-enforcer 109 | rack-test 110 | rake 111 | rspec 112 | sinatra 113 | sinatra_auth_github 114 | thin (~> 1.5.0) 115 | 116 | BUNDLED WITH 117 | 2.2.33 118 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Jason Dixon 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * The name Jason Dixon may not be used to endorse or promote products 15 | derived from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL JASON DIXON BE LIABLE FOR ANY DIRECT, INDIRECT, 21 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 26 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec rackup -I lib -p $PORT -s thin 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tasseo 2 | 3 | [![Build Status](https://secure.travis-ci.org/obfuscurity/tasseo.png)](http://travis-ci.org/obfuscurity/tasseo) 4 | 5 | Reading the tea leaves. 6 | 7 | ![graph](https://github.com/obfuscurity/tasseo/raw/master/lib/tasseo/public/i/tasseo.png "Tasseo Dashboard") 8 | 9 | ## Overview 10 | 11 | Tasseo is a lightweight, easily configurable, near-realtime dashboard for time-series metrics. Charts are refreshed every two seconds and provide a heads-up view of the most current value. 12 | 13 | The default behavior is designed for a retention policy with a 1-second resolution for at least 5 minutes, although this can be modified within the dashboard and metric attributes. 14 | 15 | Tasseo was originally designed for the Graphite TSDB, but has since been extended to support InfluxDB, Librato Metrics, and Amazon CloudWatch backend sources. 16 | 17 | ## Configuration 18 | 19 | ### Examples 20 | 21 | Creating your own dashboard is as simple as dropping a JSON file into the `dashboards` directory, committing it, and pushing the code to a Heroku app. The name of your file (minus the `.js` suffix) becomes the name of your dashboard. Here's an example configuration that you could put in e.g. `dashboards/example.js`: 22 | 23 | ```json 24 | var metrics = 25 | [ 26 | { 27 | "alias": "pulse-events-per-second", 28 | "target": "pulse.pulse-events-per-second", 29 | "warning": 100, 30 | "critical": 500 31 | } 32 | ]; 33 | ``` 34 | 35 | The `target` attribute is the only mandatory field. As you might expect, each dashboard can contain an arbitrary list of different Graphite metrics. Another perfectly valid example, this time including the dashboard-level attribute `period`: 36 | 37 | ```json 38 | var period = 3; 39 | var metrics = 40 | [ 41 | { "target": "pulse.hermes-econns-apps-per-minute" }, 42 | { "target": "pulse.hermes-econns-per-minute" }, 43 | { "target": "pulse.hermes-elevated-route-lookups-per-minute" }, 44 | { "target": "pulse.hermes-errors-per-minute" }, 45 | { "target": "pulse.hermes-h10-per-minute" }, 46 | { "target": "pulse.hermes-h11-per-minute" }, 47 | { "target": "pulse.hermes-h12-per-minute" }, 48 | { "target": "pulse.hermes-h13-per-minute" }, 49 | { "target": "pulse.hermes-h14-per-minute" }, 50 | { "target": "pulse.hermes-h18-per-minute" }, 51 | { "target": "pulse.hermes-h99-per-minute" } 52 | ]; 53 | ``` 54 | 55 | As an alternative to static dashboard layouts, it's possible to use a `false` target to _pad_ cells on the dashboard grid. Because metrics are read in a predictable manner from their respective `.js` files, this provides a mechanism for organizing an otherwise uncontrollable layout. 56 | 57 | 58 | ```json 59 | var metrics = 60 | [ 61 | { "target": "foo" }, 62 | { "target": false }, 63 | { "target": "bar" } 64 | ]; 65 | ``` 66 | 67 | ### Thresholds 68 | 69 | `warning` and `critical` thresholds are optional. If defined, the color of the graph will change when the current value exceeds the respective threshold. If the thresholds are reversed (i.e. `critical` is lower than `warning`), Tasseo understands that an inverse threshold is expected. 70 | 71 | ### Dashboard Attributes 72 | 73 | Dashboard-level attributes are top-level variables defined in your dashboard configuration. 74 | 75 | * period - Range (in minutes) of data to query from Graphite. (optional, defaults to _5_) 76 | * refresh - Refresh interval for charts, in milliseconds. (optional, defaults to _2000_) 77 | * theme - Default theme for dashboard. Currently the only option is `dark`. (optional) 78 | * padnulls - Determines whether to pad null values or not. (optional, defaults to _true_) 79 | * title - Dictates whether the dashboard title is shown or not. (optional, defaults to _true_) 80 | * toolbar - Dictates whether the toolbar is shown or not. (optional, defaults to _true_) 81 | * normalColor - Set normal graph color. (optional, defaults to `#afdab1`) 82 | * criticalColor - Set `critical` graph color. (optional, defaults to `#d59295`) 83 | * warningColor - Set `warning` graph color. (optional, defaults to `#f5cb56`) 84 | * interpolation - Line smoothing method supported by D3. (optional, defaults to _step-after_) 85 | * renderer - Rendering method supported by D3. (optional, defaults to _area_) 86 | * stroke - Dictates whether stroke outline is shown or not. (optional, defaults to _true_) 87 | 88 | ### Metric Attributes 89 | 90 | Metric-level attributes are attributes of the metric object(s) in your `metrics` array. 91 | 92 | * alias - Short name for the metric. (optional) 93 | * target - Full target name as used by Graphite. Can contain a combination of chained functions. (mandatory) 94 | * description - Text description or comment. (optional) 95 | * link - External link to apply to metric name or alias. (optional) 96 | * warning - Warning threshold. Exceeding this value causes the graph to turn yellow. (optional) 97 | * critical - Critical threshold. Exceeding this value causes the graph to turn red. (optional) 98 | * unit - Arbitrary string that can be used to designate a unit value; for example, "Mbps". (optional) 99 | * series - Name of the InfluxDB series that each target belongs to. (mandatory for InfluxDB) 100 | * transform - A function that takes the value and returns a transformed value. (optional) 101 | * scale - Use a dynamic y-axis scale rather than defaulting to zero min. (optional) 102 | * where - A `where` clause to pass to InfluxDB. (optional for InfluxDB) 103 | * Amazon CloudWatch specific fields which are documented [here](http://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_GetMetricStatistics.html) and are discussed in the *Amazon CloudWatch* section below 104 | * Namespace, MetricName, Dimensions, Statistics, EndTime, StartTime, Period, Unit 105 | 106 | ## Deployment 107 | 108 | The only required environment variable is `GRAPHITE_URL`. This should be set to the base URL of your Graphite composer (e.g. `https://graphite.yourdomain.com`). If your server requires Basic Auth, you can set the `GRAPHITE_AUTH` variable (e.g. `username:password`). 109 | 110 | ### Local 111 | 112 | ```bash 113 | $ rvm use 1.9.2 114 | $ bundle install 115 | $ export GRAPHITE_URL=... 116 | $ export GRAPHITE_AUTH=... # e.g. username:password (optional) 117 | $ foreman start 118 | $ open http://127.0.0.1:5000 119 | ``` 120 | 121 | ### Heroku 122 | 123 | ```bash 124 | $ export DEPLOY=production/staging/you 125 | $ heroku create -r $DEPLOY -s cedar tasseo-$DEPLOY 126 | $ heroku config:set -r $DEPLOY GRAPHITE_URL=... 127 | $ heroku config:set -r $DEPLOY GRAPHITE_AUTH=... 128 | $ git push $DEPLOY master 129 | $ heroku scale -r $DEPLOY web=1 130 | $ heroku open -r $DEPLOY 131 | ``` 132 | 133 | ## Graphite Server Configuration 134 | 135 | In order to support CORS with JSON instead of JSONP, we need to allow specific headers and allow the cross-domain origin request. The following are suggested settings for Apache 2.x. Adjust as necessary for your environment or webserver. 136 | 137 | ``` 138 | Header set Access-Control-Allow-Origin "*" 139 | Header set Access-Control-Allow-Methods "GET, OPTIONS" 140 | Header set Access-Control-Allow-Headers "origin, authorization, accept" 141 | ``` 142 | 143 | If your Graphite composer is protected by basic authentication, you have to ensure that the HTTP verb OPTIONS is allowed unauthenticated. This looks like the following for Apache: 144 | ``` 145 | 146 | AuthName "graphs restricted" 147 | AuthType Basic 148 | AuthUserFile /etc/apache2/htpasswd 149 | 150 | require valid-user 151 | 152 | 153 | ``` 154 | 155 | See http://blog.rogeriopvl.com/archives/nginx-and-the-http-options-method/ for an Nginx example. 156 | 157 | ## Alternate Backends 158 | 159 | ### Librato Metrics 160 | 161 | Tasseo can be configured to fetch metrics from [Librato Metrics](https://metrics.librato.com/) 162 | instead of Graphite by setting the `LIBRATO_AUTH` environment variable instead of `GRAPHITE_AUTH`. 163 | 164 | The format of this variable is: 165 | 166 | ``` 167 | LIBRATO_AUTH=: 168 | ``` 169 | 170 | By default, all sources for a metric are aggregated. To limit to a specific 171 | source, specify the `source:` option when defining a metric. For instance, to 172 | limit to the "web1" source: 173 | 174 | ``` 175 | { 176 | target: "fetch.timer", 177 | source: "web1" 178 | } 179 | ``` 180 | 181 | If you are sending data less frequently than 1 second, you should adjust the 182 | `period=` and `refresh=` configuration settings accordingly. 183 | 184 | For instance, if you were sending metrics every 60 seconds, this could be sufficient: 185 | 186 | ``` 187 | var period = 60; 188 | var refresh = 30000; 189 | ``` 190 | 191 | ### InfluxDB 192 | 193 | Tasseo can also be configured to fetch metrics from an [InfluxDB](http://influxdb.org/) server. The necessary environment variables are `INFLUXDB_URL` and `INFLUXDB_AUTH`. Within the configuration, each target must also contain a `series` attribute. 194 | 195 | The formats of these variables are: 196 | 197 | ``` 198 | INFLUXDB_URL=http://sandbox.influxdb.org:8086 199 | INFLUXDB_AUTH=: 200 | ``` 201 | 202 | Sample configuration: 203 | 204 | ``` 205 | var metrics = 206 | [ 207 | { 208 | target: "available", 209 | series: "disk_usage", 210 | transform: function(value) { 211 | // metric is logged in MB but we want to display GB 212 | return value / 1024; 213 | }, 214 | // minimum y axis value will equal minimum metric y value (instead of 0) 215 | scale: true, 216 | db: "points" 217 | } 218 | ] 219 | ``` 220 | 221 | Is equivalent to the InfluxDB query `select available from disk_usage`. 222 | 223 | ### Amazon CloudWatch 224 | 225 | Tasseo can be configured to fetch metrics from [Amazon CloudWatch](http://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/Welcome.html) 226 | instead of Graphite by setting the `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_REGION` environment variables instead of `GRAPHITE_AUTH`. **As warned [here](http://docs.aws.amazon.com/AWSJavaScriptSDK/guide/browser-configuring.html)**, only use AWS IAM code credentials that have read only access to specific resources. These environment variables are used on the client and may be downloaded by anyone who happens to browse to your deployed dashboard. In addition, you will need to write `var usingCloudWatch = true;` in the metric configuration file. 227 | 228 | By default, metric values are aggregated, come in 1 minute segments (CloudWatch's minimum), and span the default Tasseo 5 minute period (these correspond to the fields: "Statistics", "Period", and "EndTime"/"StartTime"). These fields are documented further [here](http://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_GetMetricStatistics.html). The fields "Namespace", "MetricName", "Dimensions", must be specified by the user. Although a target is required to be present, its value is irrelevant. An example for getting the Put latency off of a Kinesis Stream: 229 | 230 | ``` 231 | { 232 | 'target': '', 233 | 'Namespace': 'AWS/Kinesis', 234 | 'MetricName': 'PutRecord.Latency', 235 | 'Dimensions': [ 236 | { 237 | 'Name': 'StreamName', 238 | 'Value': 'what-i-named-my-stream' 239 | } 240 | ] 241 | } 242 | ``` 243 | 244 | To view data on a bigger window, you should adjust the 245 | `period=` configuration variable accordingly. `period`, given in minutes, will affect the window set by "StartTime" and "EndTime". You can override any of the CloudWatch settings from your metric JSON. 246 | 247 | For instance, if you wanted to see metrics for the last hour, and have them refresh every minute, this could be sufficient: 248 | 249 | ``` 250 | var period = 60; // 60 minutes 251 | var refresh = 1 * 60 * 1000; // 1 minute 252 | ``` 253 | 254 | To get an idea of what values for "Namespace", "MetricName", "Dimensions" are necessary for your purposes, consult your [CloudWatch dashboard](https://console.aws.amazon.com/cloudwatch/home#metrics:) or browse the response of the [listMetrics](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/frames.html#\!AWS/CloudWatch.html) API. 255 | 256 | 257 | ## GitHub Authentication 258 | 259 | To authenticate against a GitHub organization, set the following environment variables: 260 | 261 | ```bash 262 | $ export GITHUB_CLIENT_ID= 263 | $ export GITHUB_CLIENT_SECRET= 264 | $ export GITHUB_AUTH_ORGANIZATION= 265 | ``` 266 | 267 | To register an OAuth application, go here: https://github.com/settings/applications 268 | 269 | 270 | ## License 271 | 272 | Tasseo is distributed under a 3-clause BSD license. Third-party software libraries included with this project are distributed under their respective licenses. 273 | 274 | * d3.js - [3-clause BSD](https://github.com/mbostock/d3/blob/master/LICENSE) 275 | * Rickshaw - [MIT](https://github.com/shutterstock/rickshaw) 276 | * underscore.js - [MIT](https://github.com/jashkenas/underscore/blob/master/LICENSE) 277 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rspec/core/rake_task' 2 | 3 | RSpec::Core::RakeTask.new(:spec) do |spec| 4 | spec.ruby_opts = '-I .' 5 | end 6 | 7 | task :default => :spec 8 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | $LOAD_PATH << './lib' 2 | require "tasseo/web" 3 | 4 | run Tasseo::Web 5 | -------------------------------------------------------------------------------- /dashboards/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | -------------------------------------------------------------------------------- /lib/tasseo/public/c/style.css: -------------------------------------------------------------------------------- 1 | 2 | * { 3 | font-family: Helvetica; 4 | } 5 | 6 | body.night { 7 | background-color: #000; 8 | } 9 | 10 | div.title { 11 | margin: 50px 8% 0px; 12 | } 13 | 14 | .title span { 15 | font-size: 3.0em; 16 | font-weight: bold; 17 | } 18 | 19 | .night .title span { 20 | color: #fff; 21 | opacity: 0.9; 22 | } 23 | 24 | .title span select { 25 | font-size: 0.4em; 26 | min-width: 200px; 27 | } 28 | 29 | .toolbar { 30 | right: 0; 31 | float: right; 32 | } 33 | 34 | .title ul { 35 | list-style-type: none; 36 | } 37 | 38 | .title ul.timepanel { 39 | float: left; 40 | } 41 | 42 | .title ul.toggle { 43 | float: right; 44 | } 45 | 46 | .title ul li { 47 | float: left; 48 | } 49 | 50 | .title ul li.timepanel { 51 | padding: 10px 0; 52 | width: 90px; 53 | text-align: center; 54 | background-color: #999; 55 | } 56 | 57 | .title ul li.timepanel.live { 58 | width: 120px; 59 | } 60 | 61 | .title ul li.timepanel.night { 62 | background-color: #333; 63 | } 64 | 65 | .title ul li.timepanel.selected { 66 | background-color: #333; 67 | color: #fff; 68 | } 69 | 70 | .title ul li.timepanel.selected.night { 71 | background-color: #999; 72 | } 73 | 74 | .title ul li.timepanel a { 75 | font-size: 18px; 76 | font-weight: bold; 77 | color: #fff; 78 | text-decoration: none; 79 | } 80 | 81 | .title ul li.toggle-nonum, .title ul li.toggle-night { 82 | margin-left: 20px; 83 | } 84 | 85 | .title ul li a img { 86 | opacity: 0.8; 87 | } 88 | 89 | .title p { 90 | margin-left: 10px; 91 | font-size: 20px; 92 | color: #999; 93 | } 94 | 95 | div.main { 96 | margin: 30px 8% 0px; 97 | text-align: center; 98 | } 99 | 100 | div.nav { 101 | margin: 30px 8% 0px; 102 | } 103 | 104 | .nav ul { 105 | margin-left: 10px; 106 | list-style-type: none; 107 | padding: 0; 108 | } 109 | 110 | .nav li { 111 | padding-bottom: 3px; 112 | } 113 | 114 | .nav li a { 115 | font-size: 20px; 116 | text-decoration: none; 117 | color: #999; 118 | } 119 | 120 | .nav li a:hover { 121 | color: red; 122 | } 123 | 124 | .graph, .false { 125 | position: relative; 126 | float: left; 127 | padding: 10px; 128 | margin: 10px; 129 | } 130 | 131 | .false { 132 | width: 350px; 133 | height: 104px; 134 | } 135 | 136 | .graph a { 137 | color: #000; 138 | } 139 | 140 | .graph svg { 141 | border-left: 1px dotted #ddd; 142 | border-right: 1px dotted #ddd; 143 | } 144 | 145 | .night .graph svg { 146 | border-left: 1px dotted #333; 147 | border-right: 1px dotted #333; 148 | } 149 | 150 | span.description { 151 | visibility: hidden; 152 | position: absolute; 153 | display: block; 154 | z-index: 300; 155 | min-width: 300px; 156 | min-height: 50px; 157 | margin: -50px 0 0 -20px; 158 | background-color: rgba(255, 255, 255, 0.85); 159 | -moz-border-radius: 10px; 160 | -webkit-border-radius: 10px; 161 | border-radius: 10px; 162 | border: 3px solid #cecccc; 163 | -moz-box-shadow: 5px 5px 15px 2px rgba(134, 134, 134, 0.76); 164 | -webkit-box-shadow: 5px 5px 15px 2px rgba(134, 134, 134, 0.76); 165 | box-shadow: 5px 5px 15px 2px rgba(134, 134, 134, 0.76); 166 | padding: 15px; 167 | text-align: left; 168 | } 169 | 170 | .night span.description { 171 | border: 0; 172 | } 173 | 174 | span.description:hover { 175 | visibility: visible; 176 | } 177 | 178 | .overlay-name { 179 | position: absolute; 180 | font-size: 24px; 181 | text-align: left; 182 | width: 150px; 183 | left: 20px; 184 | bottom: 20px; 185 | z-index: 200; 186 | } 187 | 188 | .overlay-number { 189 | position: absolute; 190 | font-size: 48px; 191 | font-weight: bold; 192 | right: 20px; 193 | bottom: 20px; 194 | z-index: 200; 195 | } 196 | 197 | .overlay-name.night, .overlay-number.night { 198 | color: #fff; 199 | opacity: 1.0; 200 | } 201 | 202 | .overlay-number.nonum { 203 | opacity: 0.0; 204 | } 205 | 206 | .overlay-number span.error { 207 | color: red; 208 | } 209 | 210 | span.unit { 211 | font-size: 24px; 212 | } 213 | 214 | div.x_label { 215 | font-size: 13px; 216 | } 217 | 218 | div.item { 219 | font-size: 12px; 220 | } 221 | 222 | -------------------------------------------------------------------------------- /lib/tasseo/public/i/spin-night.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obfuscurity/tasseo/0dff00fbc7aab503585d659a6d8c41aa90ffd769/lib/tasseo/public/i/spin-night.gif -------------------------------------------------------------------------------- /lib/tasseo/public/i/spin.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obfuscurity/tasseo/0dff00fbc7aab503585d659a6d8c41aa90ffd769/lib/tasseo/public/i/spin.gif -------------------------------------------------------------------------------- /lib/tasseo/public/i/tasseo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obfuscurity/tasseo/0dff00fbc7aab503585d659a6d8c41aa90ffd769/lib/tasseo/public/i/tasseo.png -------------------------------------------------------------------------------- /lib/tasseo/public/i/toggle-night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obfuscurity/tasseo/0dff00fbc7aab503585d659a6d8c41aa90ffd769/lib/tasseo/public/i/toggle-night.png -------------------------------------------------------------------------------- /lib/tasseo/public/i/toggle-number.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obfuscurity/tasseo/0dff00fbc7aab503585d659a6d8c41aa90ffd769/lib/tasseo/public/i/toggle-number.png -------------------------------------------------------------------------------- /lib/tasseo/public/j/crypto-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Crypto-JS v2.5.3 3 | * http://code.google.com/p/crypto-js/ 4 | * (c) 2009-2012 by Jeff Mott. All rights reserved. 5 | * http://code.google.com/p/crypto-js/wiki/License 6 | */ 7 | (typeof Crypto=="undefined"||!Crypto.util)&&function(){var e=window.Crypto={},g=e.util={rotl:function(a,b){return a<>>32-b},rotr:function(a,b){return a<<32-b|a>>>b},endian:function(a){if(a.constructor==Number)return g.rotl(a,8)&16711935|g.rotl(a,24)&4278255360;for(var b=0;b0;a--)b.push(Math.floor(Math.random()*256));return b},bytesToWords:function(a){for(var b=[],c=0,d=0;c>>5]|=(a[c]&255)<< 8 | 24-d%32;return b},wordsToBytes:function(a){for(var b=[],c=0;c>>5]>>>24-c%32&255);return b},bytesToHex:function(a){for(var b=[],c=0;c>>4).toString(16)),b.push((a[c]&15).toString(16));return b.join("")},hexToBytes:function(a){for(var b=[],c=0;c>>6*(3-e)&63)):b.push("=");return b.join("")},base64ToBytes:function(a){if(typeof atob=="function")return f.stringToBytes(atob(a));for(var a=a.replace(/[^A-Z0-9+\/]/ig,""),b=[],c=0,d=0;c>> 10 | 6-d*2);return b}},e=e.charenc={};e.UTF8={stringToBytes:function(a){return f.stringToBytes(unescape(encodeURIComponent(a)))},bytesToString:function(a){return decodeURIComponent(escape(f.bytesToString(a)))}};var f=e.Binary={stringToBytes:function(a){for(var b=[],c=0;c=0?a.substring(b):(b=a.length,""),d=[];while(b>0)d.push(a.substring(b-=3,b+3));return d.reverse().join(",")+c}function I(a,b){return{scale:Math.pow(10,(8-b)*3),symbol:a}}function O(a){return function(b){return b<=0?0:b>=1?1:a(b)}}function P(a){return function(b){return 1-a(1-b)}}function Q(a){return function(b){return.5*(b<.5?a(2*b):2-a(2-2*b))}}function R(a){return a}function S(a){return function(b){return Math.pow(b,a)}}function T(a){return 1-Math.cos(a*Math.PI/2)}function U(a){return Math.pow(2,10*(a-1))}function V(a){return 1-Math.sqrt(1-a*a)}function W(a,b){var c;return arguments.length<2&&(b=.45),arguments.length<1?(a=1,c=b/4):c=b/(2*Math.PI)*Math.asin(1/a),function(d){return 1+a*Math.pow(2,10*-d)*Math.sin((d-c)*2*Math.PI/b)}}function X(a){return a||(a=1.70158),function(b){return b*b*((a+1)*b-a)}}function Y(a){return a<1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+.9375:7.5625*(a-=2.625/2.75)*a+.984375}function Z(){d3.event.stopPropagation(),d3.event.preventDefault()}function $(){var a=d3.event,b;while(b=a.sourceEvent)a=b;return a}function _(a){var b=new A,c=0,d=arguments.length;while(++c360?a-=360:a<0&&(a+=360),a<60?d+(e-d)*a/60:a<180?e:a<240?d+(e-d)*(240-a)/60:d}function g(a){return Math.round(f(a)*255)}var d,e;return a%=360,a<0&&(a+=360),b=b<0?0:b>1?1:b,c=c<0?0:c>1?1:c,e=c<=.5?c*(1+b):c+b-c*b,d=2*c-e,be(g(a+120),g(a),g(a-120))}function bo(a){return j(a,bu),a}function bv(a){return function(){return bp(a,this)}}function bw(a){return function(){return bq(a,this)}}function by(a,b){function f(){if(b=this.classList)return b.add(a);var b=this.className,d=b.baseVal!=null,e=d?b.baseVal:b;c.lastIndex=0,c.test(e)||(e=w(e+" "+a),d?b.baseVal=e:this.className=e)}function g(){if(b=this.classList)return b.remove(a);var b=this.className,d=b.baseVal!=null,e=d?b.baseVal:b;e=w(e.replace(c," ")),d?b.baseVal=e:this.className=e}function h(){(b.apply(this,arguments)?f:g).call(this)}var c=new RegExp("(^|\\s+)"+d3.requote(a)+"(\\s+|$)","g");if(arguments.length<2){var d=this.node();if(e=d.classList)return e.contains(a);var e=d.className;return c.lastIndex=0,c.test(e.baseVal!=null?e.baseVal:e)}return this.each(typeof b=="function"?h:b?f:g)}function bz(a){return{__data__:a}}function bA(a){return function(){return bt(this,a)}}function bB(a){return arguments.length||(a=d3.ascending),function(b,c){return a(b&&b.__data__,c&&c.__data__)}}function bD(a){return j(a,bE),a}function bF(a,b,c){j(a,bJ);var d=new k,e=d3.dispatch("start","end"),f=bR;return a.id=b,a.time=c,a.tween=function(b,c){return arguments.length<2?d.get(b):(c==null?d.remove(b):d.set(b,c),a)},a.ease=function(b){return arguments.length?(f=typeof b=="function"?b:d3.ease.apply(d3,arguments),a):f},a.each=function(b,c){return arguments.length<2?bS.call(a,b):(e.on(b,c),a)},d3.timer(function(g){return a.each(function(h,i,j){function p(a){return o.active>b?r():(o.active=b,d.forEach(function(a,b){(b=b.call(l,h,i))&&k.push(b)}),e.start.call(l,h,i),q(a)||d3.timer(q,0,c),1)}function q(a){if(o.active!==b)return r();var c=(a-m)/n,d=f(c),g=k.length;while(g>0)k[--g].call(l,d);if(c>=1)return r(),bL=b,e.end.call(l,h,i),bL=0,1}function r(){return--o.count||delete l.__transition__,1}var k=[],l=this,m=a[j][i].delay,n=a[j][i].duration,o=l.__transition__||(l.__transition__={active:0,count:0});++o.count,m<=g?p(g):d3.timer(p,m,c)}),1},0,c),a}function bH(a,b,c){return c!=""&&bG}function bI(a,b){function d(a,d,e){var f=b.call(this,a,d);return f==null?e!=""&&bG:e!=f&&c(e,f)}function e(a,d,e){return e!=b&&c(e,b)}var c=bb(a);return typeof b=="function"?d:b==null?bH:(b+="",e)}function bS(a){var b=bL,c=bR,d=bP,e=bQ;bL=this.id,bR=this.ease();for(var f=0,g=this.length;f=c.delay&&(c.flush=c.callback(a)),c=c.next;var d=bX()-b;d>24?(isFinite(d)&&(clearTimeout(bV),bV=setTimeout(bW,d)),bU=0):(bU=1,bY(bW))}function bX(){var a=null,b=bT,c=Infinity;while(b)b.flush?b=a?a.next=b.next:bT=b.next:(c=Math.min(c,b.then+b.delay),b=(a=b).next);return c}function bZ(a){var b=[a.a,a.b],c=[a.c,a.d],d=b_(b),e=b$(b,c),f=b_(ca(c,b,-e))||0;b[0]*c[1]2?cq:cp,i=d?bd:bc;return e=g(a,b,i,c),f=g(b,a,i,d3.interpolate),h}function h(a){return e(a)}var e,f;return h.invert=function(a){return f(a)},h.domain=function(b){return arguments.length?(a=b.map(Number),g()):a},h.range=function(a){return arguments.length?(b=a,g()):b},h.rangeRound=function(a){return h.range(a).interpolate(d3.interpolateRound)},h.clamp=function(a){return arguments.length?(d=a,g()):d},h.interpolate=function(a){return arguments.length?(c=a,g()):c},h.ticks=function(b){return cn(a,b)},h.tickFormat=function(b){return co(a,b)},h.nice=function(){return ch(a,cl),g()},h.copy=function(){return cj(a,b,c,d)},g()}function ck(a,b){return d3.rebind(a,b,"range","rangeRound","interpolate","clamp")}function cl(a){return a=Math.pow(10,Math.round(Math.log(a)/Math.LN10)-1),{floor:function(b){return Math.floor(b/a)*a},ceil:function(b){return Math.ceil(b/a)*a}}}function cm(a,b){var c=cf(a),d=c[1]-c[0],e=Math.pow(10,Math.floor(Math.log(d/b)/Math.LN10)),f=b/d*e;return f<=.15?e*=10:f<=.35?e*=5:f<=.75&&(e*=2),c[0]=Math.ceil(c[0]/e)*e,c[1]=Math.floor(c[1]/e)*e+e*.5,c[2]=e,c}function cn(a,b){return d3.range.apply(d3,cm(a,b))}function co(a,b){return d3.format(",."+Math.max(0,-Math.floor(Math.log(cm(a,b)[2])/Math.LN10+.01))+"f")}function cp(a,b,c,d){var e=c(a[0],a[1]),f=d(b[0],b[1]);return function(a){return f(e(a))}}function cq(a,b,c,d){var e=[],f=[],g=0,h=Math.min(a.length,b.length)-1;a[h]0;j--)e.push(c(f)*j)}else{for(;fi;g--);e=e.slice(f,g)}return e},d.tickFormat=function(a,e){arguments.length<2&&(e=cs);if(arguments.length<1)return e;var f=a/d.ticks().length,g=b===cu?(h=-1e-12,Math.floor):(h=1e-12,Math.ceil),h;return function(a){return a/c(g(b(a)+h))0?0:-a)/Math.LN10}function cv(a,b){function e(b){return a(c(b))}var c=cw(b),d=cw(1/b);return e.invert=function(b){return d(a.invert(b))},e.domain=function(b){return arguments.length?(a.domain(b.map(c)),e):a.domain().map(d)},e.ticks=function(a){return cn(e.domain(),a)},e.tickFormat=function(a){return co(e.domain(),a)},e.nice=function(){return e.domain(ch(e.domain(),cl))},e.exponent=function(a){if(!arguments.length)return b;var f=e.domain();return c=cw(b=a),d=cw(1/b),e.domain(f)},e.copy=function(){return cv(a.copy(),b)},ck(e,a)}function cw(a){return function(b){return b<0?-Math.pow(-b,a):Math.pow(b,a)}}function cx(a,b){function f(b){return d[((c.get(b)||c.set(b,a.push(b)))-1)%d.length]}function g(b,c){return d3.range(a.length).map(function(a){return b+c*a})}var c,d,e;return f.domain=function(d){if(!arguments.length)return a;a=[],c=new k;var e=-1,g=d.length,h;while(++e1){h=b[1],f=a[i],i++,d+="C"+(e[0]+g[0])+","+(e[1]+g[1])+","+(f[0]-h[0])+","+(f[1]-h[1])+","+f[0]+","+f[1];for(var j=2;j9&&(f=c*3/Math.sqrt(f),g[h]=f*d,g[h+1]=f*e));h=-1;while(++h<=i)f=(a[Math.min(i,h+1)][0]-a[Math.max(0,h-1)][0])/(6*(1+g[h]*g[h])),b.push([f||0,g[h]*f||0]);return b}function di(a){return a.length<3?cQ(a):a[0]+cW(a,dh(a))}function dj(a){var b,c=-1,d=a.length,e,f;while(++c1){var d=cf(a.domain()),e,f=-1,g=b.length,h=(b[1]-b[0])/++c,i,j;while(++f0;)(j=+b[f]-i*h)>=d[0]&&e.push(j);for(--f,i=0;++id&&(c=b,d=e);return c}function ea(a){return a.reduce(eb,0)}function eb(a,b){return a+b[1]}function ec(a,b){return ed(a,Math.ceil(Math.log(b.length)/Math.LN2+1))}function ed(a,b){var c=-1,d=+a[0],e=(a[1]-d)/b,f=[];while(++c<=b)f[c]=e*c+d;return f}function ee(a){return[d3.min(a),d3.max(a)]}function ef(a,b){return d3.rebind(a,b,"sort","children","value"),a.links=ej,a.nodes=function(b){return ek=!0,(a.nodes=a)(b)},a}function eg(a){return a.children}function eh(a){return a.value}function ei(a,b){return b.value-a.value}function ej(a){return d3.merge(a.map(function(a){return(a.children||[]).map(function(b){return{source:a,target:b}})}))}function el(a,b){return a.value-b.value}function em(a,b){var c=a._pack_next;a._pack_next=b,b._pack_prev=a,b._pack_next=c,c._pack_prev=b}function en(a,b){a._pack_next=b,b._pack_prev=a}function eo(a,b){var c=b.x-a.x,d=b.y-a.y,e=a.r+b.r;return e*e-c*c-d*d>.001}function ep(a){function l(a){b=Math.min(a.x-a.r,b),c=Math.max(a.x+a.r,c),d=Math.min(a.y-a.r,d),e=Math.max(a.y+a.r,e)}var b=Infinity,c=-Infinity,d=Infinity,e=-Infinity,f=a.length,g,h,i,j,k;a.forEach(eq),g=a[0],g.x=-g.r,g.y=0,l(g);if(f>1){h=a[1],h.x=h.r,h.y=0,l(h);if(f>2){i=a[2],eu(g,h,i),l(i),em(g,i),g._pack_prev=i,em(i,h),h=g._pack_next;for(var m=3;m0&&(a=d)}return a}function eD(a,b){return a.x-b.x}function eE(a,b){return b.x-a.x}function eF(a,b){return a.depth-b.depth}function eG(a,b){function c(a,d){var e=a.children;if(e&&(i=e.length)){var f,g=null,h=-1,i;while(++h=0)f=d[e]._tree,f.prelim+=b,f.mod+=b,b+=f.shift+(c+=f.change)}function eI(a,b,c){a=a._tree,b=b._tree;var d=c/(b.number-a.number);a.change+=d,b.change-=d,b.shift+=c,b.prelim+=c,b.mod+=c}function eJ(a,b,c){return a._tree.ancestor.parent==b.parent?a._tree.ancestor:c}function eK(a){return{x:a.x,y:a.y,dx:a.dx,dy:a.dy}}function eL(a,b){var c=a.x+b[3],d=a.y+b[0],e=a.dx-b[1]-b[3],f=a.dy-b[0]-b[2];return e<0&&(c+=e/2,e=0),f<0&&(d+=f/2,f=0),{x:c,y:d,dx:e,dy:f}}function eM(a){return a.map(eN).join(",")}function eN(a){return/[",\n]/.test(a)?'"'+a.replace(/\"/g,'""')+'"':a}function eP(a,b){return function(c){return c&&a.hasOwnProperty(c.type)?a[c.type](c):b}}function eQ(a){return"m0,"+a+"a"+a+","+a+" 0 1,1 0,"+ -2*a+"a"+a+","+a+" 0 1,1 0,"+2*a+"z"}function eR(a,b){eS.hasOwnProperty(a.type)&&eS[a.type](a,b)}function eT(a,b){eR(a.geometry,b)}function eU(a,b){for(var c=a.features,d=0,e=c.length;d0}function fg(a,b,c){return(c[0]-b[0])*(a[1]-b[1])<(c[1]-b[1])*(a[0]-b[0])}function fh(a,b,c,d){var e=a[0],f=b[0],g=c[0],h=d[0],i=a[1],j=b[1],k=c[1],l=d[1],m=e-g,n=f-e,o=h-g,p=i-k,q=j-i,r=l-k,s=(o*p-r*m)/(r*n-o*q);return[e+s*n,i+s*q]}function fj(a,b){var c={list:a.map(function(a,b){return{index:b,x:a[0],y:a[1]}}).sort(function(a,b){return a.yb.y?1:a.xb.x?1:0}),bottomSite:null},d={list:[],leftEnd:null,rightEnd:null,init:function(){d.leftEnd=d.createHalfEdge(null,"l"),d.rightEnd=d.createHalfEdge(null,"l"),d.leftEnd.r=d.rightEnd,d.rightEnd.l=d.leftEnd,d.list.unshift(d.leftEnd,d.rightEnd)},createHalfEdge:function(a,b){return{edge:a,side:b,vertex:null,l:null,r:null}},insert:function(a,b){b.l=a,b.r=a.r,a.r.l=b,a.r=b},leftBound:function(a){var b=d.leftEnd;do b=b.r;while(b!=d.rightEnd&&e.rightOf(b,a));return b=b.l,b},del:function(a){a.l.r=a.r,a.r.l=a.l,a.edge=null},right:function(a){return a.r},left:function(a){return a.l},leftRegion:function(a){return a.edge==null?c.bottomSite:a.edge.region[a.side]},rightRegion:function(a){return a.edge==null?c.bottomSite:a.edge.region[fi[a.side]]}},e={bisect:function(a,b){var c={region:{l:a,r:b},ep:{l:null,r:null}},d=b.x-a.x,e=b.y-a.y,f=d>0?d:-d,g=e>0?e:-e;return c.c=a.x*d+a.y*e+(d*d+e*e)*.5,f>g?(c.a=1,c.b=e/d,c.c/=d):(c.b=1,c.a=d/e,c.c/=e),c},intersect:function(a,b){var c=a.edge,d=b.edge;if(!c||!d||c.region.r==d.region.r)return null;var e=c.a*d.b-c.b*d.a;if(Math.abs(e)<1e-10)return null;var f=(c.c*d.b-d.c*c.b)/e,g=(d.c*c.a-c.c*d.a)/e,h=c.region.r,i=d.region.r,j,k;h.y=k.region.r.x;return l&&j.side==="l"||!l&&j.side==="r"?null:{x:f,y:g}},rightOf:function(a,b){var c=a.edge,d=c.region.r,e=b.x>d.x;if(e&&a.side==="l")return 1;if(!e&&a.side==="r")return 0;if(c.a===1){var f=b.y-d.y,g=b.x-d.x,h=0,i=0;!e&&c.b<0||e&&c.b>=0?i=h=f>=c.b*g:(i=b.x+b.y*c.b>c.c,c.b<0&&(i=!i),i||(h=1));if(!h){var j=d.x-c.region.l.x;i=c.b*(g*g-f*f)m*m+n*n}return a.side==="l"?i:!i},endPoint:function(a,c,d){a.ep[c]=d;if(!a.ep[fi[c]])return;b(a)},distance:function(a,b){var c=a.x-b.x,d=a.y-b.y;return Math.sqrt(c*c+d*d)}},f={list:[],insert:function(a,b,c){a.vertex=b,a.ystar=b.y+c;for(var d=0,e=f.list,g=e.length;dh.ystar||a.ystar==h.ystar&&b.x>h.vertex.x)continue;break}e.splice(d,0,a)},del:function(a){for(var b=0,c=f.list,d=c.length;bo.y&&(p=n,n=o,o=p,t="r"),s=e.bisect(n,o),m=d.createHalfEdge(s,t),d.insert(k,m),e.endPoint(s,fi[t],r),q=e.intersect(k,m),q&&(f.del(k),f.insert(k,q,e.distance(q,n))),q=e.intersect(m,l),q&&f.insert(m,q,e.distance(q,n));else break}for(i=d.right(d.leftEnd);i!=d.rightEnd;i=d.right(i))b(i.edge)}function fk(){return{leaf:!0,nodes:[],point:null}}function fl(a,b,c,d,e,f){if(!a(b,c,d,e,f)){var g=(c+e)*.5,h=(d+f)*.5,i=b.nodes;i[0]&&fl(a,i[0],c,d,g,h),i[1]&&fl(a,i[1],g,d,e,h),i[2]&&fl(a,i[2],c,h,g,f),i[3]&&fl(a,i[3],g,h,e,f)}}function fm(a){return{x:a[0],y:a[1]}}function fo(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function fq(a,b,c,d){var e,f,g=0,h=b.length,i=c.length;while(g=i)return-1;e=b.charCodeAt(g++);if(e==37){f=fw[b.charAt(g++)];if(!f||(d=f(a,c,d))<0)return-1}else if(e!=c.charCodeAt(d++))return-1}return d}function fx(a,b,c){return fz.test(b.substring(c,c+=3))?c:-1}function fy(a,b,c){fA.lastIndex=0;var d=fA.exec(b.substring(c,c+10));return d?c+=d[0].length:-1}function fC(a,b,c){var d=fD.get(b.substring(c,c+=3).toLowerCase());return d==null?-1:(a.m=d,c)}function fE(a,b,c){fF.lastIndex=0;var d=fF.exec(b.substring(c,c+12));return d?(a.m=fG.get(d[0].toLowerCase()),c+=d[0].length):-1}function fI(a,b,c){return fq(a,fv.c.toString(),b,c)}function fJ(a,b,c){return fq(a,fv.x.toString(),b,c)}function fK(a,b,c){return fq(a,fv.X.toString(),b,c)}function fL(a,b,c){fU.lastIndex=0;var d=fU.exec(b.substring(c,c+4));return d?(a.y=+d[0],c+=d[0].length):-1}function fM(a,b,c){fU.lastIndex=0;var d=fU.exec(b.substring(c,c+2));return d?(a.y=fN()+ +d[0],c+=d[0].length):-1}function fN(){return~~((new Date).getFullYear()/1e3)*1e3}function fO(a,b,c){fU.lastIndex=0;var d=fU.exec(b.substring(c,c+2));return d?(a.m=d[0]-1,c+=d[0].length):-1}function fP(a,b,c){fU.lastIndex=0;var d=fU.exec(b.substring(c,c+2));return d?(a.d=+d[0],c+=d[0].length):-1}function fQ(a,b,c){fU.lastIndex=0;var d=fU.exec(b.substring(c,c+2));return d?(a.H=+d[0],c+=d[0].length):-1}function fR(a,b,c){fU.lastIndex=0;var d=fU.exec(b.substring(c,c+2));return d?(a.M=+d[0],c+=d[0].length):-1}function fS(a,b,c){fU.lastIndex=0;var d=fU.exec(b.substring(c,c+2));return d?(a.S=+d[0],c+=d[0].length):-1}function fT(a,b,c){fU.lastIndex=0;var d=fU.exec(b.substring(c,c+3));return d?(a.L=+d[0],c+=d[0].length):-1}function fV(a,b,c){var d=fW.get(b.substring(c,c+=2).toLowerCase());return d==null?-1:(a.p=d,c)}function fX(a){var b=a.getTimezoneOffset(),c=b>0?"-":"+",d=~~(Math.abs(b)/60),e=Math.abs(b)%60;return c+fr(d)+fr(e)}function fZ(a){return a.toISOString()}function f$(a,b,c){function d(b){var c=a(b),d=f(c,1);return b-c1)while(gb?1:a>=b?0:NaN},d3.descending=function(a,b){return ba?1:b>=a?0:NaN},d3.mean=function(a,b){var c=a.length,d,e=0,f=-1,g=0;if(arguments.length===1)while(++f1&&(a=a.map(b)),a=a.filter(s),a.length?d3.quantile(a.sort(d3.ascending),.5):undefined},d3.min=function(a,b){var c=-1,d=a.length,e,f;if(arguments.length===1){while(++cf&&(e=f)}else{while(++cf&&(e=f)}return e},d3.max=function(a,b){var c=-1,d=a.length,e,f;if(arguments.length===1){while(++ce&&(e=f)}else{while(++ce&&(e=f)}return e},d3.extent=function(a,b){var c=-1,d=a.length,e,f,g;if(arguments.length===1){while(++cf&&(e=f),gf&&(e=f),g1);return a+b*c*Math.sqrt(-2*Math.log(e)/e)}}},d3.sum=function(a,b){var c=0,d=a.length,e,f=-1;if(arguments.length===1)while(++f>1;a.call(b,b[f],f)>1;c0&&(e=f);return e},d3.last=function(a,b){var c=0,d=a.length,e=a[0],f;arguments.length===1&&(b=d3.ascending);while(++c=b.length)return e?e.call(a,c):d?c.sort(d):c;var h=-1,i=c.length,j=b[g++],l,m,n=new k,o,p={};while(++h=b.length)return a;var e=[],f=c[d++],h;for(h in a)e.push({key:h,values:g(a[h],d)});return f&&e.sort(function(a,b){return f(a.key,b.key)}),e}var a={},b=[],c=[],d,e;return a.map=function(a){return f(a,0)},a.entries=function(a){return g(f(a,0),0)},a.key=function(c){return b.push(c),a},a.sortKeys=function(d){return c[b.length-1]=d,a},a.sortValues=function(b){return d=b,a},a.rollup=function(b){return e=b,a},a},d3.keys=function(a){var b=[];for(var c in a)b.push(c);return b},d3.values=function(a){var b=[];for(var c in a)b.push(a[c]);return b},d3.entries=function(a){var b=[];for(var c in a)b.push({key:c,value:a[c]});return b},d3.permute=function(a,b){var c=[],d=-1,e=b.length;while(++db)d.push(g/e);else while((g=a+c*++f)=200&&a<300||a===304?d:null)}},d.send(null)},d3.text=function(a,b,c){function d(a){c(a&&a.responseText)}arguments.length<3&&(c=b,b=null),d3.xhr(a,b,d)},d3.json=function(a,b){d3.text(a,"application/json",function(a){b(a?JSON.parse(a):null)})},d3.html=function(a,b){d3.text(a,"text/html",function(a){if(a!=null){var c=document.createRange();c.selectNode(document.body),a=c.createContextualFragment(a)}b(a)})},d3.xml=function(a,b,c){function d(a){c(a&&a.responseXML)}arguments.length<3&&(c=b,b=null),d3.xhr(a,b,d)};var z={svg:"http://www.w3.org/2000/svg",xhtml:"http://www.w3.org/1999/xhtml",xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"};d3.ns={prefix:z,qualify:function(a){var b=a.indexOf(":"),c=a;return b>=0&&(c=a.substring(0,b),a=a.substring(b+1)),z.hasOwnProperty(c)?{space:z[c],local:a}:a}},d3.dispatch=function(){var a=new A,b=-1,c=arguments.length;while(++b0&&(d=a.substring(c+1),a=a.substring(0,c)),arguments.length<2?this[a].on(d):this[a].on(d,b)},d3.format=function(a){var b=C.exec(a),c=b[1]||" ",d=b[3]||"",e=b[5],f=+b[6],g=b[7],h=b[8],i=b[9],j=1,k="",l=!1;h&&(h=+h.substring(1)),e&&(c="0",g&&(f-=Math.floor((f-1)/4)));switch(i){case"n":g=!0,i="g";break;case"%":j=100,k="%",i="f";break;case"p":j=100,k="%",i="r";break;case"d":l=!0,h=0;break;case"s":j=-1,i="r"}return i=="r"&&!h&&(i="g"),i=D.get(i)||F,function(a){if(l&&a%1)return"";var b=a<0&&(a=-a)?"−":d;if(j<0){var m=d3.formatPrefix(a,h);a*=m.scale,k=m.symbol}else a*=j;a=i(a,h);if(e){var n=a.length+b.length;n=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/,D=d3.map({g:function(a,b){return a.toPrecision(b)},e:function(a,b){return a.toExponential(b)},f:function(a,b){return a.toFixed(b)},r:function(a,b){return d3.round(a,b=E(a,b)).toFixed(Math.max(0,Math.min(20,b)))}}),H=["y","z","a","f","p","n","μ","m","","k","M","G","T","P","E","Z","Y"].map(I);d3.formatPrefix=function(a,b){var c=0;return a&&(a<0&&(a*=-1),b&&(a=d3.round(a,E(a,b))),c=1+Math.floor(1e-12+Math.log(a)/Math.LN10),c=Math.max(-24,Math.min(24,Math.floor((c<=0?c+1:c-1)/3)*3))),H[8+c/3]};var J=S(2),K=S(3),L=function(){return R},M=d3.map({linear:L,poly:S,quad:function(){return J},cubic:function(){return K},sin:function(){return T},exp:function(){return U},circle:function(){return V},elastic:W,back:X,bounce:function(){return Y}}),N=d3.map({"in":R,out:P,"in-out":Q,"out-in":function(a){return Q(P(a))}});d3.ease=function(a){var b=a.indexOf("-"),c=b>=0?a.substring(0,b):a,d=b>=0?a.substring(b+1):"in";return c=M.get(c)||L,d=N.get(d)||R,O(d(c.apply(null,Array.prototype.slice.call(arguments,1))))},d3.event=null,d3.interpolate=function(a,b){var c=d3.interpolators.length,d;while(--c>=0&&!(d=d3.interpolators[c](a,b)));return d},d3.interpolateNumber=function(a,b){return b-=a,function(c){return a+b*c}},d3.interpolateRound=function(a,b){return b-=a,function(c){return Math.round(a+b*c)}},d3.interpolateString=function(a,b){var c,d,e,f=0,g=0,h=[],i=[],j,k;ba.lastIndex=0;for(d=0;c=ba.exec(b);++d)c.index&&h.push(b.substring(f,g=c.index)),i.push({i:h.length,x:c[0]}),h.push(null),f=ba.lastIndex;f1){while(++e=0;)if(f=c[d])e&&e!==f.nextSibling&&e.parentNode.insertBefore(f,e),e=f;return this},bu.sort=function(a){a=bB.apply(this,arguments);for(var b=-1,c=this.length;++b0&&(a=a.substring(0,e)),arguments.length<2?(e=this.node()[d])&&e._:this.each(function(e,f){function i(a){var c=d3.event;d3.event=a;try{b.call(g,g.__data__,f)}finally{d3.event=c}}var g=this,h=g[d];h&&(g.removeEventListener(a,h,h.$),delete g[d]),b&&(g.addEventListener(a,g[d]=i,i.$=c),i._=b)})},bu.each=function(a){for(var b=-1,c=this.length;++b=cG?e?"M0,"+f+"A"+f+","+f+" 0 1,1 0,"+ -f+"A"+f+","+f+" 0 1,1 0,"+f+"M0,"+e+"A"+e+","+e+" 0 1,0 0,"+ -e+"A"+e+","+e+" 0 1,0 0,"+e+"Z":"M0,"+f+"A"+f+","+f+" 0 1,1 0,"+ -f+"A"+f+","+f+" 0 1,1 0,"+f+"Z":e?"M"+f*k+","+f*l+"A"+f+","+f+" 0 "+j+",1 "+f*m+","+f*n+"L"+e*m+","+e*n+"A"+e+","+e+" 0 "+j+",0 "+e*k+","+e*l+"Z":"M"+f*k+","+f*l+"A"+f+","+f+" 0 "+j+",1 "+f*m+","+f*n+"L0,0"+"Z"}var a=cH,b=cI,c=cJ,d=cK;return e.innerRadius=function(b){return arguments.length?(a=q(b),e):a},e.outerRadius=function(a){return arguments.length?(b=q(a),e):b},e.startAngle=function(a){return arguments.length?(c=q(a),e):c},e.endAngle=function(a){return arguments.length?(d=q(a),e):d},e.centroid=function(){var e=(a.apply(this,arguments)+b.apply(this,arguments))/2,f=(c.apply(this,arguments)+d.apply(this,arguments))/2+cF;return[Math.cos(f)*e,Math.sin(f)*e]},e};var cF=-Math.PI/2,cG=2*Math.PI-1e-6;d3.svg.line=function(){return cL(n)};var cO="linear",cP=d3.map({linear:cQ,"step-before":cR,"step-after":cS,basis:cY,"basis-open":cZ,"basis-closed":c$,bundle:c_,cardinal:cV,"cardinal-open":cT,"cardinal-closed":cU,monotone:di}),db=[0,2/3,1/3,0],dc=[0,1/3,2/3,0],dd=[0,1/6,2/3,1/6];d3.svg.line.radial=function(){var a=cL(dj);return a.radius=a.x,delete a.x,a.angle=a.y,delete a.y,a},cR.reverse=cS,cS.reverse=cR,d3.svg.area=function(){return dk(Object)},d3.svg.area.radial=function(){var a=dk(dj);return a.radius=a.x,delete a.x,a.innerRadius=a.x0,delete a.x0,a.outerRadius=a.x1,delete a.x1,a.angle=a.y,delete a.y,a.startAngle=a.y0,delete a.y0,a.endAngle=a.y1,delete a.y1,a},d3.svg.chord=function(){function f(c,d){var e=g(this,a,c,d),f=g(this,b,c,d);return"M"+e.p0+i(e.r,e.p1,e.a1-e.a0)+(h(e,f)?j(e.r,e.p1,e.r,e.p0):j(e.r,e.p1,f.r,f.p0)+i(f.r,f.p1,f.a1-f.a0)+j(f.r,f.p1,e.r,e.p0))+"Z"}function g(a,b,f,g){var h=b.call(a,f,g),i=c.call(a,h,g),j=d.call(a,h,g)+cF,k=e.call(a,h,g)+cF;return{r:i,a0:j,a1:k,p0:[i*Math.cos(j),i*Math.sin(j)],p1:[i*Math.cos(k),i*Math.sin(k)]}}function h(a,b){return a.a0==b.a0&&a.a1==b.a1}function i(a,b,c){return"A"+a+","+a+" 0 "+ +(c>Math.PI)+",1 "+b}function j(a,b,c,d){return"Q 0,0 "+d}var a=dl,b=dm,c=dn,d=cJ,e=cK;return f.radius=function(a){return arguments.length?(c=q(a),f):c},f.source=function(b){return arguments.length?(a=q(b),f):a},f.target=function(a){return arguments.length?(b=q(a),f):b},f.startAngle=function(a){return arguments.length?(d=q(a),f):d},f.endAngle=function(a){return arguments.length?(e=q(a),f):e},f},d3.svg.diagonal=function(){function d(d,e){var f=a.call(this,d,e),g=b.call(this,d,e),h=(f.y+g.y)/2,i=[f,{x:f.x,y:h},{x:g.x,y:h},g];return i=i.map(c),"M"+i[0]+"C"+i[1]+" "+i[2]+" "+i[3]}var a=dl,b=dm,c=dr;return d.source=function(b){return arguments.length?(a=q(b),d):a},d.target=function(a){return arguments.length?(b=q(a),d):b},d.projection=function(a){return arguments.length?(c=a,d):c},d},d3.svg.diagonal.radial=function(){var a=d3.svg.diagonal(),b=dr,c=a.projection;return a.projection=function(a){return arguments.length?c(ds(b=a)):b},a},d3.svg.mouse=d3.mouse,d3.svg.touches=d3.touches,d3.svg.symbol=function(){function c(c,d){return(dw.get(a.call(this,c,d))||dv)(b.call(this,c,d))}var a=du,b=dt;return c 3 | .type=function(b){return arguments.length?(a=q(b),c):a},c.size=function(a){return arguments.length?(b=q(a),c):b},c};var dw=d3.map({circle:dv,cross:function(a){var b=Math.sqrt(a/5)/2;return"M"+ -3*b+","+ -b+"H"+ -b+"V"+ -3*b+"H"+b+"V"+ -b+"H"+3*b+"V"+b+"H"+b+"V"+3*b+"H"+ -b+"V"+b+"H"+ -3*b+"Z"},diamond:function(a){var b=Math.sqrt(a/(2*dy)),c=b*dy;return"M0,"+ -b+"L"+c+",0"+" 0,"+b+" "+ -c+",0"+"Z"},square:function(a){var b=Math.sqrt(a)/2;return"M"+ -b+","+ -b+"L"+b+","+ -b+" "+b+","+b+" "+ -b+","+b+"Z"},"triangle-down":function(a){var b=Math.sqrt(a/dx),c=b*dx/2;return"M0,"+c+"L"+b+","+ -c+" "+ -b+","+ -c+"Z"},"triangle-up":function(a){var b=Math.sqrt(a/dx),c=b*dx/2;return"M0,"+ -c+"L"+b+","+c+" "+ -b+","+c+"Z"}});d3.svg.symbolTypes=dw.keys();var dx=Math.sqrt(3),dy=Math.tan(30*Math.PI/180);d3.svg.axis=function(){function k(k){k.each(function(){var k=d3.select(this),l=h==null?a.ticks?a.ticks.apply(a,g):a.domain():h,m=i==null?a.tickFormat?a.tickFormat.apply(a,g):String:i,n=dB(a,l,j),o=k.selectAll(".minor").data(n,String),p=o.enter().insert("line","g").attr("class","tick minor").style("opacity",1e-6),q=d3.transition(o.exit()).style("opacity",1e-6).remove(),r=d3.transition(o).style("opacity",1),s=k.selectAll("g").data(l,String),t=s.enter().insert("g","path").style("opacity",1e-6),u=d3.transition(s.exit()).style("opacity",1e-6).remove(),v=d3.transition(s).style("opacity",1),w,x=cg(a),y=k.selectAll(".domain").data([0]),z=y.enter().append("path").attr("class","domain"),A=d3.transition(y),B=a.copy(),C=this.__chart__||B;this.__chart__=B,t.append("line").attr("class","tick"),t.append("text"),v.select("text").text(m);switch(b){case"bottom":w=dz,p.attr("y2",d),r.attr("x2",0).attr("y2",d),t.select("line").attr("y2",c),t.select("text").attr("y",Math.max(c,0)+f),v.select("line").attr("x2",0).attr("y2",c),v.select("text").attr("x",0).attr("y",Math.max(c,0)+f).attr("dy",".71em").attr("text-anchor","middle"),A.attr("d","M"+x[0]+","+e+"V0H"+x[1]+"V"+e);break;case"top":w=dz,p.attr("y2",-d),r.attr("x2",0).attr("y2",-d),t.select("line").attr("y2",-c),t.select("text").attr("y",-(Math.max(c,0)+f)),v.select("line").attr("x2",0).attr("y2",-c),v.select("text").attr("x",0).attr("y",-(Math.max(c,0)+f)).attr("dy","0em").attr("text-anchor","middle"),A.attr("d","M"+x[0]+","+ -e+"V0H"+x[1]+"V"+ -e);break;case"left":w=dA,p.attr("x2",-d),r.attr("x2",-d).attr("y2",0),t.select("line").attr("x2",-c),t.select("text").attr("x",-(Math.max(c,0)+f)),v.select("line").attr("x2",-c).attr("y2",0),v.select("text").attr("x",-(Math.max(c,0)+f)).attr("y",0).attr("dy",".32em").attr("text-anchor","end"),A.attr("d","M"+ -e+","+x[0]+"H0V"+x[1]+"H"+ -e);break;case"right":w=dA,p.attr("x2",d),r.attr("x2",d).attr("y2",0),t.select("line").attr("x2",c),t.select("text").attr("x",Math.max(c,0)+f),v.select("line").attr("x2",c).attr("y2",0),v.select("text").attr("x",Math.max(c,0)+f).attr("y",0).attr("dy",".32em").attr("text-anchor","start"),A.attr("d","M"+e+","+x[0]+"H0V"+x[1]+"H"+e)}if(a.ticks)t.call(w,C),v.call(w,B),u.call(w,B),p.call(w,C),r.call(w,B),q.call(w,B);else{var D=B.rangeBand()/2,E=function(a){return B(a)+D};t.call(w,E),v.call(w,E)}})}var a=d3.scale.linear(),b="bottom",c=6,d=6,e=6,f=3,g=[10],h=null,i,j=0;return k.scale=function(b){return arguments.length?(a=b,k):a},k.orient=function(a){return arguments.length?(b=a,k):b},k.ticks=function(){return arguments.length?(g=arguments,k):g},k.tickValues=function(a){return arguments.length?(h=a,k):h},k.tickFormat=function(a){return arguments.length?(i=a,k):i},k.tickSize=function(a,b,f){if(!arguments.length)return c;var g=arguments.length-1;return c=+a,d=g>1?+b:c,e=g>0?+arguments[g]:c,k},k.tickPadding=function(a){return arguments.length?(f=+a,k):f},k.tickSubdivide=function(a){return arguments.length?(j=+a,k):j},k},d3.svg.brush=function(){function g(a){a.each(function(){var a=d3.select(this),e=a.selectAll(".background").data([0]),f=a.selectAll(".extent").data([0]),l=a.selectAll(".resize").data(d,String),m;a.style("pointer-events","all").on("mousedown.brush",k).on("touchstart.brush",k),e.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),f.enter().append("rect").attr("class","extent").style("cursor","move"),l.enter().append("g").attr("class",function(a){return"resize "+a}).style("cursor",function(a){return dC[a]}).append("rect").attr("x",function(a){return/[ew]$/.test(a)?-3:null}).attr("y",function(a){return/^[ns]/.test(a)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),l.style("display",g.empty()?"none":null),l.exit().remove(),b&&(m=cg(b),e.attr("x",m[0]).attr("width",m[1]-m[0]),i(a)),c&&(m=cg(c),e.attr("y",m[0]).attr("height",m[1]-m[0]),j(a)),h(a)})}function h(a){a.selectAll(".resize").attr("transform",function(a){return"translate("+e[+/e$/.test(a)][0]+","+e[+/^s/.test(a)][1]+")"})}function i(a){a.select(".extent").attr("x",e[0][0]),a.selectAll(".extent,.n>rect,.s>rect").attr("width",e[1][0]-e[0][0])}function j(a){a.select(".extent").attr("y",e[0][1]),a.selectAll(".extent,.e>rect,.w>rect").attr("height",e[1][1]-e[0][1])}function k(){function x(){var a=d3.event.changedTouches;return a?d3.touches(d,a)[0]:d3.mouse(d)}function y(){d3.event.keyCode==32&&(q||(r=null,s[0]-=e[1][0],s[1]-=e[1][1],q=2),Z())}function z(){d3.event.keyCode==32&&q==2&&(s[0]+=e[1][0],s[1]+=e[1][1],q=0,Z())}function A(){var a=x(),d=!1;t&&(a[0]+=t[0],a[1]+=t[1]),q||(d3.event.altKey?(r||(r=[(e[0][0]+e[1][0])/2,(e[0][1]+e[1][1])/2]),s[0]=e[+(a[0]0?e=c:e=0:c>0&&(b.start({type:"start",alpha:e=c}),d3.timer(a.tick)),a):e},a.start=function(){function q(a,c){var d=t(b),e=-1,f=d.length,g;while(++ee&&(e=h),d.push(h)}for(g=0;g0){f=-1;while(++f=i[0]&&o<=i[1]&&(k=g[d3.bisect(j,o,1,m)-1],k.y+=n,k.push(e[f]))}return g}var a=!0,b=Number,c=ee,d=ec;return e.value=function(a){return arguments.length?(b=a,e):b},e.range=function(a){return arguments.length?(c=q(a),e):c},e.bins=function(a){return arguments.length?(d=typeof a=="number"?function(b){return ed(b,a)}:q(a),e):d},e.frequency=function(b){return arguments.length?(a=!!b,e):a},e},d3.layout.hierarchy=function(){function e(f,h,i){var j=b.call(g,f,h),k=ek?f:{data:f};k.depth=h,i.push(k);if(j&&(m=j.length)){var l=-1,m,n=k.children=[],o=0,p=h+1;while(++l0&&(eI(eJ(g,a,d),a,m),i+=m,j+=m),k+=g._tree.mod,i+=e._tree.mod,l+=h._tree.mod,j+=f._tree.mod;g&&!eB(f)&&(f._tree.thread=g,f._tree.mod+=k-j),e&&!eA(h)&&(h._tree.thread=e,h._tree.mod+=i-l,d=a)}return d}var f=a.call(this,d,e),g=f[0];eG(g,function(a,b){a._tree={ancestor:a,prelim:0,mod:0,change:0,shift:0,number:b?b._tree.number+1:0}}),h(g),i(g,-g._tree.prelim);var k=eC(g,eE),l=eC(g,eD),m=eC(g,eF),n=k.x-b(k,l)/2,o=l.x+b(l,k)/2,p=m.depth||1;return eG(g,function(a){a.x=(a.x-n)/(o-n)*c[0],a.y=a.depth/p*c[1],delete a._tree}),f}var a=d3.layout.hierarchy().sort(null).value(null),b=ez,c=[1,1];return d.separation=function(a){return arguments.length?(b=a,d):b},d.size=function(a){return arguments.length?(c=a,d):c},ef(d,a)},d3.layout.treemap=function(){function i(a,b){var c=-1,d=a.length,e,f;while(++c0)d.push(g=f[o-1]),d.area+=g.area,(k=l(d,n))<=h?(f.pop(),h=k):(d.area-=d.pop().area,m(d,n,c,!1),n=Math.min(c.dx,c.dy),d.length=d.area=0,h=Infinity);d.length&&(m(d,n,c,!0),d.length=d.area=0),b.forEach(j)}}function k(a){var b=a.children;if(b&&b.length){var c=e(a),d=b.slice(),f,g=[];i(d,c.dx*c.dy/a.value),g.area=0;while(f=d.pop())g.push(f),g.area+=f.area,f.z!=null&&(m(g,f.z?c.dx:c.dy,c,!d.length),g.length=g.area=0);b.forEach(k)}}function l(a,b){var c=a.area,d,e=0,f=Infinity,g=-1,i=a.length;while(++ge&&(e=d)}return c*=c,b*=b,c?Math.max(b*e*h/c,c/(b*f*h)):Infinity}function m(a,c,d,e){var f=-1,g=a.length,h=d.x,i=d.y,j=c?b(a.area/c):0,k;if(c==d.dx){if(e||j>d.dy)j=d.dy;while(++fd.dx)j=d.dx;while(++f=a.length)return d;if(i)return i=!1,c;var b=f.lastIndex;if(a.charCodeAt(b)===34){var e=b;while(e++50?b:f<-140?c:g<21?d:a)(e)}var a=d3.geo.albers(),b=d3.geo.albers().origin([-160,60]).parallels([55,65]),c=d3.geo.albers().origin([-160,20]).parallels([8,18]),d=d3.geo.albers().origin([-60,10]).parallels([8,18]);return e.scale=function(f){return arguments.length?(a.scale(f),b.scale(f*.6),c.scale(f),d.scale(f*1.5),e.translate(a.translate())):a.scale()},e.translate=function(f){if(!arguments.length)return a.translate();var g=a.scale()/1e3,h=f[0],i=f[1];return a.translate(f),b.translate([h-400*g,i+170*g]),c.translate([h-190*g,i+200*g]),d.translate([h+580*g,i+430*g]),e},e.scale(a.scale())},d3.geo.bonne=function(){function g(g){var h=g[0]*eO-c,i=g[1]*eO-d;if(e){var j=f+e-i,k=h*Math.cos(i)/j;h=j*Math.sin(k),i=j*Math.cos(k)-f}else h*=Math.cos(i),i*=-1;return[a*h+b[0],a*i+b[1]]}var a=200,b=[480,250],c,d,e,f;return g.invert=function(d){var g=(d[0]-b[0])/a,h=(d[1]-b[1])/a;if(e){var i=f+h,j=Math.sqrt(g*g+i*i);h=f+e-j,g=c+j*Math.atan2(g,i)/Math.cos(h)}else h*=-1,g/=Math.cos(h);return[g/eO,h/eO]},g.parallel=function(a){return arguments.length?(f=1/Math.tan(e=a*eO),g):e/eO},g.origin=function(a){return arguments.length?(c=a[0]*eO,d=a[1]*eO,g):[c/eO,d/eO]},g.scale=function(b){return arguments.length?(a=+b,g):a},g.translate=function(a){return arguments.length?(b=[+a[0],+a[1]],g):b},g.origin([0,0]).parallel(45)},d3.geo.equirectangular=function(){function c(c){var d=c[0]/360,e=-c[1]/360;return[a*d+b[0],a*e+b[1]]}var a=500,b=[480,250];return c.invert=function(c){var d=(c[0]-b[0])/a,e=(c[1]-b[1])/a;return[360*d,-360*e]},c.scale=function(b){return arguments.length?(a=+b,c):a},c.translate=function(a){return arguments.length?(b=[+a[0],+a[1]],c):b},c},d3.geo.mercator=function(){function c(c){var d=c[0]/360,e=-(Math.log(Math.tan(Math.PI/4+c[1]*eO/2))/eO)/360;return[a*d+b[0],a*Math.max(-0.5,Math.min(.5,e))+b[1]]}var a=500,b=[480,250];return c.invert=function(c){var d=(c[0]-b[0])/a,e=(c[1]-b[1])/a;return[360*d,2*Math.atan(Math.exp(-360*e*eO))/eO-90]},c.scale=function(b){return arguments.length?(a=+b,c):a},c.translate=function(a){return arguments.length?(b=[+a[0],+a[1]],c):b},c},d3.geo.path=function(){function d(c,d){return typeof a=="function"&&(b=eQ(a.apply(this,arguments))),f(c)||null}function e(a){return c(a).join(",")}function h(a){var b=k(a[0]),c=0,d=a.length;while(++c0){b.push("M");while(++h0){b.push("M");while(++kd&&(d=a),fe&&(e=f)}),[[b,c],[d,e]]};var eS={Feature:eT,FeatureCollection:eU,GeometryCollection:eV,LineString:eW,MultiLineString:eX,MultiPoint:eW,MultiPolygon:eY,Point:eZ,Polygon:e$};d3.geo.circle=function(){function e(){}function f(a){return d.distance(a)=k*k+l*l?d[f].index=-1:(d[m].index=-1,o=d[f].angle,m=f,n=g)):(o=d[f].angle,m=f,n=g);e.push(h);for(f=0,g=0;f<2;++g)d[g].index!==-1&&(e.push(d[g].index),f++);p=e.length;for(;g=0?(c=a.ep.r,d=a.ep.l):(c=a.ep.l,d=a.ep.r),a.a===1?(g=c?c.y:-1e6,e=a.c-a.b*g,h=d?d.y:1e6,f=a.c-a.b*h):(e=c?c.x:-1e6,g=a.c-a.a*e,f=d?d.x:1e6,h=a.c-a.a*f);var i=[e,g],j=[f,h];b[a.region.l.index].push(i,j),b[a.region.r.index].push(i,j)}),b.map(function(b,c){var d=a[c][0],e=a[c][1];return b.forEach(function(a){a.angle=Math.atan2(a[0]-d,a[1]-e)}),b.sort(function(a,b){return a.angle-b.angle}).filter(function(a,c){return!c||a.angle-b[c-1].angle>1e-10})})};var fi={l:"r",r:"l"};d3.geom.delaunay=function(a){var b=a.map(function(){return[]}),c=[];return fj(a,function(c){b[c.region.l.index].push(a[c.region.r.index])}),b.forEach(function(b,d){var e=a[d],f=e[0],g=e[1];b.forEach(function(a){a.angle=Math.atan2(a[0]-f,a[1]-g)}),b.sort(function(a,b){return a.angle-b.angle});for(var h=0,i=b.length-1;h=g,j=b.y>=h,l=(j<<1)+i;a.leaf=!1,a=a.nodes[l]||(a.nodes[l]=fk()),i?c=g:e=g,j?d=h:f=h,k(a,b,c,d,e,f)}var f,g=-1,h=a.length;h&&isNaN(a[0].x)&&(a=a.map(fm));if(arguments.length<5)if(arguments.length===3)e=d=c,c=b;else{b=c=Infinity,d=e=-Infinity;while(++gd&&(d=f.x),f.y>e&&(e=f.y);var i=d-b,j=e-c;i>j?e=c+i:d=b+j}var m=fk();return m.add=function(a){k(m,a,b,c,d,e)},m.visit=function(a){fl(a,m,b,c,d,e)},a.forEach(m.add),m},d3.time={};var fn=Date;fo.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){fp.setUTCDate.apply(this._,arguments)},setDay:function(){fp.setUTCDay.apply(this._,arguments)},setFullYear:function(){fp.setUTCFullYear.apply(this._,arguments)},setHours:function(){fp.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){fp.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){fp.setUTCMinutes.apply(this._,arguments)},setMonth:function(){fp.setUTCMonth.apply(this._,arguments)},setSeconds:function(){fp.setUTCSeconds.apply(this._,arguments)},setTime:function(){fp.setTime.apply(this._,arguments)}};var fp=Date.prototype;d3.time.format=function(a){function c(c){var d=[],e=-1,f=0,g,h;while(++e=12?"PM":"AM"},S:function(a){return fr(a.getSeconds())},U:function(a){return fr(d3.time.sundayOfYear(a))},w:function(a){return a.getDay()},W:function(a){return fr(d3.time.mondayOfYear(a))},x:d3.time.format("%m/%d/%y"),X:d3.time.format("%H:%M:%S"),y:function(a){return fr(a.getFullYear()%100)},Y:function(a){return ft(a.getFullYear()%1e4)},Z:fX,"%":function(a){return"%"}},fw={a:fx,A:fy,b:fC,B:fE,c:fI,d:fP,e:fP,H:fQ,I:fQ,L:fT,m:fO,M:fR,p:fV,S:fS,x:fJ,X:fK,y:fM,Y:fL},fz=/^(?:sun|mon|tue|wed|thu|fri|sat)/i,fA=/^(?:Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday)/i,fB=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],fD=d3.map({jan:0,feb:1,mar:2,apr:3,may:4,jun:5,jul:6,aug:7,sep:8,oct:9,nov:10,dec:11}),fF=/^(?:January|February|March|April|May|June|July|August|September|October|November|December)/ig,fG=d3.map({january:0,february:1,march:2,april:3,may:4,june:5,july:6,august:7,september:8,october:9,november:10,december:11}),fH=["January","February","March","April","May","June","July","August","September","October","November","December"],fU=/\s*\d+/,fW=d3.map({am:0,pm:1});d3.time.format.utc=function(a){function c(a){try{fn=fo;var c=new fn;return c._=a,b(c)}finally{fn=Date}}var b=d3.time.format(a);return c.parse=function(a){try{fn=fo;var c=b.parse(a);return c&&c._}finally{fn=Date}},c.toString=b.toString,c};var fY=d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ");d3.time.format.iso=Date.prototype.toISOString?fZ:fY,fZ.parse=function(a){return new Date(a)},fZ.toString=fY.toString,d3.time.second=f$(function(a){return new fn(Math.floor(a/1e3)*1e3)},function(a,b){a.setTime(a.getTime()+Math.floor(b)*1e3)},function(a){return a.getSeconds()}),d3.time.seconds=d3.time.second.range,d3.time.seconds.utc=d3.time.second.utc.range,d3.time.minute=f$(function(a){return new fn(Math.floor(a/6e4)*6e4)},function(a,b){a.setTime(a.getTime()+Math.floor(b)*6e4)},function(a){return a.getMinutes()}),d3.time.minutes=d3.time.minute.range,d3.time.minutes.utc=d3.time.minute.utc.range,d3.time.hour=f$(function(a){var b=a.getTimezoneOffset()/60;return new fn((Math.floor(a/36e5-b)+b)*36e5)},function(a,b){a.setTime(a.getTime()+Math.floor(b)*36e5)},function(a){return a.getHours()}),d3.time.hours=d3.time.hour.range,d3.time.hours.utc=d3.time.hour.utc.range,d3.time.day=f$(function(a){return new fn(a.getFullYear(),a.getMonth(),a.getDate())},function(a,b){a.setDate(a.getDate()+b)},function(a){return a.getDate()-1}),d3.time.days=d3.time.day.range,d3.time.days.utc=d3.time.day.utc.range,d3.time.dayOfYear=function(a){var b=d3.time.year(a);return Math.floor((a-b)/864e5-(a.getTimezoneOffset()-b.getTimezoneOffset())/1440)},fB.forEach(function(a,b){a=a.toLowerCase(),b=7-b;var c=d3.time[a]=f$(function(a){return(a=d3.time.day(a)).setDate(a.getDate()-(a.getDay()+b)%7),a},function(a,b){a.setDate(a.getDate()+Math.floor(b)*7)},function(a){var c=d3.time.year(a).getDay();return Math.floor((d3.time.dayOfYear(a)+(c+b)%7)/7)-(c!==b)});d3.time[a+"s"]=c.range,d3.time[a+"s"].utc=c.utc.range,d3.time[a+"OfYear"]=function(a){var c=d3.time.year(a).getDay();return Math.floor((d3.time.dayOfYear(a)+(c+b)%7)/7)}}),d3.time.week=d3.time.sunday,d3.time.weeks=d3.time.sunday.range,d3.time.weeks.utc=d3.time.sunday.utc.range,d3.time.weekOfYear=d3.time.sundayOfYear,d3.time.month=f$(function(a){return new fn(a.getFullYear(),a.getMonth(),1)},function(a,b){a.setMonth(a.getMonth()+b)},function(a){return a.getMonth()}),d3.time.months=d3.time.month.range,d3.time.months.utc=d3.time.month.utc.range,d3.time.year=f$(function(a){return new fn(a.getFullYear(),0,1)},function(a,b){a.setFullYear(a.getFullYear()+b)},function(a){return a.getFullYear()}),d3.time.years=d3.time.year.range,d3.time.years.utc=d3.time.year.utc.range;var gg=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],gh=[[d3.time.second,1],[d3.time.second,5],[d3.time.second,15],[d3.time.second,30],[d3.time.minute,1],[d3.time.minute,5],[d3.time.minute,15],[d3.time.minute,30],[d3.time.hour,1],[d3.time.hour,3],[d3.time.hour,6],[d3.time.hour,12],[d3.time.day,1],[d3.time.day,2],[d3.time.week,1],[d3.time.month,1],[d3.time.month,3],[d3.time.year,1]],gi=[[d3.time.format("%Y"),function(a){return!0}],[d3.time.format("%B"),function(a){return a.getMonth()}],[d3.time.format("%b %d"),function(a){return a.getDate()!=1}],[d3.time.format("%a %d"),function(a){return a.getDay()&&a.getDate()!=1}],[d3.time.format("%I %p"),function(a){return a.getHours()}],[d3.time.format("%I:%M"),function(a){return a.getMinutes()}],[d3.time.format(":%S"),function(a){return a.getSeconds()}],[d3.time.format(".%L"),function(a){return a.getMilliseconds()}]],gj=d3.scale.linear(),gk=gd(gi);gh.year=function(a,b){return gj.domain(a.map(gf)).ticks(b).map(ge)},d3.time.scale=function(){return ga(d3.scale.linear(),gh,gk)};var gl=gh.map(function(a){return[a[0].utc,a[1]]}),gm=[[d3.time.format.utc("%Y"),function(a){return!0}],[d3.time.format.utc("%B"),function(a){return a.getUTCMonth()}],[d3.time.format.utc("%b %d"),function(a){return a.getUTCDate()!=1}],[d3.time.format.utc("%a %d"),function(a){return a.getUTCDay()&&a.getUTCDate()!=1}],[d3.time.format.utc("%I %p"),function(a){return a.getUTCHours()}],[d3.time.format.utc("%I:%M"),function(a){return a.getUTCMinutes()}],[d3.time.format.utc(":%S"),function(a){return a.getUTCSeconds()}],[d3.time.format.utc(".%L"),function(a){return a.getUTCMilliseconds()}]],gn=gd(gm);gl.year=function(a,b){return gj.domain(a.map(gp)).ticks(b).map(go)},d3.time.scale.utc=function(){return ga(d3.scale.linear(),gl,gn)}})(); -------------------------------------------------------------------------------- /lib/tasseo/public/j/rickshaw.min.js: -------------------------------------------------------------------------------- 1 | var Rickshaw={namespace:function(a,b){var c=a.split("."),d=Rickshaw;for(var e=1,f=c.length;ethis.window.xMax&&(b=!1),b}return!0},this.onUpdate=function(a){this.updateCallbacks.push(a)},this.registerRenderer=function(a){this._renderers=this._renderers||{},this._renderers[a.name]=a},this.configure=function(a){(a.width||a.height)&&this.setSize(a),Rickshaw.keys(this.defaults).forEach(function(b){this[b]=b in a?a[b]:b in this?this[b]:this.defaults[b]},this),this.setRenderer(a.renderer||this.renderer.name,a)},this.setRenderer=function(a,b){if(!this._renderers[a])throw"couldn't find renderer "+a;this.renderer=this._renderers[a],typeof b=="object"&&this.renderer.configure(b)},this.setSize=function(a){a=a||{};if(typeof window!==undefined)var b=window.getComputedStyle(this.element,null),c=parseInt(b.getPropertyValue("width")),d=parseInt(b.getPropertyValue("height"));this.width=a.width||c||400,this.height=a.height||d||250,this.vis&&this.vis.attr("width",this.width).attr("height",this.height)},this.initialize(a)},Rickshaw.namespace("Rickshaw.Fixtures.Color"),Rickshaw.Fixtures.Color=function(){this.schemes={},this.schemes.spectrum14=["#ecb796","#dc8f70","#b2a470","#92875a","#716c49","#d2ed82","#bbe468","#a1d05d","#e7cbe6","#d8aad6","#a888c2","#9dc2d3","#649eb9","#387aa3"].reverse(),this.schemes.spectrum2000=["#57306f","#514c76","#646583","#738394","#6b9c7d","#84b665","#a7ca50","#bfe746","#e2f528","#fff726","#ecdd00","#d4b11d","#de8800","#de4800","#c91515","#9a0000","#7b0429","#580839","#31082b"],this.schemes.spectrum2001=["#2f243f","#3c2c55","#4a3768","#565270","#6b6b7c","#72957f","#86ad6e","#a1bc5e","#b8d954","#d3e04e","#ccad2a","#cc8412","#c1521d","#ad3821","#8a1010","#681717","#531e1e","#3d1818","#320a1b"],this.schemes.classic9=["#423d4f","#4a6860","#848f39","#a2b73c","#ddcb53","#c5a32f","#7d5836","#963b20","#7c2626","#491d37","#2f254a"].reverse(),this.schemes.httpStatus={503:"#ea5029",502:"#d23f14",500:"#bf3613",410:"#efacea",409:"#e291dc",403:"#f457e8",408:"#e121d2",401:"#b92dae",405:"#f47ceb",404:"#a82a9f",400:"#b263c6",301:"#6fa024",302:"#87c32b",307:"#a0d84c",304:"#28b55c",200:"#1a4f74",206:"#27839f",201:"#52adc9",202:"#7c979f",203:"#a5b8bd",204:"#c1cdd1"},this.schemes.colorwheel=["#b5b6a9","#858772","#785f43","#96557e","#4682b4","#65b9ac","#73c03a","#cb513a"].reverse(),this.schemes.cool=["#5e9d2f","#73c03a","#4682b4","#7bc3b8","#a9884e","#c1b266","#a47493","#c09fb5"],this.schemes.munin=["#00cc00","#0066b3","#ff8000","#ffcc00","#330099","#990099","#ccff00","#ff0000","#808080","#008f00","#00487d","#b35a00","#b38f00","#6b006b","#8fb300","#b30000","#bebebe","#80ff80","#80c9ff","#ffc080","#ffe680","#aa80ff","#ee00cc","#ff8080","#666600","#ffbfff","#00ffcc","#cc6699","#999900"]},Rickshaw.namespace("Rickshaw.Fixtures.RandomData"),Rickshaw.Fixtures.RandomData=function(a){var b;a=a||1;var c=200,d=Math.floor((new Date).getTime()/1e3);this.addData=function(b){var e=Math.random()*100+15+c,f=b[0].length,g=1;b.forEach(function(b){var c=Math.random()*20,h=e/25+g++ +(Math.cos(f*g*11/960)+2)*15+(Math.cos(f/7)+2)*7+(Math.cos(f/17)+2)*1;b.push({x:f*a+d,y:h+c})}),c=e*.85}},Rickshaw.namespace("Rickshaw.Fixtures.Time"),Rickshaw.Fixtures.Time=function(){var a=(new Date).getTimezoneOffset()*60,b=this;this.months=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],this.units=[{name:"decade",seconds:315576e3,formatter:function(a){return parseInt(a.getUTCFullYear()/10)*10}},{name:"year",seconds:31557600,formatter:function(a){return a.getUTCFullYear()}},{name:"month",seconds:2635200,formatter:function(a){return b.months[a.getUTCMonth()]}},{name:"week",seconds:604800,formatter:function(a){return b.formatDate(a)}},{name:"day",seconds:86400,formatter:function(a){return a.getUTCDate()}},{name:"6 hour",seconds:21600,formatter:function(a){return b.formatTime(a)}},{name:"hour",seconds:3600,formatter:function(a){return b.formatTime(a)}},{name:"15 minute",seconds:900,formatter:function(a){return b.formatTime(a)}},{name:"minute",seconds:60,formatter:function(a){return a.getUTCMinutes()}},{name:"15 second",seconds:15,formatter:function(a){return a.getUTCSeconds()+"s"}},{name:"second",seconds:1,formatter:function(a){return a.getUTCSeconds()+"s"}}],this.unit=function(a){return this.units.filter(function(b){return a==b.name}).shift()},this.formatDate=function(a){return a.toUTCString().match(/, (\w+ \w+ \w+)/)[1]},this.formatTime=function(a){return a.toUTCString().match(/(\d+:\d+):/)[1]},this.ceil=function(a,b){if(b.name=="month"){var c=new Date((a+b.seconds-1)*1e3),d=new Date(0);return d.setUTCFullYear(c.getUTCFullYear()),d.setUTCMonth(c.getUTCMonth()),d.setUTCDate(1),d.setUTCHours(0),d.setUTCMinutes(0),d.setUTCSeconds(0),d.setUTCMilliseconds(0),d.getTime()/1e3}if(b.name=="year"){var c=new Date((a+b.seconds-1)*1e3),d=new Date(0);return d.setUTCFullYear(c.getUTCFullYear()),d.setUTCMonth(0),d.setUTCDate(1),d.setUTCHours(0),d.setUTCMinutes(0),d.setUTCSeconds(0),d.setUTCMilliseconds(0),d.getTime()/1e3}return Math.ceil(a/b.seconds)*b.seconds}},Rickshaw.namespace("Rickshaw.Fixtures.Number"),Rickshaw.Fixtures.Number.formatKMBT=function(a){return a>=1e12?a/1e12+"T":a>=1e9?a/1e9+"B":a>=1e6?a/1e6+"M":a>=1e3?a/1e3+"K":a<1&&a>0?a.toFixed(2):a==0?"":a},Rickshaw.Fixtures.Number.formatBase1024KMGTP=function(a){return a>=0x4000000000000?a/0x4000000000000+"P":a>=1099511627776?a/1099511627776+"T":a>=1073741824?a/1073741824+"G":a>=1048576?a/1048576+"M":a>=1024?a/1024+"K":a<1&&a>0?a.toFixed(2):a==0?"":a},Rickshaw.namespace("Rickshaw.Color.Palette"),Rickshaw.Color.Palette=function(a){var b=new Rickshaw.Fixtures.Color;a=a||{},this.schemes={},this.scheme=b.schemes[a.scheme]||a.scheme||b.schemes.colorwheel,this.runningIndex=0,this.generatorIndex=0;if(a.interpolatedStopCount){var c=this.scheme.length-1,d,e,f=[];for(d=0;dc.graph.x.range()[1]){b.element&&(b.line.classList.add("offscreen"),b.element.style.display="none"),b.boxes.forEach(function(a){a.rangeElement&&a.rangeElement.classList.add("offscreen")});return}if(!b.element){var e=b.element=document.createElement("div");e.classList.add("annotation"),this.elements.timeline.appendChild(e),e.addEventListener("click",function(a){e.classList.toggle("active"),b.line.classList.toggle("active"),b.boxes.forEach(function(a){a.rangeElement&&a.rangeElement.classList.toggle("active")})},!1)}b.element.style.left=d+"px",b.element.style.display="block",b.boxes.forEach(function(a){var e=a.element;e||(e=a.element=document.createElement("div"),e.classList.add("content"),e.innerHTML=a.content,b.element.appendChild(e),b.line=document.createElement("div"),b.line.classList.add("annotation_line"),c.graph.element.appendChild(b.line),a.end&&(a.rangeElement=document.createElement("div"),a.rangeElement.classList.add("annotation_range"),c.graph.element.appendChild(a.rangeElement)));if(a.end){var f=d,g=Math.min(c.graph.x(a.end),c.graph.x.range()[1]);f>g&&(g=d,f=Math.max(c.graph.x(a.end),c.graph.x.range()[0]));var h=g-f;a.rangeElement.style.left=f+"px",a.rangeElement.style.width=h+"px",a.rangeElement.classList.remove("offscreen")}b.line.classList.remove("offscreen"),b.line.style.left=d+"px"})},this)},this.graph.onUpdate(function(){c.update()})},Rickshaw.namespace("Rickshaw.Graph.Axis.Time"),Rickshaw.Graph.Axis.Time=function(a){var b=this;this.graph=a.graph,this.elements=[],this.ticksTreatment=a.ticksTreatment||"plain",this.fixedTimeUnit=a.timeUnit;var c=new Rickshaw.Fixtures.Time;this.appropriateTimeUnit=function(){var a,b=c.units,d=this.graph.x.domain(),e=d[1]-d[0];return b.forEach(function(b){Math.floor(e/b.seconds)>=2&&(a=a||b)}),a||c.units[c.units.length-1]},this.tickOffsets=function(){var a=this.graph.x.domain(),b=this.fixedTimeUnit||this.appropriateTimeUnit(),d=Math.ceil((a[1]-a[0])/b.seconds),e=a[0],f=[];for(var g=0;gb.graph.x.range()[1])return;var c=document.createElement("div");c.style.left=b.graph.x(a.value)+"px",c.classList.add("x_tick"),c.classList.add(b.ticksTreatment);var d=document.createElement("div");d.classList.add("title"),d.innerHTML=a.unit.formatter(new Date(a.value*1e3)),c.appendChild(d),b.graph.element.appendChild(c),b.elements.push(c)})},this.graph.onUpdate(function(){b.render()})},Rickshaw.namespace("Rickshaw.Graph.Axis.Y"),Rickshaw.Graph.Axis.Y=function(a){var b=this,c=.1;this.initialize=function(a){this.graph=a.graph,this.orientation=a.orientation||"right";var c=a.pixelsPerTick||75;this.ticks=a.ticks||Math.floor(this.graph.height/c),this.tickSize=a.tickSize||4,this.ticksTreatment=a.ticksTreatment||"plain",a.element?(this.element=a.element,this.vis=d3.select(a.element).append("svg:svg").attr("class","rickshaw_graph y_axis"),this.element=this.vis[0][0],this.element.style.position="relative",this.setSize({width:a.width,height:a.height})):this.vis=this.graph.vis,this.graph.onUpdate(function(){b.render()})},this.setSize=function(a){a=a||{};if(!this.element)return;if(typeof window!="undefined"){var b=window.getComputedStyle(this.element.parentNode,null),d=parseInt(b.getPropertyValue("width"));if(!a.auto)var e=parseInt(b.getPropertyValue("height"))}this.width=a.width||d||this.graph.width*c,this.height=a.height||e||this.graph.height,this.vis.attr("width",this.width).attr("height",this.height*(1+c));var f=this.height*c;this.element.style.top=-1*f+"px"},this.render=function(){this.graph.height!==this._renderHeight&&this.setSize({auto:!0});var b=d3.svg.axis().scale(this.graph.y).orient(this.orientation);b.tickFormat(a.tickFormat||function(a){return a});if(this.orientation=="left")var d=this.height*c,e="translate("+this.width+", "+d+")";this.element&&this.vis.selectAll("*").remove(),this.vis.append("svg:g").attr("class",["y_ticks",this.ticksTreatment].join(" ")).attr("transform",e).call(b.ticks(this.ticks).tickSubdivide(0).tickSize(this.tickSize));var f=(this.orientation=="right"?1:-1)*this.graph.width;this.graph.vis.append("svg:g").attr("class","y_grid").call(b.ticks(this.ticks).tickSubdivide(0).tickSize(f)),this._renderHeight=this.graph.height},this.initialize(a)},Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Highlight"),Rickshaw.Graph.Behavior.Series.Highlight=function(a){this.graph=a.graph,this.legend=a.legend;var b=this,c={};this.addHighlightEvents=function(a){a.element.addEventListener("mouseover",function(d){b.legend.lines.forEach(function(b){if(a===b)return;c[b.series.name]=c[b.series.name]||b.series.color,b.series.color=d3.interpolateRgb(b.series.color,d3.rgb("#d8d8d8"))(.8).toString()}),b.graph.update()},!1),a.element.addEventListener("mouseout",function(a){b.legend.lines.forEach(function(a){c[a.series.name]&&(a.series.color=c[a.series.name])}),b.graph.update()},!1)},this.legend&&this.legend.lines.forEach(function(a){b.addHighlightEvents(a)})},Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Order"),Rickshaw.Graph.Behavior.Series.Order=function(a){this.graph=a.graph,this.legend=a.legend;var b=this;$(function(){$(b.legend.list).sortable({containment:"parent",tolerance:"pointer",update:function(a,c){var d=[];$(b.legend.list).find("li").each(function(a,b){if(!b.series)return;d.push(b.series)});for(var e=b.graph.series.length-1;e>=0;e--)b.graph.series[e]=d.shift();b.graph.update()}}),$(b.legend.list).disableSelection()}),this.graph.onUpdate(function(){var a=window.getComputedStyle(b.legend.element).height;b.legend.element.style.height=a})},Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Toggle"),Rickshaw.Graph.Behavior.Series.Toggle=function(a){this.graph=a.graph,this.legend=a.legend;var b=this;this.addAnchor=function(a){var c=document.createElement("a");c.innerHTML="✔",c.classList.add("action"),a.element.insertBefore(c,a.element.firstChild),c.onclick=function(b){a.series.disabled?(a.series.enable(),a.element.classList.remove("disabled")):(a.series.disable(),a.element.classList.add("disabled"))};var d=a.element.getElementsByTagName("span")[0];d.onclick=function(c){var d=a.series.disabled;if(!d)for(var e=0;ee){j=k;break}f[0][k+1]<=e?k++:k--}var e=f[0][j].x,l=this.xFormatter(e),m=b.x(e),n=0,o=b.series.active().map(function(a){return{order:n++,series:a,name:a.name,value:a.stack[j]}}),p,q=function(a,b){return a.value.y0+a.value.y-(b.value.y0+b.value.y)},r=b.y.magnitude.invert(b.element.offsetHeight-d);o.sort(q).forEach(function(a){a.formattedYValue=this.yFormatter.constructor==Array?this.yFormatter[o.indexOf(a)](a.value.y):this.yFormatter(a.value.y),a.graphX=m,a.graphY=b.y(a.value.y0+a.value.y),r>a.value.y0&&r0?this[0].data.forEach(function(b){a.data.push({x:b.x,y:0})}):a.data.length==0&&a.data.push({x:this.timeBase-(this.timeInterval||0),y:0}),this.push(a),this.legend&&this.legend.addLine(this.itemByName(a.name))},addData:function(a){var b=this.getIndex();Rickshaw.keys(a).forEach(function(a){this.itemByName(a)||this.addItem({name:a})},this),this.forEach(function(c){c.data.push({x:(b*this.timeInterval||1)+this.timeBase,y:a[c.name]||0})},this)},getIndex:function(){return this[0]&&this[0].data&&this[0].data.length?this[0].data.length:0},itemByName:function(a){for(var b=0;b0;d--)this.currentSize+=1,this.currentIndex+=1,this.forEach(function(a){a.data.unshift({x:((d-1)*this.timeInterval||1)+this.timeBase,y:0,i:d})},this)},addData:function($super,a){$super(a),this.currentSize+=1,this.currentIndex+=1;if(this.maxDataPoints!==undefined)while(this.currentSize>this.maxDataPoints)this.dropData()},dropData:function(){this.forEach(function(a){a.data.splice(0,1)}),this.currentSize-=1},getIndex:function(){return this.currentIndex}}); -------------------------------------------------------------------------------- /lib/tasseo/public/j/tasseo.js: -------------------------------------------------------------------------------- 1 | /* Dashboard object */ 2 | 3 | function TasseoDashboard(metrics, datasource, period, options) { 4 | this.options = $.extend({ 5 | interpolation: 'step-after', 6 | renderer: 'area', 7 | stroke: true, 8 | criticalColor: '#d59295', 9 | warningColor: '#f5cb56', 10 | normalColor: '#afdab1', 11 | container: '.main' 12 | }, options) 13 | 14 | this.period = period; 15 | this.datasource = datasource; 16 | this.buildContainers(metrics); 17 | this.createGraphs(metrics); 18 | } 19 | 20 | TasseoDashboard.prototype = { 21 | createGraphs: function(metrics) { 22 | var filtered = [] 23 | _.each(metrics, function(metric, idx) { 24 | if (_.isString(metric.target)) { 25 | var element = document.querySelector('.graph' + idx); 26 | filtered.push(new TasseoMetric(element, metric, this.options)); 27 | } 28 | }, this); 29 | 30 | this.metrics = filtered; 31 | }, 32 | 33 | clearGraphs: function() { 34 | _.each(this.metrics, function(metric) { 35 | metric.clear(); 36 | }) 37 | }, 38 | 39 | // add our containers 40 | buildContainers: function(metrics) { 41 | var falseTargets = 0; 42 | for (var i=0; i'); 45 | falseTargets++; 46 | } else { 47 | var j = i - falseTargets; 48 | var link_open = 'link' in metrics[i] ? '' : ''; 49 | var link_close = 'link' in metrics[i] ? '' : ''; 50 | var graph_div = 51 | '
' + 52 | '' + 53 | link_open + '
' + link_close + 54 | '
' + 55 | '
'; 56 | $(this.options.container).append(graph_div); 57 | } 58 | } 59 | }, 60 | 61 | setPeriod: function(period) { 62 | this.period = period; 63 | }, 64 | 65 | refreshData: function() { 66 | this.datasource.refresh(this.metrics, this.period); 67 | } 68 | } 69 | 70 | 71 | function TasseoMetric(element, metricSpec, dashboardOptions) { 72 | // Merge the keys from metricSpec into this object 73 | _.extend(this, metricSpec) 74 | 75 | this.element = element 76 | this.alias = this.alias || this.target; 77 | this.transform = this.transform || function(value) { return value; }; 78 | this.scale = this.scale || false; 79 | this.metricSpec = metricSpec 80 | this.dashboardOptions = dashboardOptions; 81 | 82 | this.clear(); 83 | } 84 | 85 | TasseoMetric.prototype = { 86 | clear: function() { 87 | this.datum = [{ x:0, y:0 }]; 88 | this.graph = new Rickshaw.Graph({ 89 | element: this.element, 90 | width: this.dashboardOptions.graph_width || 348, 91 | height: this.dashboardOptions.graph_height || 100, 92 | interpolation: this.dashboardOptions.interpolation, 93 | renderer: this.dashboardOptions.renderer, 94 | stroke: this.dashboardOptions.stroke, 95 | series: [{ 96 | name: this.alias, 97 | color: this.dashboardOptions.normalColor, 98 | data: this.datum 99 | }] 100 | }); 101 | 102 | this.graph.render(); 103 | }, 104 | 105 | setColor: function(color) { 106 | this.graph.series[0].color = color; 107 | }, 108 | 109 | replaceDatum: function(newDatum) { 110 | if (newDatum.length > 0) 111 | { 112 | // Replace the array without changing the reference 113 | this.datum.length = 0 114 | Array.prototype.push.apply(this.datum, newDatum) 115 | } 116 | }, 117 | 118 | datumMin: function() { 119 | return _.chain(this.datum) 120 | .map(function(pt) { return pt.y }) 121 | .min() 122 | .value() 123 | }, 124 | 125 | update: function(newData) { 126 | var cleanData = _.chain(newData) 127 | .select(function(data) { 128 | return _.isNumber(data.x) && _.isNumber(data.y) 129 | }) 130 | .sortBy(function(data) { 131 | return data.x 132 | }).value() 133 | 134 | this.replaceDatum(cleanData) 135 | 136 | // check our thresholds and update color 137 | var lastValue = this.transform(_.last(this.datum).y); 138 | var warning = this.warning; 139 | var critical = this.critical; 140 | if (critical > warning) { 141 | if (lastValue >= critical) { 142 | this.setColor(this.dashboardOptions.criticalColor) 143 | } else if (lastValue >= warning) { 144 | this.setColor(this.dashboardOptions.warningColor) 145 | } else { 146 | this.setColor(this.dashboardOptions.normalColor) 147 | } 148 | } else { 149 | if (lastValue <= critical) { 150 | this.setColor(this.dashboardOptions.criticalColor) 151 | } else if (lastValue <= warning) { 152 | this.setColor(this.dashboardOptions.warningColor) 153 | } else { 154 | this.setColor(this.dashboardOptions.normalColor) 155 | } 156 | } 157 | 158 | this.redraw() 159 | }, 160 | 161 | redraw: function() { 162 | var element = $(this.element); 163 | 164 | // scale our graph so that the min is not 0 165 | if (this.scale) { 166 | this.graph.configure({ min: this.datumMin() }); 167 | } 168 | // update our graph 169 | this.graph.update(); 170 | 171 | element.find('.overlay-name').text(this.alias); 172 | 173 | if (!_.isUndefined(_.last(this.datum))) { 174 | var lastValue = this.transform(_.last(this.datum).y); 175 | var lastValueDisplay; 176 | if (_.isNumber(lastValue) && lastValue < 2.0) { 177 | lastValueDisplay = Math.round(lastValue*1000)/1000; 178 | } else { 179 | lastValueDisplay = parseInt(lastValue, 10); 180 | } 181 | if (this.description) { 182 | element.find('.description').html('Note:

' + this.description); 183 | } 184 | element.find('.overlay-name').text(this.alias); 185 | element.find('.overlay-number').text(lastValueDisplay); 186 | if (!_.isUndefined(this.unit)) { 187 | element.find('.overlay-number').append('' + this.unit + ''); 188 | } 189 | } else { 190 | element.find('.overlay-number').html('NF'); 191 | } 192 | } 193 | } 194 | 195 | 196 | /* Graphite datasource */ 197 | 198 | function TasseoGraphiteDatasource(url, auth, options) { 199 | this.baseUrl = url; 200 | this.auth = auth; 201 | this.options = _.extend({ 202 | padnulls: true 203 | }, options) 204 | } 205 | 206 | TasseoGraphiteDatasource.prototype = { 207 | refresh: function(metrics, period) { 208 | var self = this; 209 | 210 | $.ajax({ 211 | beforeSend: function(xhr) { 212 | if (self.auth && self.auth.length > 0) { 213 | var bytes = Crypto.charenc.Binary.stringToBytes(self.auth); 214 | var base64 = Crypto.util.bytesToBase64(bytes); 215 | xhr.setRequestHeader('Authorization', 'Basic ' + base64); 216 | } 217 | }, 218 | dataType: 'json', 219 | error: function(xhr, textStatus, errorThrown) { console.log(errorThrown); }, 220 | url: this.urlForMetrics(metrics, period) 221 | }).done(function(metricResults) { 222 | _.each(metrics, function(metric, idx) { 223 | var metricResult = _.find(metricResults, function(metricResult) { 224 | return ('keepLastValue(' + metric.target + ')') == metricResult.target; 225 | }); 226 | if(metricResult === undefined) { 227 | console.log("not found: " + metric.alias); 228 | } else { 229 | var newDatapoints = _.map(metricResult.datapoints, function(datapoint) { 230 | return { x: datapoint[1], y: datapoint[0] } 231 | }) 232 | metric.update(newDatapoints) 233 | } 234 | }) 235 | }) 236 | }, 237 | 238 | urlForMetrics: function(metrics, period) { 239 | var targets = _.map(metrics, function(metric) { 240 | if (this.options.padnulls) { 241 | return 'target=keepLastValue(' + encodeURI(metric.target) + ')' 242 | } else { 243 | return 'target=' + encodeURI(metric.target) 244 | } 245 | }, this).join("&"); 246 | 247 | var myUrl = this.baseUrl + '/render/?' + targets + '&from=-' + period + 'minutes&format=json'; 248 | return myUrl; 249 | } 250 | } 251 | 252 | 253 | 254 | /* InfluxDB datasource */ 255 | 256 | function TasseoInfluxDBDatasource(url, auth, options) { 257 | this.auth = auth.split(':'); 258 | this.user = this.auth[0]; 259 | this.pass = this.auth[1]; 260 | this.options = _.extend({}, options); 261 | this.url = url; 262 | } 263 | 264 | TasseoInfluxDBDatasource.prototype = { 265 | refresh: function(metrics, period) { 266 | _.each(metrics, function(metric) { 267 | this.refreshMetric(metric, period); 268 | }, this) 269 | }, 270 | 271 | refreshMetric: function(metric, period) { 272 | var self = this; 273 | 274 | $.ajax({ 275 | url: this.urlForMetric(metric, period), 276 | dataType: 'json', 277 | error: function(xhr, textStatus, errorThrown) { console.log(errorThrown); }, 278 | success: function(metricResult) { 279 | var datapoints = metricResult.results[0].series[0].values; 280 | var newDatapoints = _.map(datapoints, function(datapoint) { 281 | return { x: new Date(datapoint[0]).getTime()/1000.0, y: parseFloat(datapoint[datapoint.length - 1]) } 282 | }); 283 | metric.update(newDatapoints) 284 | } 285 | }) 286 | }, 287 | 288 | urlForMetric: function(metric, period) { 289 | var self = this; 290 | 291 | var query = 'select ' + metric.target + ' from ' + metric.series + ' where time > now() - ' + period + 'm'; 292 | if (metric.where) { 293 | query += ' and (' + metric.where + ')'; 294 | } 295 | 296 | return this.url + '/query?&time_precision=s&q=' + escape(query) + '&db=' + metric.db + '&u=' + self.user + '&p=' + self.pass; 297 | } 298 | } 299 | 300 | 301 | 302 | /* Librato datasource */ 303 | 304 | function TasseoLibratoDatasource(auth, options) { 305 | this.auth = auth; 306 | this.options = _.extend({}, options); 307 | this.url = this.options.url || 'https://metrics-api.librato.com/v1/metrics'; 308 | } 309 | 310 | TasseoLibratoDatasource.prototype = { 311 | refresh: function(metrics, period) { 312 | _.each(metrics, function(metric) { 313 | this.refreshMetric(metric, period); 314 | }, this) 315 | }, 316 | 317 | refreshMetric: function(metric, period) { 318 | var self = this; 319 | 320 | $.ajax({ 321 | url: this.urlForMetric(metric, period), 322 | beforeSend: function(xhr) { 323 | if (self.auth && self.auth.length > 0) { 324 | var bytes = Crypto.charenc.Binary.stringToBytes(self.auth); 325 | var base64 = Crypto.util.bytesToBase64(bytes); 326 | xhr.setRequestHeader('Authorization', 'Basic ' + base64); 327 | } 328 | }, 329 | dataType: 'json', 330 | error: function(xhr, textStatus, errorThrown) { console.log(errorThrown); }, 331 | success: function(metricResult) { 332 | var datapoints = metricResult.measurements[metric.source || "all"] 333 | var newDatapoints = _.map(datapoints, function(datapoint) { 334 | return { x: datapoint['measure_time'], y: datapoint['value'] } 335 | }) 336 | metric.update(newDatapoints) 337 | } 338 | }) 339 | }, 340 | 341 | urlForMetric: function(metric, period) { 342 | var now = Math.floor(new Date() / 1000), 343 | start_time = now - (period * 60), 344 | end_time = now; 345 | 346 | var url = this.url + "/" + metric.target + "?start_time=" + start_time + "&end_time=" + end_time + "&resolution=1"; 347 | 348 | if (metric.source) { 349 | url += "&source=" + metric.source; 350 | } else { 351 | url += "&summarize_sources=true&breakout_sources=false" 352 | } 353 | 354 | return url; 355 | } 356 | } 357 | 358 | /* AWSCloudWatch datasource */ 359 | 360 | function TasseoAWSCloudWatchDatasource(accessKeyId, secretAccessKey, region, options) { 361 | this.options = _.extend({}, options); 362 | AWS.config.update({accessKeyId: accessKeyId, secretAccessKey: secretAccessKey}); 363 | AWS.config.region = region ; 364 | this.client = new AWS.CloudWatch(); 365 | } 366 | 367 | TasseoAWSCloudWatchDatasource.prototype = { 368 | // http://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_GetMetricStatistics.html 369 | GET_METRIC_STATISTICS_VALID_PARAMS: [ 370 | 'Namespace', 371 | 'MetricName', 372 | 'Dimensions', 373 | 'Statistics', 374 | 'EndTime', 375 | 'StartTime', 376 | 'Period', 377 | 'Unit' 378 | ], 379 | 380 | refresh: function(metrics, period) { 381 | _.each(metrics, function(metric) { 382 | this.refreshMetric(metric, period); 383 | }, this) 384 | }, 385 | 386 | defaultParams: function(period) { 387 | var endTime = new Date(); 388 | var startTime = new Date(endTime.getTime() - period * 60 * 1000); // convert period min to ms 389 | return { 390 | EndTime: endTime.toISOString(), 391 | StartTime: startTime.toISOString(), 392 | Period: 60, // 1 minute segments of data, the minimum 393 | Statistics: [ 'Sum' ] 394 | } 395 | }, 396 | 397 | buildParams: function(metricParams, period) { 398 | return _.reduce( 399 | this.GET_METRIC_STATISTICS_VALID_PARAMS, 400 | function(acc, el) { 401 | metricParams[el] ? acc[el] = metricParams[el] : null; 402 | return acc 403 | }, 404 | this.defaultParams(period)) 405 | }, 406 | 407 | refreshMetric: function(metric, period) { 408 | var requestParams = this.buildParams(metric, period) 409 | 410 | this.client.getMetricStatistics(requestParams, function(err, data) { 411 | if (err) { 412 | console.log(err, err.stack); 413 | } else { 414 | var stat = requestParams.Statistics[0] 415 | var newDatapoints = _.map(data['Datapoints'], function(datapoint) { 416 | return { y: datapoint[stat], x: datapoint.Timestamp.getTime() } 417 | }) 418 | metric.update(newDatapoints) 419 | } 420 | }); 421 | } 422 | } 423 | 424 | 425 | 426 | /* UI functionality */ 427 | 428 | function TasseoUi(dashboard, options) { 429 | this.dashboard = dashboard; 430 | this.options = _.extend({ 431 | darkMode: false, 432 | title: true, 433 | toolbar: true, 434 | refreshInterval: 2000, 435 | realtimePeriod: 5 436 | }, options) 437 | 438 | this.setDarkMode(this.options.darkMode) 439 | 440 | if (!this.options.title) { 441 | $('div.title span').css('visibility', 'hidden'); 442 | } 443 | 444 | if (!this.options.toolbar) { 445 | $('div.toolbar').css('visibility', 'hidden'); 446 | } 447 | 448 | if (this.isDarkMode()) { 449 | $('.overlay-number').html(''); 450 | } else { 451 | $('.overlay-number').html(''); 452 | } 453 | 454 | this.attach(); 455 | this.start(); 456 | } 457 | 458 | TasseoUi.prototype = { 459 | start: function() { 460 | if (!this.refreshId) { 461 | this.refreshId = setInterval(_.bind(dashboard.refreshData, dashboard), this.options.refreshInterval); 462 | _.bind(dashboard.refreshData, dashboard)(); //fire an initial call 463 | } 464 | 465 | // set our 'live' interval hint 466 | $('.toolbar ul li.timepanel a.play'). 467 | removeClass('pause'). 468 | text(this.options.realtimePeriod + 'min'); 469 | }, 470 | pause: function() { 471 | if (this.refreshId) { 472 | clearInterval(this.refreshId); 473 | this.refreshId = undefined; 474 | } 475 | 476 | $('.toolbar ul li.timepanel a.play'). 477 | addClass('pause'). 478 | text('paused'); 479 | }, 480 | 481 | attach: function() { 482 | var self = this; 483 | 484 | // populate and render our navigation list 485 | $('.title').on('hover', 'span', function() { 486 | self.getDashboards(function(list) { 487 | $('.title span').html(''); 488 | for (var i in list) { 489 | if (list[i] === window.location.pathname.replace(/^\//, '')) { 490 | $('.title select').append(''); 491 | } else { 492 | $('.title select').append(''); 493 | } 494 | } 495 | $('.title span select').focus(); 496 | }); 497 | }); 498 | 499 | // display description 500 | $(document).on('mouseenter', 'div.graph', function() { 501 | if ($(this).find('span.description').text().length > 0) { 502 | $(this).find('span.description').css('visibility', 'visible'); 503 | } 504 | }); 505 | 506 | // hide description 507 | $(document).on('mouseleave', 'div.graph', function() { 508 | $(this).find('span.description').css('visibility', 'hidden'); 509 | }); 510 | 511 | // clear navigation list on focusout 512 | $('.title span').on('focusout', 'select', function() { 513 | $('.title span').html(window.location.pathname.replace(/^\//, '')); 514 | }); 515 | 516 | // navigate to selection 517 | $('.title span').on('change', 'select', function() { 518 | window.location.pathname = '/' + $(this).val(); 519 | }); 520 | 521 | 522 | // activate night mode by click 523 | $('li.toggle-night').on('click', 'a', function() { 524 | self.toggleDarkMode() 525 | }); 526 | 527 | // toggle number display 528 | $('li.toggle-nonum').on('click', 'a', function() { 529 | $('div.overlay-number').toggleClass('nonum'); 530 | }); 531 | 532 | // time panel, pause live feed and show range 533 | $('.toolbar ul li.timepanel').on('click', 'a.range', function() { 534 | $(this).parent('li').parent('ul').find('li').removeClass('selected'); 535 | $(this).parent('li').addClass('selected'); 536 | 537 | self.pause() 538 | 539 | var period = $(this).attr('title'); 540 | self.dashboard.setPeriod(period); 541 | self.dashboard.refreshData(); 542 | }); 543 | 544 | // time panel, resume live feed 545 | $('.toolbar ul li.timepanel').on('click', 'a.play', function() { 546 | $(this).parent('li').parent('ul').find('li').removeClass('selected'); 547 | $(this).parent('li').addClass('selected'); 548 | 549 | self.clearGraphs() 550 | 551 | self.dashboard.setPeriod(self.options.realtimePeriod); 552 | self.dashboard.refreshData(); 553 | self.start() 554 | }); 555 | }, 556 | 557 | clearGraphs: function() { 558 | $('.graph svg').remove(); 559 | this.dashboard.clearGraphs(); 560 | 561 | // reapply dark mode 562 | this.setDarkMode(this.options.darkMode) 563 | }, 564 | 565 | setDarkMode: function(enabled) { 566 | this.options.darkMode = enabled; 567 | 568 | if (enabled) { 569 | this.enableNightMode() 570 | } else { 571 | this.disableNightMode() 572 | } 573 | }, 574 | 575 | isDarkMode: function() { 576 | return this.options.darkMode; 577 | }, 578 | 579 | toggleDarkMode: function() { 580 | this.setDarkMode(!this.isDarkMode()) 581 | }, 582 | 583 | // activate night mode 584 | enableNightMode: function() { 585 | $('body').addClass('night'); 586 | $('div.title h1').addClass('night'); 587 | $('div.graph svg').css('opacity', '0.8'); 588 | $('span.description').addClass('night'); 589 | $('div.overlay-name').addClass('night'); 590 | $('div.overlay-number').addClass('night'); 591 | $('div.toolbar ul li.timepanel').addClass('night'); 592 | }, 593 | 594 | // deactivate night mode 595 | disableNightMode: function() { 596 | $('body').removeClass('night'); 597 | $('div.title h1').removeClass('night'); 598 | $('div.graph svg').css('opacity', '1.0'); 599 | $('span.description').removeClass('night'); 600 | $('div.overlay-name').removeClass('night'); 601 | $('div.overlay-number').removeClass('night'); 602 | $('div.toolbar ul li.timepanel').removeClass('night'); 603 | }, 604 | 605 | // retrieve dashboard list 606 | getDashboards: function(cb) { 607 | $.ajax({ 608 | dataType: 'json', 609 | error: function(xhr, textStatus, errorThrown) { console.log(errorThrown); }, 610 | url: '/' 611 | }).done(function(d) { 612 | cb(d.dashboards); 613 | }); 614 | } 615 | } 616 | -------------------------------------------------------------------------------- /lib/tasseo/views/index.haml: -------------------------------------------------------------------------------- 1 | !!! 5 2 | %html{ :xmlns => "http://www.w3.org/1999/xhtml" } 3 | %head 4 | %title Tasseo 5 | %meta{ "http-equiv" => "Content-Type", :content => "text/html;charset=utf-8" } 6 | %link{ :rel => "stylesheet", :type => "text/css", :href => "c/style.css" } 7 | %script{ :type => "text/javascript", :src => "https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" } 8 | %script{ :type => "text/javascript", :src => "https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js" } 9 | %script{ :type => "text/javascript", :src => "j/d3.v2.min.js" } 10 | %script{ :type => "text/javascript", :src => "j/rickshaw.min.js" } 11 | %script{ :type => "text/javascript", :src => "j/crypto-min.js" } 12 | %script{ :type => "text/javascript", :src => "j/tasseo.js" } 13 | - if ENV['AWS_ACCESS_KEY_ID'] 14 | %script{ :type => "text/javascript", :src => "https://sdk.amazonaws.com/js/aws-sdk-2.0.18.min.js" } 15 | %body 16 | .title 17 | - if dashboard 18 | %span= dashboard 19 | .toolbar 20 | %ul.timepanel 21 | %li.timepanel.live.selected 22 | %a{ :href => "#", :class => "play" } live 23 | %li.timepanel 24 | %a{ :href => "#", :class => "range", :title => "60" } 1h 25 | %li.timepanel 26 | %a{ :href => "#", :class => "range", :title => "180" } 3h 27 | %li.timepanel 28 | %a{ :href => "#", :class => "range", :title => "1440" } 1d 29 | %li.timepanel 30 | %a{ :href => "#", :class => "range", :title => "10080" } 1w 31 | %ul.toggle 32 | %li.toggle-nonum 33 | %a{ :href => "#" } 34 | %img{ :src => "i/toggle-number.png" } 35 | %li.toggle-night 36 | %a{ :href => "#" } 37 | %img{ :src => "i/toggle-night.png" } 38 | - else 39 | %span Tasseo 40 | - if error 41 | %p= error 42 | - if dashboard 43 | .main 44 | %script{ :type => "text/javascript", :src => "dashboards/#{dashboard}.js" } 45 | :javascript 46 | 47 | var datasource; 48 | 49 | var graphiteUrl = "#{ENV['GRAPHITE_URL']}"; 50 | var graphiteAuth = "#{ENV['GRAPHITE_AUTH']}"; 51 | var libratoAuth = "#{ENV['LIBRATO_AUTH']}"; 52 | var influxDbUrl = "#{ENV['INFLUXDB_URL']}"; 53 | var influxDbAuth = "#{ENV['INFLUXDB_AUTH']}"; 54 | var awsAccessKeyId = "#{ENV['AWS_ACCESS_KEY_ID']}"; 55 | var awsSecretAccessKey = "#{ENV['AWS_SECRET_ACCESS_KEY']}"; 56 | var awsRegion = "#{ENV['AWS_REGION']}"; 57 | 58 | if (libratoAuth != "") { 59 | datasource = new TasseoLibratoDatasource(libratoAuth) 60 | } else if (influxDbUrl != "") { 61 | datasource = new TasseoInfluxDBDatasource(influxDbUrl, influxDbAuth) 62 | } else if (typeof usingCloudWatch != 'undefined' && usingCloudWatch) { 63 | datasource = new TasseoAWSCloudWatchDatasource(awsAccessKeyId, awsSecretAccessKey, awsRegion) 64 | } else { 65 | var graphiteOptions = {} 66 | if (typeof padnulls != 'undefined') graphiteOptions['padnulls'] = padnulls; 67 | datasource = new TasseoGraphiteDatasource(graphiteUrl, graphiteAuth, graphiteOptions) 68 | } 69 | 70 | /* gather all configuration settings from global variables and turn 71 | * them into proper options. 72 | */ 73 | var realtimePeriod = (typeof period == 'undefined') ? 5 : period; 74 | var refreshInterval = (typeof refresh == 'undefined') ? 2000 : refresh; 75 | 76 | var dashboardOptions = {} 77 | if (typeof interpolation != 'undefined') dashboardOptions['interpolation'] = interpolation; 78 | if (typeof renderer != 'undefined') dashboardOptions['renderer'] = renderer; 79 | if (typeof stroke != 'undefined') dashboardOptions['stroke'] = stroke; 80 | 81 | if (typeof criticalColor != 'undefined') dashboardOptions['criticalColor'] = criticalColor; 82 | if (typeof warningColor != 'undefined') dashboardOptions['warningColor'] = warningColor; 83 | if (typeof normalColor != 'undefined') dashboardOptions['normalColor'] = normalColor; 84 | 85 | var uiOptions = { 86 | refreshInterval: refreshInterval, 87 | realtimePeriod: realtimePeriod 88 | } 89 | if (typeof title != 'undefined') uiOptions['title'] = title; 90 | if (typeof toolbar != 'undefined') uiOptions['toolbar'] = toolbar; 91 | if (typeof theme != 'undefined' && theme == "dark") uiOptions['darkMode'] = true; 92 | 93 | var dashboard = new TasseoDashboard(metrics, datasource, realtimePeriod, dashboardOptions) 94 | var ui = new TasseoUi(dashboard, uiOptions) 95 | 96 | - else 97 | - if list 98 | .nav 99 | %ul 100 | - list.each do |d| 101 | %li 102 | %a{ :href => "#{d}" } #{d} 103 | -------------------------------------------------------------------------------- /lib/tasseo/web.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra' 2 | require 'rack-ssl-enforcer' 3 | require 'haml' 4 | require 'json' 5 | require 'sinatra_auth_github' 6 | 7 | module Tasseo 8 | class Web < Sinatra::Base 9 | 10 | configure do 11 | enable :logging 12 | enable :sessions 13 | mime_type :js, 'text/javascript' 14 | use Rack::SslEnforcer if ENV['FORCE_HTTPS'] 15 | use Rack::Static, :urls => ['/dashboards/'] 16 | 17 | set :protection, :except => :frame_options 18 | set :session_secret, ENV['SESSION_SECRET'] || Digest::SHA1.hexdigest(Time.now.to_f.to_s) 19 | set :github_options, { :scopes => "user" } 20 | 21 | if ENV['GITHUB_AUTH_TEAM'] || ENV['GITHUB_AUTH_ORGANIZATION'] 22 | register Sinatra::Auth::Github 23 | end 24 | end 25 | 26 | before do 27 | if team = ENV['GITHUB_AUTH_TEAM'] 28 | github_team_authenticate!(team) unless request.path == '/health' 29 | elsif organization = ENV['GITHUB_AUTH_ORGANIZATION'] 30 | github_organization_authenticate!(organization) unless request.path == '/health' 31 | end 32 | 33 | find_dashboards 34 | end 35 | 36 | helpers do 37 | def dashboards 38 | @dashboards 39 | end 40 | 41 | def dashboards_dir 42 | File.expand_path('../../../dashboards', __FILE__) 43 | end 44 | 45 | def find_dashboards 46 | @dashboards = [] 47 | Dir.foreach(dashboards_dir).grep(/\.js/).sort.each do |f| 48 | @dashboards.push(f.split(".").first) 49 | end 50 | end 51 | end 52 | 53 | get '/' do 54 | if !dashboards.empty? 55 | if request.accept.include?('application/json') 56 | content_type 'application/json' 57 | status 200 58 | { :dashboards => dashboards }.to_json 59 | else 60 | haml :index, :locals => { 61 | :dashboard => nil, 62 | :list => dashboards, 63 | :error => nil 64 | } 65 | end 66 | else 67 | if request.accept.include?('application/json') 68 | content_type 'application/json' 69 | status 204 70 | else 71 | haml :index, :locals => { 72 | :dashboard => nil, 73 | :list => nil, 74 | :error => 'No dashboard files found.' 75 | } 76 | end 77 | end 78 | end 79 | 80 | get '/health' do 81 | content_type :json 82 | {'status' => 'ok'}.to_json 83 | end 84 | 85 | get %r{/([\S]+)} do 86 | path = params[:captures].first 87 | if dashboards.include?(path) 88 | haml :index, :locals => { :dashboard => path } 89 | else 90 | body = haml :index, :locals => { 91 | :dashboard => nil, 92 | :list => dashboards, 93 | :error => 'That dashboard does not exist.' 94 | } 95 | [404, body] 96 | end 97 | end 98 | end 99 | end 100 | 101 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.treat_symbols_as_metadata_keys_with_true_values = true 3 | config.run_all_when_everything_filtered = true 4 | config.filter_run :focus 5 | config.order = 'random' 6 | end 7 | -------------------------------------------------------------------------------- /spec/web_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rack/test' 2 | require 'spec_helper' 3 | 4 | require 'tasseo/web' 5 | 6 | describe Tasseo::Web do 7 | include Rack::Test::Methods 8 | 9 | def app 10 | Tasseo::Web 11 | end 12 | 13 | describe 'GET /' do 14 | context 'JSON' do 15 | before do 16 | header 'Accept', 'application/json' 17 | end 18 | 19 | context 'without dashboards' do 20 | before do 21 | app.any_instance.stub(:dashboards) { [] } 22 | end 23 | 24 | it 'should return a 204' do 25 | get '/' 26 | last_response.status.should eq(204) 27 | end 28 | 29 | it 'should return an empty array if there are no dashboards' do 30 | get '/' 31 | last_response.body.should be_empty 32 | end 33 | end 34 | 35 | context 'with dashboards' do 36 | before do 37 | app.any_instance.stub(:dashboards) { ['foo'] } 38 | end 39 | 40 | it 'should return ok' do 41 | get '/' 42 | last_response.should be_ok 43 | end 44 | 45 | it 'should return an array of dashboards' do 46 | get '/' 47 | last_response.body.should eq({'dashboards' => ['foo']}.to_json) 48 | end 49 | end 50 | end 51 | 52 | context 'html' do 53 | it 'should return ok' do 54 | get '/' 55 | last_response.should be_ok 56 | end 57 | end 58 | end 59 | 60 | describe 'GET /health' do 61 | it 'should respond with a 200' do 62 | get '/health' 63 | last_response.should be_ok 64 | end 65 | 66 | it 'should respond with the text "ok"' do 67 | get '/health' 68 | last_response.body.should eq({'status' => 'ok'}.to_json) 69 | end 70 | 71 | it 'should be JSON' do 72 | get '/health' 73 | last_response.headers['Content-Type'].should eq('application/json;charset=utf-8') 74 | end 75 | 76 | context 'GITHUB_AUTH_ORGANIZATION is set' do 77 | before do 78 | ENV['GITHUB_AUTH_ORGANIZATION'] = 'foo' 79 | end 80 | 81 | after do 82 | ENV.delete('GITHUB_AUTH_ORGANIZATION') 83 | end 84 | 85 | it 'should work even if Github auth is enabled' do 86 | get '/health' 87 | last_response.should be_ok 88 | end 89 | end 90 | end 91 | 92 | describe 'GET *' do 93 | context 'dashboard exists' do 94 | it 'should be ok' do 95 | app.any_instance.stub(:dashboards) { ['foo'] } 96 | get '/foo' 97 | last_response.should be_ok 98 | end 99 | end 100 | 101 | context 'dashboard does not exist' do 102 | it 'should 404' do 103 | app.any_instance.stub(:dashboards) { [] } 104 | get '/foo' 105 | last_response.status.should eq(404) 106 | end 107 | end 108 | end 109 | end 110 | --------------------------------------------------------------------------------