├── .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 | [](http://travis-ci.org/obfuscurity/tasseo)
4 |
5 | Reading the tea leaves.
6 |
7 | 
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 |
--------------------------------------------------------------------------------