From 1315cac7c303429bdfc56ef061d98b158bf67b4d Mon Sep 17 00:00:00 2001 From: Jeremy Attali Date: Sat, 25 Jan 2014 23:57:37 +0100 Subject: add error string when widget not rendered This prevents the cryptic ruby raw string being printed if no rendering template were found. Closes: #307 --- lib/dashing/app.rb | 1 + test/app_test.rb | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/lib/dashing/app.rb b/lib/dashing/app.rb index 033849c..933ef5b 100644 --- a/lib/dashing/app.rb +++ b/lib/dashing/app.rb @@ -117,6 +117,7 @@ get '/views/:widget?.html' do file = File.join(settings.root, "widgets", params[:widget], "#{params[:widget]}.#{suffix}") return engines.first.new(file).render if File.exist? file end + "Drats! Unable to find a widget file named: #{params[:widget]} to render." end def send_event(id, body, target=nil) diff --git a/test/app_test.rb b/test/app_test.rb index 4101e91..da889ff 100644 --- a/test/app_test.rb +++ b/test/app_test.rb @@ -137,6 +137,14 @@ class AppTest < Dashing::Test end end + def test_get_nonexistent_widget + with_generated_project do + get '/views/nowidget.html' + assert_equal 200, last_response.status + assert_equal last_response.body, 'Drats! Unable to find a widget file named: nowidget to render.' + end + end + def with_generated_project source_path = File.expand_path('../../templates', __FILE__) -- cgit v1.2.3 From 89cf5d74f0e193a4cdfa5bb9bf9b2d4ec756556d Mon Sep 17 00:00:00 2001 From: Alexander Moras Date: Thu, 19 May 2016 14:36:26 +0100 Subject: Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fd50c77..e76941f 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Dashing is a Sinatra based framework that lets you build beautiful dashboards. I [Check out the homepage](http://shopify.github.com/dashing). -Note: Dashing is no longer being actively maintained. Read about it [here](https://github.com/Shopify/dashing/issues/711). +Note: This is a fork of the original Dashing project. The original is no longer being maintained, hence this version. Read about it [here](https://github.com/Shopify/dashing/issues/711). # License Distributed under the [MIT license](MIT-LICENSE) -- cgit v1.2.3 From dde182d5ee57dd839cae5595523b105b0bad7457 Mon Sep 17 00:00:00 2001 From: Jan Vansteenkiste Date: Tue, 7 Jun 2016 22:25:24 +0200 Subject: Upgrade Thor --- dashing.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dashing.gemspec b/dashing.gemspec index a526d20..f7b266e 100644 --- a/dashing.gemspec +++ b/dashing.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |s| s.add_dependency('sinatra-contrib', '~> 1.4.2') s.add_dependency('thin', '~> 1.6.1') s.add_dependency('rufus-scheduler', '~> 2.0.24') - s.add_dependency('thor', '> 0.18.1') + s.add_dependency('thor', '~> 0.19') s.add_dependency('sprockets', '~> 2.10.1') s.add_dependency('rack', '~> 1.5.4') -- cgit v1.2.3 From e92c999bd17605bba9a331adf0c610bc448c9dc0 Mon Sep 17 00:00:00 2001 From: Shahzada Hatim Date: Sat, 9 Jul 2016 21:08:26 +0200 Subject: Cleaned up shopify references in docs Use consistent naming --- CONTRIBUTING.md | 6 ++---- README.md | 5 ++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6b22e83..33d27a6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,9 +1,7 @@ ## Have an Issue? -Dashing is no longer being actively maintained. - -1. Check the [Troubleshooting Guide](https://github.com/Shopify/dashing/wiki#how-tos) in the wiki. +1. Check the [Troubleshooting Guide](https://github.com/Dashing-io/dashing/wiki#how-tos) in the wiki. 2. Use the [GitHub Issue Search](https://help.github.com/articles/searching-issues/) to check if the issue has already been reported. 3. You can ask your issue on the tracker, but your best bet is to go to [Stack Overflow](http://stackoverflow.com/questions/tagged/dashing) @@ -12,5 +10,5 @@ Dashing is no longer being actively maintained. If you feel that you have a really amazing, super neato idea that should be a part of Dashing, it may be a good candidate for an external Gem which supercharges a project. An excellent example of this is [dashing-contrib](https://github.com/QubitProducts/dashing-contrib). If you -do create a third-party extension for Dashing, please add it [here](https://github.com/Shopify/dashing/wiki/Additional-Widgets#other-third-party-tools). +do create a third-party extension for Dashing, please add it [here](https://github.com/Dashing-io/dashing/wiki/Additional-Widgets#other-third-party-tools). diff --git a/README.md b/README.md index e76941f..36f89e9 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ -# [Dashing](http://shopify.github.com/dashing) -[![Build Status](https://secure.travis-ci.org/Shopify/dashing.png?branch=master)](http://travis-ci.org/Shopify/dashing) +# [Dashing](https://github.com/dashing-io/dashing/wiki) Dashing is a Sinatra based framework that lets you build beautiful dashboards. It looks especially great on TVs. -[Check out the homepage](http://shopify.github.com/dashing). +[Check out wiki](https://github.com/dashing-io/dashing/wiki). Note: This is a fork of the original Dashing project. The original is no longer being maintained, hence this version. Read about it [here](https://github.com/Shopify/dashing/issues/711). -- cgit v1.2.3 From 3d0b97e8feec98bd5f222e325f1c8717ac308832 Mon Sep 17 00:00:00 2001 From: Tyler Mauthe Date: Mon, 26 Sep 2016 09:03:03 -0700 Subject: Update CONTRIBUTING.md --- CONTRIBUTING.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 33d27a6..d6deb4c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,14 +1,67 @@ +# Welcome! +Thank you for your interest in Dashing! This exceptionally handsome +framework welcomes all input, but being stylish requires certain +protocols be followed. :bowtie: + +Below you will find a set of guidelines that will ensure the best outcome +for you _and_ Dashing. + -## Have an Issue? +## Have an Issue +If you run into problems with Dashing, please take these steps before +submitting an issue: -1. Check the [Troubleshooting Guide](https://github.com/Dashing-io/dashing/wiki#how-tos) in the wiki. +1. Check the [Troubleshooting Guide](https://github.com/Shopify/dashing/wiki#how-tos) in the wiki. 2. Use the [GitHub Issue Search](https://help.github.com/articles/searching-issues/) to check if the issue has already been reported. -3. You can ask your issue on the tracker, but your best bet is to go to [Stack Overflow](http://stackoverflow.com/questions/tagged/dashing) +3. Submit your issue to our Issue Tracker. Please provide as much helpful information as possible, preferably making use of a [reduced test case](https://www.google.ca/search?#q=reduced%20test%20case). +**Support requests should be directed to [Stack Overflow](http://stackoverflow.com/questions/tagged/dashing).** +## Feature Requests +Feature requests are welcome, but take a moment to consider whether your idea +fits with the scope and aim of the project. A good rule of thumb is to apply +the 80/20 rule: every feature should be useful to at least 80% of users. Adding +in every possible edge case will only make it more difficult to understand, maintain, +and hack on. -If you feel that you have a really amazing, super neato idea that should be a part of Dashing, it may be a good candidate for an external Gem which supercharges a project. An excellent example of this is +If you feel that you have a really amazing, super neato idea that doesn't +quite fit with the core use of Dashing, it may be a good candidate for an +external Gem which supercharges a project. An excellent example of this is [dashing-contrib](https://github.com/QubitProducts/dashing-contrib). If you -do create a third-party extension for Dashing, please add it [here](https://github.com/Dashing-io/dashing/wiki/Additional-Widgets#other-third-party-tools). +do create a third-party extension for Dashing, please add it [here](https://github.com/Shopify/dashing/wiki/Additional-Widgets#other-third-party-tools). + + +## Pull Requests + +Patches, improvements and new features are a fantastic +help -- thank you! + +**Please ask first** before embarking on any significant pull request (e.g. +implementing features, refactoring code, porting to a different language), +otherwise you risk spending a lot of time working on something that may +not be merged into the project. + +Please adhere to the coding conventions used throughout a project (indentation, +accurate comments, etc.) and any other requirements (such as test coverage). +All code submitted via Pull Request will be dicussed and critiqued in a +respectful manner. + +GitHub has [excellent documentation on how to use Pull Requests.](https://help.github.com/articles/using-pull-requests/) + +## Git Commit Message Suggestions +* Consider starting the commit message with an applicable emoji: + * :art: `:art:` when improving the format/structure of the code + * :moyai: `:moyai:` when adding a new feature + * :wrench: `:wrench:` when dealing with the toolchain (Git, Travis, etc) + * :notebook: `:notebook` when dealing with docs + * :racehorse: `:racehorse:` when improving performance + * :penguin: `:penguin:` when fixing something on Linux + * :apple: `:apple:` when fixing something on Mac OS + * :bug: `:bug:` when fixing a bug + * :bomb: `:bomb:` when removing code or files + * :white_check_mark: `:white_check_mark:` when adding tests + * :lock: `:lock:` when dealing with security + * :arrow_up: `:arrow_up:` when upgrading dependencies + * :arrow_down: `:arrow_down:` when downgrading dependencies -- cgit v1.2.3 From b1e2589e00197de598215c6504c7714998d3f095 Mon Sep 17 00:00:00 2001 From: Tyler Mauthe Date: Sun, 23 Oct 2016 14:42:17 -0700 Subject: Renamed to Smashing --- CONTRIBUTING.md | 19 ++++++------------- README.md | 10 +++++----- bin/dashing | 9 --------- bin/smashing | 9 +++++++++ dashing.gemspec | 36 ------------------------------------ smashing.gemspec | 35 +++++++++++++++++++++++++++++++++++ templates/project/Gemfile | 2 +- templates/project/README.md | 2 +- 8 files changed, 57 insertions(+), 65 deletions(-) delete mode 100755 bin/dashing create mode 100755 bin/smashing delete mode 100644 dashing.gemspec create mode 100644 smashing.gemspec diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d6deb4c..b7e4dfe 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,21 +1,14 @@ # Welcome! -Thank you for your interest in Dashing! This exceptionally handsome -framework welcomes all input, but being stylish requires certain -protocols be followed. :bowtie: - -Below you will find a set of guidelines that will ensure the best outcome -for you _and_ Dashing. +Thank you for your interest in Smashing! This project is a fork of Dashing, the exceptionally handsome dashboard framework. We're still in the process of transitioning, so please excuse the mess. ## Have an Issue -If you run into problems with Dashing, please take these steps before +If you run into problems with Smashing (or Dashing), please take these steps before submitting an issue: -1. Check the [Troubleshooting Guide](https://github.com/Shopify/dashing/wiki#how-tos) in the wiki. +1. Check the [Troubleshooting Guide](https://github.com/Dashing-io/dashing/wiki#how-tos) in the wiki. 2. Use the [GitHub Issue Search](https://help.github.com/articles/searching-issues/) to check if the issue has already been reported. -3. Submit your issue to our Issue Tracker. Please provide as much helpful information as possible, preferably making use of a [reduced test case](https://www.google.ca/search?#q=reduced%20test%20case). - -**Support requests should be directed to [Stack Overflow](http://stackoverflow.com/questions/tagged/dashing).** +3. Submit your issue to our Issue Tracker. Please provide as much helpful information as possible, preferably making use of a [reduced test case](https://developer.mozilla.org/en-US/docs/Mozilla/QA/Reducing_testcases). ## Feature Requests @@ -26,10 +19,10 @@ in every possible edge case will only make it more difficult to understand, main and hack on. If you feel that you have a really amazing, super neato idea that doesn't -quite fit with the core use of Dashing, it may be a good candidate for an +quite fit with the core use of Smashing, it may be a good candidate for an external Gem which supercharges a project. An excellent example of this is [dashing-contrib](https://github.com/QubitProducts/dashing-contrib). If you -do create a third-party extension for Dashing, please add it [here](https://github.com/Shopify/dashing/wiki/Additional-Widgets#other-third-party-tools). +do create a third-party extension for Smashing, please add it [here](https://github.com/Dashing-io/dashing/wiki/Additional-Widgets#other-third-party-tools). ## Pull Requests diff --git a/README.md b/README.md index 36f89e9..81d71c7 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# [Dashing](https://github.com/dashing-io/dashing/wiki) +# [Smashing](https://github.com/dashing-io/dashing/wiki) -Dashing is a Sinatra based framework that lets you build beautiful dashboards. It looks especially great on TVs. +Smashing, the spiritual successor to Dashing, is a Sinatra based framework that lets you build excellent dashboards. It looks especially great on TVs. -[Check out wiki](https://github.com/dashing-io/dashing/wiki). +[Check out our wiki](https://github.com/dashing-io/dashing/wiki). -Note: This is a fork of the original Dashing project. The original is no longer being maintained, hence this version. Read about it [here](https://github.com/Shopify/dashing/issues/711). +Note: This is a fork of the Dashing project, which is no longer being maintained. Read about that [here](https://github.com/Shopify/dashing/issues/711). # License -Distributed under the [MIT license](MIT-LICENSE) +Distributed under the [MIT license](MIT-LICENSE). diff --git a/bin/dashing b/bin/dashing deleted file mode 100755 index cce9340..0000000 --- a/bin/dashing +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env ruby -require "pathname" -bin_file = Pathname.new(__FILE__).realpath -$:.unshift File.expand_path("../../lib", bin_file) - -require 'dashing/cli' -require 'dashing/downloader' -Dashing::CLI.source_root(File.expand_path('../../templates', bin_file)) -Dashing::CLI.start(ARGV) diff --git a/bin/smashing b/bin/smashing new file mode 100755 index 0000000..cce9340 --- /dev/null +++ b/bin/smashing @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby +require "pathname" +bin_file = Pathname.new(__FILE__).realpath +$:.unshift File.expand_path("../../lib", bin_file) + +require 'dashing/cli' +require 'dashing/downloader' +Dashing::CLI.source_root(File.expand_path('../../templates', bin_file)) +Dashing::CLI.start(ARGV) diff --git a/dashing.gemspec b/dashing.gemspec deleted file mode 100644 index f7b266e..0000000 --- a/dashing.gemspec +++ /dev/null @@ -1,36 +0,0 @@ -# -*- encoding: utf-8 -*- - -Gem::Specification.new do |s| - s.name = 'dashing' - s.version = '1.3.6' - s.date = '2015-04-11' - s.executables = %w(dashing) - - - s.summary = "The exceptionally handsome dashboard framework." - s.description = "This framework lets you build & easily layout dashboards with your own custom widgets. Use it to make a status boards for your ops team, or use it to track signups, conversion rates, or whatever else metrics you'd like to see in one spot. Included with the framework are ready-made widgets for you to use or customize. All of this code was extracted out of a project at Shopify that displays dashboards on TVs around the office." - s.author = "Daniel Beauchamp" - s.email = 'daniel.beauchamp@shopify.com' - s.homepage = 'http://shopify.github.com/dashing' - s.license = "MIT" - - s.files = Dir['README.md', 'javascripts/**/*', 'templates/**/*','templates/**/.[a-z]*', 'lib/**/*'] - - s.add_dependency('sass', '~> 3.2.12') - s.add_dependency('coffee-script', '~> 2.2.0') - s.add_dependency('execjs', '~> 2.0.2') - s.add_dependency('sinatra', '~> 1.4.4') - s.add_dependency('sinatra-contrib', '~> 1.4.2') - s.add_dependency('thin', '~> 1.6.1') - s.add_dependency('rufus-scheduler', '~> 2.0.24') - s.add_dependency('thor', '~> 0.19') - s.add_dependency('sprockets', '~> 2.10.1') - s.add_dependency('rack', '~> 1.5.4') - - s.add_development_dependency('rake', '~> 10.1.0') - s.add_development_dependency('haml', '~> 4.0.4') - s.add_development_dependency('minitest', '~> 5.2.0') - s.add_development_dependency('mocha', '~> 0.14.0') - s.add_development_dependency('fakeweb', '~> 1.3.0') - s.add_development_dependency('simplecov', '~> 0.8.2') -end diff --git a/smashing.gemspec b/smashing.gemspec new file mode 100644 index 0000000..8be031c --- /dev/null +++ b/smashing.gemspec @@ -0,0 +1,35 @@ +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = 'smashing' + s.version = '1.0.0' + s.date = '2016-10-23' + s.executables = %w(smashing) + + + s.summary = "The wonderfully excellent dashboard framework." + s.description = "A framework for pulling together an overview of data that is important to your team and displaying it easily on TVs around the office. You write a bit of ruby code to gather data from some services and let Smashing handle the rest - displaying that data in a wonderfully simple layout. Built for developers and hackers, Smashing is highly customizable while maintaining humble roots that make it approachable to beginners." + s.author = "Daniel Beauchamp" + s.homepage = 'http://smashing.github.io' + s.license = "MIT" + + s.files = Dir['README.md', 'javascripts/**/*', 'templates/**/*','templates/**/.[a-z]*', 'lib/**/*'] + + s.add_dependency('sass', '~> 3.2.12') + s.add_dependency('coffee-script', '~> 2.2.0') + s.add_dependency('execjs', '~> 2.0.2') + s.add_dependency('sinatra', '~> 1.4.4') + s.add_dependency('sinatra-contrib', '~> 1.4.2') + s.add_dependency('thin', '~> 1.6.1') + s.add_dependency('rufus-scheduler', '~> 2.0.24') + s.add_dependency('thor', '~> 0.19') + s.add_dependency('sprockets', '~> 2.10.1') + s.add_dependency('rack', '~> 1.5.4') + + s.add_development_dependency('rake', '~> 10.1.0') + s.add_development_dependency('haml', '~> 4.0.4') + s.add_development_dependency('minitest', '~> 5.2.0') + s.add_development_dependency('mocha', '~> 0.14.0') + s.add_development_dependency('fakeweb', '~> 1.3.0') + s.add_development_dependency('simplecov', '~> 0.8.2') +end diff --git a/templates/project/Gemfile b/templates/project/Gemfile index 1235b37..46fea32 100644 --- a/templates/project/Gemfile +++ b/templates/project/Gemfile @@ -1,6 +1,6 @@ source 'https://rubygems.org' -gem 'dashing' +gem 'smashing' ## Remove this if you don't need a twitter widget. gem 'twitter', '>= 5.9.0' \ No newline at end of file diff --git a/templates/project/README.md b/templates/project/README.md index 260ac2a..d041f84 100644 --- a/templates/project/README.md +++ b/templates/project/README.md @@ -1 +1 @@ -Check out http://shopify.github.com/dashing for more information. \ No newline at end of file +Check out http://smashing.github.io/ for more information. \ No newline at end of file -- cgit v1.2.3 From c55edd708398a40c9f8720ee560073462543de61 Mon Sep 17 00:00:00 2001 From: Tyler Mauthe Date: Sun, 23 Oct 2016 14:58:29 -0700 Subject: Update README.md --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 81d71c7..4cad882 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,12 @@ -# [Smashing](https://github.com/dashing-io/dashing/wiki) +# [Smashing](https://github.com/dashing-io/smashing/wiki) Smashing, the spiritual successor to Dashing, is a Sinatra based framework that lets you build excellent dashboards. It looks especially great on TVs. -[Check out our wiki](https://github.com/dashing-io/dashing/wiki). +**Installation**: + +`gem install smashing` + +[Check out our wiki](https://github.com/dashing-io/smashing/wiki). Note: This is a fork of the Dashing project, which is no longer being maintained. Read about that [here](https://github.com/Shopify/dashing/issues/711). -- cgit v1.2.3 From e0a6f6f87d7381f329ad0debe7136329d14c1ae4 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Fri, 18 Nov 2016 16:40:33 +0100 Subject: More precise install instructions --- README.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4cad882..fbf50a4 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,21 @@ # [Smashing](https://github.com/dashing-io/smashing/wiki) -Smashing, the spiritual successor to Dashing, is a Sinatra based framework that lets you build excellent dashboards. It looks especially great on TVs. +Smashing, the spiritual successor to [Dashing](https://github.com/Shopify/dashing), is a Sinatra based framework that lets you build excellent dashboards. It looks especially great on TVs. **Installation**: -`gem install smashing` +```bash +$ gem install bundler +Fetching: bundler-X.X.X.gem (100%) +# +$ gem install smashing +Fetching: smashing-1.0.0.gem (100%) +# +$ smashing new my-project +create my-project +create my-project/.gitignore +# +``` [Check out our wiki](https://github.com/dashing-io/smashing/wiki). -- cgit v1.2.3 From f08bbc16148a6eecf0c0166ce95cbdf956306209 Mon Sep 17 00:00:00 2001 From: Tyler Mauthe Date: Sat, 19 Nov 2016 13:00:20 -0800 Subject: Update README.md Further clarification of installation and new project instructions. --- README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index fbf50a4..7458a0a 100644 --- a/README.md +++ b/README.md @@ -5,16 +5,18 @@ Smashing, the spiritual successor to [Dashing](https://github.com/Shopify/dashin **Installation**: ```bash +# Install bundler $ gem install bundler -Fetching: bundler-X.X.X.gem (100%) -# +# Install smashing $ gem install smashing -Fetching: smashing-1.0.0.gem (100%) -# +# Create a new project $ smashing new my-project -create my-project -create my-project/.gitignore -# +# Change Directory into the project +$ cd my-project +# Install the bundle of project specific gems +$ bundle +# Start the example dashboard! +$ smashing start ``` [Check out our wiki](https://github.com/dashing-io/smashing/wiki). -- cgit v1.2.3 From a61c20be2d519591ef64960734b1b2bd70893fbe Mon Sep 17 00:00:00 2001 From: Peter Hamberg Date: Tue, 29 Nov 2016 10:28:19 +0100 Subject: Add data transformation method to widget base in order to make it possible to transform the incoming data before it is sent to batman. --- javascripts/dashing.coffee | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/javascripts/dashing.coffee b/javascripts/dashing.coffee index 20a9987..a85d595 100644 --- a/javascripts/dashing.coffee +++ b/javascripts/dashing.coffee @@ -57,16 +57,22 @@ class Dashing.Widget extends Batman.View # In case the events from the server came before the widget was rendered lastData = Dashing.lastEvents[@id] if lastData + lastData = @select(lastData) @mixin(lastData) @onData(lastData) receiveData: (data) => + data = @select(data) @mixin(data) @onData(data) onData: (data) => # Widgets override this to handle incoming data + select: (data) => + # Widgets override this to transform data before it is applied to the model + return data + Dashing.AnimatedValue = get: Batman.Property.defaultAccessor.get set: (k, to) -> -- cgit v1.2.3 From b15d231a04f910a071001ca92ece80152647fc99 Mon Sep 17 00:00:00 2001 From: Jan Vansteenkiste Date: Fri, 9 Dec 2016 08:51:24 +0100 Subject: Correct url to the wiki pages --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7458a0a..87b3a47 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ $ bundle $ smashing start ``` -[Check out our wiki](https://github.com/dashing-io/smashing/wiki). +[Check out our wiki](https://github.com/SmashingDashboard/smashing/wiki). Note: This is a fork of the Dashing project, which is no longer being maintained. Read about that [here](https://github.com/Shopify/dashing/issues/711). -- cgit v1.2.3 From b40872c74aaa2cda856cd16c1e80ec2d21417f68 Mon Sep 17 00:00:00 2001 From: "Bruno P. Kinoshita" Date: Sat, 17 Dec 2016 09:55:44 +1300 Subject: Update README adding mailing list Note about mailing list, GitHub issues, and also move both under a Community section, and move it before the Installation (now a section in Markdown too) --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 87b3a47..169a09d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,11 @@ Smashing, the spiritual successor to [Dashing](https://github.com/Shopify/dashing), is a Sinatra based framework that lets you build excellent dashboards. It looks especially great on TVs. -**Installation**: +## Community + +Feel free to submit issues for bugs, new features, and enhancements in [GitHub](https://github.com/SmashingDashboard/smashing). For more general questions, or help with widgets, please use the [project mailing list](https://groups.google.com/forum/#!forum/smashing-dashboard). + +## Installation ```bash # Install bundler @@ -23,5 +27,5 @@ $ smashing start Note: This is a fork of the Dashing project, which is no longer being maintained. Read about that [here](https://github.com/Shopify/dashing/issues/711). -# License +## License Distributed under the [MIT license](MIT-LICENSE). -- cgit v1.2.3 From 9f5337f6c5e1a03ee5544178b16b806873cd8b57 Mon Sep 17 00:00:00 2001 From: "Bruno P. Kinoshita" Date: Sat, 17 Dec 2016 09:56:47 +1300 Subject: Use issues URL instead of GitHub repo URL --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 169a09d..7f61528 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Smashing, the spiritual successor to [Dashing](https://github.com/Shopify/dashin ## Community -Feel free to submit issues for bugs, new features, and enhancements in [GitHub](https://github.com/SmashingDashboard/smashing). For more general questions, or help with widgets, please use the [project mailing list](https://groups.google.com/forum/#!forum/smashing-dashboard). +Feel free to submit issues for bugs, new features, and enhancements in [GitHub](https://github.com/SmashingDashboard/smashing/issues). For more general questions, or help with widgets, please use the [project mailing list](https://groups.google.com/forum/#!forum/smashing-dashboard). ## Installation -- cgit v1.2.3 From b6a5149d02533d755c898239bcae2c8f034714bb Mon Sep 17 00:00:00 2001 From: "Bruno P. Kinoshita" Date: Sat, 17 Dec 2016 09:59:33 +1300 Subject: Add mailing list to contributing --- CONTRIBUTING.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b7e4dfe..dd4e478 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,15 @@ # Welcome! + Thank you for your interest in Smashing! This project is a fork of Dashing, the exceptionally handsome dashboard framework. We're still in the process of transitioning, so please excuse the mess. +For general questions on new features, widgets, or ideas, please consider using the +[project mailing list](https://groups.google.com/forum/#!forum/smashing-dashboard). This way +developers can maintain code-related issues in GitHub, and help users with users with simple +usage or widgets questions in a more appropriate channel. + ## Have an Issue + If you run into problems with Smashing (or Dashing), please take these steps before submitting an issue: @@ -12,6 +19,7 @@ submitting an issue: ## Feature Requests + Feature requests are welcome, but take a moment to consider whether your idea fits with the scope and aim of the project. A good rule of thumb is to apply the 80/20 rule: every feature should be useful to at least 80% of users. Adding @@ -24,6 +32,9 @@ external Gem which supercharges a project. An excellent example of this is [dashing-contrib](https://github.com/QubitProducts/dashing-contrib). If you do create a third-party extension for Smashing, please add it [here](https://github.com/Dashing-io/dashing/wiki/Additional-Widgets#other-third-party-tools). +For general questions on new features, widgets, or ideas, please use the +[project mailing list](https://groups.google.com/forum/#!forum/smashing-dashboard). + ## Pull Requests @@ -44,6 +55,7 @@ GitHub has [excellent documentation on how to use Pull Requests.](https://help.g ## Git Commit Message Suggestions + * Consider starting the commit message with an applicable emoji: * :art: `:art:` when improving the format/structure of the code * :moyai: `:moyai:` when adding a new feature -- cgit v1.2.3 From 5fe067c4780c4c25275844d7ff7097a9a934f267 Mon Sep 17 00:00:00 2001 From: Tyler Mauthe Date: Wed, 4 Jan 2017 01:49:43 -0800 Subject: Deprecate mailing list in favour of gitter --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7f61528..5c98f92 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Smashing, the spiritual successor to [Dashing](https://github.com/Shopify/dashin ## Community -Feel free to submit issues for bugs, new features, and enhancements in [GitHub](https://github.com/SmashingDashboard/smashing/issues). For more general questions, or help with widgets, please use the [project mailing list](https://groups.google.com/forum/#!forum/smashing-dashboard). +Feel free to submit issues for bugs, new features, and enhancements in [GitHub](https://github.com/SmashingDashboard/smashing/issues). For more general questions, or help with widgets, please use the [gitter chatroom](https://gitter.im/Smashing). ## Installation -- cgit v1.2.3 From d5ae8dadee453232921fc27433b3de449cfc0a64 Mon Sep 17 00:00:00 2001 From: Tyler Mauthe Date: Sun, 15 Jan 2017 10:27:53 -0800 Subject: Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dd4e478..051a840 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ Thank you for your interest in Smashing! This project is a fork of Dashing, the exceptionally handsome dashboard framework. We're still in the process of transitioning, so please excuse the mess. For general questions on new features, widgets, or ideas, please consider using the -[project mailing list](https://groups.google.com/forum/#!forum/smashing-dashboard). This way +[Gitter Chat Room](https://gitter.im/Smashing/Lobby). This way developers can maintain code-related issues in GitHub, and help users with users with simple usage or widgets questions in a more appropriate channel. -- cgit v1.2.3 From 08dc23d12ddeb013b9e3151498bfe65e3baae590 Mon Sep 17 00:00:00 2001 From: Tyler Mauthe Date: Sun, 15 Jan 2017 10:36:17 -0800 Subject: Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5c98f92..dd5acf5 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Gitter chat](https://badges.gitter.im/smashing.png)](https://gitter.im/Smashing) + # [Smashing](https://github.com/dashing-io/smashing/wiki) Smashing, the spiritual successor to [Dashing](https://github.com/Shopify/dashing), is a Sinatra based framework that lets you build excellent dashboards. It looks especially great on TVs. -- cgit v1.2.3 From 73e75f3904a3b59321d04471c78e50688c77db9b Mon Sep 17 00:00:00 2001 From: Jonas Dambacher Date: Wed, 15 Feb 2017 02:17:13 +0100 Subject: Switched background colors for 'danger' and 'warning' --- templates/project/assets/stylesheets/application.scss | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/templates/project/assets/stylesheets/application.scss b/templates/project/assets/stylesheets/application.scss index 451f03e..ef2af59 100644 --- a/templates/project/assets/stylesheets/application.scss +++ b/templates/project/assets/stylesheets/application.scss @@ -8,14 +8,14 @@ $background-color: #222; $text-color: #fff; -$background-warning-color-1: #e82711; -$background-warning-color-2: #9b2d23; -$text-warning-color: #fff; - -$background-danger-color-1: #eeae32; -$background-danger-color-2: #ff9618; +$background-danger-color-1: #e82711; +$background-danger-color-2: #9b2d23; $text-danger-color: #fff; +$background-warning-color-1: #eeae32; +$background-warning-color-2: #ff9618; +$text-warning-color: #fff; + @-webkit-keyframes status-warning-background { 0% { background-color: $background-warning-color-1; } 50% { background-color: $background-warning-color-2; } -- cgit v1.2.3 From 27c6007b0101695241b38853aa60da6ec899fc9f Mon Sep 17 00:00:00 2001 From: David Bishop Date: Mon, 13 Mar 2017 16:06:38 -0600 Subject: Use old-style hash syntax to enable compatibility with ruby 1.8.7 --- lib/dashing/app.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/dashing/app.rb b/lib/dashing/app.rb index 0ff3085..5ee085f 100644 --- a/lib/dashing/app.rb +++ b/lib/dashing/app.rb @@ -34,16 +34,18 @@ set :root, Dir.pwd set :sprockets, Sprockets::Environment.new(settings.root) set :assets_prefix, '/assets' set :digest_assets, false -set server: 'thin', connections: [], history_file: 'history.yml' +set :server, 'thin' +set :connections, [] +set :history_file, 'history.yml' set :public_folder, File.join(settings.root, 'public') set :views, File.join(settings.root, 'dashboards') set :default_dashboard, nil set :auth_token, nil if File.exists?(settings.history_file) - set history: YAML.load_file(settings.history_file) + set :history, YAML.load_file(settings.history_file) else - set history: {} + set :history, {} end %w(javascripts stylesheets fonts images).each do |path| @@ -55,7 +57,7 @@ end end not_found do - send_file File.join(settings.public_folder, '404.html'), status: 404 + send_file File.join(settings.public_folder, '404.html'), :status => 404 end at_exit do @@ -70,7 +72,7 @@ get '/' do redirect "/" + dashboard end -get '/events', provides: 'text/event-stream' do +get '/events', :provides => 'text/event-stream' do protected! response.headers['X-Accel-Buffering'] = 'no' # Disable buffering for nginx stream :keep_open do |out| -- cgit v1.2.3 From ea31604de417ee7d06ae68c3b234988df9ffb890 Mon Sep 17 00:00:00 2001 From: Effy Elden Date: Sat, 25 Mar 2017 17:12:10 +1100 Subject: Update links in README to canonical GitHub organisation --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dd5acf5..6ff4065 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ [![Gitter chat](https://badges.gitter.im/smashing.png)](https://gitter.im/Smashing) -# [Smashing](https://github.com/dashing-io/smashing/wiki) +# [Smashing](https://github.com/Smashing/smashing/wiki) Smashing, the spiritual successor to [Dashing](https://github.com/Shopify/dashing), is a Sinatra based framework that lets you build excellent dashboards. It looks especially great on TVs. ## Community -Feel free to submit issues for bugs, new features, and enhancements in [GitHub](https://github.com/SmashingDashboard/smashing/issues). For more general questions, or help with widgets, please use the [gitter chatroom](https://gitter.im/Smashing). +Feel free to submit issues for bugs, new features, and enhancements in [GitHub](https://github.com/Smashing/smashing/issues). For more general questions, or help with widgets, please use the [gitter chatroom](https://gitter.im/Smashing). ## Installation @@ -25,7 +25,7 @@ $ bundle $ smashing start ``` -[Check out our wiki](https://github.com/SmashingDashboard/smashing/wiki). +[Check out our wiki](https://github.com/Smashing/smashing/wiki). Note: This is a fork of the Dashing project, which is no longer being maintained. Read about that [here](https://github.com/Shopify/dashing/issues/711). -- cgit v1.2.3 From 1e076c2f94a1c522a26f4a4e9332800ab4278a56 Mon Sep 17 00:00:00 2001 From: Effy Elden Date: Sat, 25 Mar 2017 17:12:45 +1100 Subject: Update template README.md to have a valid link to the Smashing GitHub pages site --- templates/project/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/project/README.md b/templates/project/README.md index d041f84..7bd2471 100644 --- a/templates/project/README.md +++ b/templates/project/README.md @@ -1 +1 @@ -Check out http://smashing.github.io/ for more information. \ No newline at end of file +Check out http://smashing.github.io/smashing for more information. -- cgit v1.2.3 From 7a6d67bd2efb56fd1d75f125184a566149a52c98 Mon Sep 17 00:00:00 2001 From: Effy Elden Date: Sat, 25 Mar 2017 17:13:42 +1100 Subject: Update gemspec to have a valid link to the Smashing GitHub pages site --- smashing.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smashing.gemspec b/smashing.gemspec index 8be031c..c8f1cb2 100644 --- a/smashing.gemspec +++ b/smashing.gemspec @@ -10,7 +10,7 @@ Gem::Specification.new do |s| s.summary = "The wonderfully excellent dashboard framework." s.description = "A framework for pulling together an overview of data that is important to your team and displaying it easily on TVs around the office. You write a bit of ruby code to gather data from some services and let Smashing handle the rest - displaying that data in a wonderfully simple layout. Built for developers and hackers, Smashing is highly customizable while maintaining humble roots that make it approachable to beginners." s.author = "Daniel Beauchamp" - s.homepage = 'http://smashing.github.io' + s.homepage = 'http://smashing.github.io/smashing' s.license = "MIT" s.files = Dir['README.md', 'javascripts/**/*', 'templates/**/*','templates/**/.[a-z]*', 'lib/**/*'] -- cgit v1.2.3 From 515b1fd3bd1c4f3c782bf81a32435b336f335f80 Mon Sep 17 00:00:00 2001 From: jackhold Date: Sun, 2 Apr 2017 22:32:54 +0200 Subject: Change update time on events Changing from epoch in sec to epoch in milisec --- lib/dashing/app.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dashing/app.rb b/lib/dashing/app.rb index 0ff3085..7f8a745 100644 --- a/lib/dashing/app.rb +++ b/lib/dashing/app.rb @@ -136,7 +136,7 @@ end def send_event(id, body, target=nil) body[:id] = id - body[:updatedAt] ||= Time.now.to_i + body[:updatedAt] ||= (Time.now.to_f * 1000.0).to_i event = format_event(body.to_json, target) Sinatra::Application.settings.history[id] = event unless target == 'dashboards' Sinatra::Application.settings.connections.each { |out| out << event } -- cgit v1.2.3 From 58501a89b43ed358874d8fe30c5077c30bd6b345 Mon Sep 17 00:00:00 2001 From: Muz Date: Fri, 26 Aug 2016 11:32:18 -0500 Subject: Update Rufus-Scheduler version --- smashing.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smashing.gemspec b/smashing.gemspec index c8f1cb2..4f1fbd6 100644 --- a/smashing.gemspec +++ b/smashing.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |s| s.add_dependency('sinatra', '~> 1.4.4') s.add_dependency('sinatra-contrib', '~> 1.4.2') s.add_dependency('thin', '~> 1.6.1') - s.add_dependency('rufus-scheduler', '~> 2.0.24') + s.add_dependency('rufus-scheduler', '~> 3.2.0') s.add_dependency('thor', '~> 0.19') s.add_dependency('sprockets', '~> 2.10.1') s.add_dependency('rack', '~> 1.5.4') -- cgit v1.2.3 From 9de9d3c75ca81438b2dd812765a0997a61c0b49f Mon Sep 17 00:00:00 2001 From: Guillaume PATRY Date: Wed, 3 May 2017 11:16:46 +0200 Subject: Fix favicon url Fix the right favicon url to match the correct defaut path : /public/favicon.ico and not /assets/favicon.ico --- templates/project/dashboards/layout.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/project/dashboards/layout.erb b/templates/project/dashboards/layout.erb index eae9785..a578f0c 100644 --- a/templates/project/dashboards/layout.erb +++ b/templates/project/dashboards/layout.erb @@ -13,7 +13,7 @@ - + @@ -29,4 +29,4 @@ Save this layout <% end %> - \ No newline at end of file + -- cgit v1.2.3 From 058ec527a3ce722031cdb301dba6950cab85e859 Mon Sep 17 00:00:00 2001 From: "Bruno P. Kinoshita" Date: Mon, 29 May 2017 18:52:03 +1200 Subject: Capture Interrupt error (Ctrl+c) and cleanly exit --- lib/dashing/cli.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/dashing/cli.rb b/lib/dashing/cli.rb index 4b93f89..57d48d0 100644 --- a/lib/dashing/cli.rb +++ b/lib/dashing/cli.rb @@ -86,7 +86,11 @@ module Dashing private def run_command(command) - system(command) + begin + system(command) + rescue Interrupt => e + say "Exiting..." + end end def install_widget_from_gist(gist, skip_overwrite) -- cgit v1.2.3 From fa7cf47e28a4ac1636e947c269ed83abe49a1491 Mon Sep 17 00:00:00 2001 From: Peter Hamberg Date: Thu, 21 Sep 2017 09:08:21 +0200 Subject: don't multiply the updatedAt timestamp by 1000 The updatedAt timestamp has already been multiplied by 1000 at https://github.com/Smashing/smashing/blob/master/lib/dashing/app.rb#L141 so there is no need to multiply it again. See https://github.com/Smashing/smashing/commit/fce2ae11158aa39d1a3a5572707a997384afc6fc --- javascripts/dashing.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascripts/dashing.coffee b/javascripts/dashing.coffee index a85d595..58218ae 100644 --- a/javascripts/dashing.coffee +++ b/javascripts/dashing.coffee @@ -46,7 +46,7 @@ class Dashing.Widget extends Batman.View @accessor 'updatedAtMessage', -> if updatedAt = @get('updatedAt') - timestamp = new Date(updatedAt * 1000) + timestamp = new Date(updatedAt) hours = timestamp.getHours() minutes = ("0" + timestamp.getMinutes()).slice(-2) "Last updated at #{hours}:#{minutes}" -- cgit v1.2.3 From 11d443664b7a785b42cbbd5b96347bafa5ad273a Mon Sep 17 00:00:00 2001 From: varac Date: Wed, 13 Jul 2016 20:07:09 +0200 Subject: initial commit, import from pixelated_dashboard --- .gitignore | 3 + Gemfile | 4 +- Gemfile.lock | 143 + README.md | 60 +- assets/fonts/fontawesome-webfont.eot | Bin 0 -> 37405 bytes assets/fonts/fontawesome-webfont.svg | 399 +++ assets/fonts/fontawesome-webfont.ttf | Bin 0 -> 79076 bytes assets/fonts/fontawesome-webfont.woff | Bin 0 -> 43572 bytes assets/images/logo.png | Bin 0 -> 68147 bytes assets/javascripts/application.coffee | 25 + assets/javascripts/d3-3.2.8.js | 5 + assets/javascripts/dashing.gridster.coffee | 37 + assets/javascripts/gridster/jquery.gridster.js | 3263 ++++++++++++++++++++ .../javascripts/gridster/jquery.leanModal.min.js | 5 + assets/javascripts/jquery.knob.js | 646 ++++ assets/javascripts/rickshaw-1.4.3.min.js | 2 + assets/stylesheets/application.scss | 257 ++ assets/stylesheets/font-awesome.css | 1479 +++++++++ assets/stylesheets/jquery.gridster.css | 57 + config.ru | 23 + credentials_example | 5 + dashboards/dashboard.erb | 13 + dashboards/default.erb | 15 + dashboards/layout.erb | 51 + jobs/jenkins_build_status.rb | 73 + jobs/nagios.rb | 90 + lib/ccmenu.rb | 57 + lib/github.rb | 123 + lib/weekly_goals.rb | 22 + public/404.html | 26 + public/favicon.ico | Bin 0 -> 5430 bytes widgets/ccmenu/ccmenu.coffee | 9 + widgets/ccmenu/ccmenu.html | 7 + widgets/ccmenu/ccmenu.scss | 44 + widgets/clock/clock.coffee | 18 + widgets/clock/clock.html | 2 + widgets/clock/clock.scss | 13 + widgets/comments/comments.coffee | 24 + widgets/comments/comments.html | 7 + widgets/comments/comments.scss | 33 + widgets/github_pr/github_pr.coffee | 6 + widgets/github_pr/github_pr.html | 10 + widgets/github_pr/github_pr.scss | 57 + widgets/graph/graph.coffee | 36 + widgets/graph/graph.html | 5 + widgets/graph/graph.scss | 65 + widgets/iframe/iframe.coffee | 7 + widgets/iframe/iframe.html | 1 + widgets/iframe/iframe.scss | 8 + widgets/image/image.coffee | 9 + widgets/image/image.html | 1 + widgets/image/image.scss | 13 + .../jenkins_build_status.coffee | 28 + .../jenkins_build_status/jenkins_build_status.html | 16 + .../jenkins_build_status/jenkins_build_status.scss | 56 + widgets/list/list.coffee | 6 + widgets/list/list.html | 18 + widgets/list/list.scss | 60 + widgets/meter/meter.coffee | 16 + widgets/meter/meter.html | 7 + widgets/meter/meter.scss | 35 + widgets/nagios/nagios.coffee | 9 + widgets/nagios/nagios.html | 42 + widgets/nagios/nagios.scss | 102 + widgets/number/number.coffee | 24 + widgets/number/number.html | 11 + widgets/number/number.scss | 39 + widgets/text/text.coffee | 1 + widgets/text/text.html | 7 + widgets/text/text.scss | 32 + 70 files changed, 7740 insertions(+), 27 deletions(-) create mode 100644 Gemfile.lock create mode 100644 assets/fonts/fontawesome-webfont.eot create mode 100644 assets/fonts/fontawesome-webfont.svg create mode 100644 assets/fonts/fontawesome-webfont.ttf create mode 100644 assets/fonts/fontawesome-webfont.woff create mode 100644 assets/images/logo.png create mode 100644 assets/javascripts/application.coffee create mode 100644 assets/javascripts/d3-3.2.8.js create mode 100644 assets/javascripts/dashing.gridster.coffee create mode 100644 assets/javascripts/gridster/jquery.gridster.js create mode 100644 assets/javascripts/gridster/jquery.leanModal.min.js create mode 100644 assets/javascripts/jquery.knob.js create mode 100644 assets/javascripts/rickshaw-1.4.3.min.js create mode 100644 assets/stylesheets/application.scss create mode 100644 assets/stylesheets/font-awesome.css create mode 100644 assets/stylesheets/jquery.gridster.css create mode 100644 config.ru create mode 100644 credentials_example create mode 100644 dashboards/dashboard.erb create mode 100644 dashboards/default.erb create mode 100644 dashboards/layout.erb create mode 100644 jobs/jenkins_build_status.rb create mode 100644 jobs/nagios.rb create mode 100644 lib/ccmenu.rb create mode 100644 lib/github.rb create mode 100644 lib/weekly_goals.rb create mode 100644 public/404.html create mode 100644 public/favicon.ico create mode 100644 widgets/ccmenu/ccmenu.coffee create mode 100644 widgets/ccmenu/ccmenu.html create mode 100644 widgets/ccmenu/ccmenu.scss create mode 100644 widgets/clock/clock.coffee create mode 100644 widgets/clock/clock.html create mode 100644 widgets/clock/clock.scss create mode 100644 widgets/comments/comments.coffee create mode 100644 widgets/comments/comments.html create mode 100644 widgets/comments/comments.scss create mode 100644 widgets/github_pr/github_pr.coffee create mode 100644 widgets/github_pr/github_pr.html create mode 100644 widgets/github_pr/github_pr.scss create mode 100644 widgets/graph/graph.coffee create mode 100644 widgets/graph/graph.html create mode 100644 widgets/graph/graph.scss create mode 100644 widgets/iframe/iframe.coffee create mode 100644 widgets/iframe/iframe.html create mode 100644 widgets/iframe/iframe.scss create mode 100644 widgets/image/image.coffee create mode 100644 widgets/image/image.html create mode 100644 widgets/image/image.scss create mode 100644 widgets/jenkins_build_status/jenkins_build_status.coffee create mode 100644 widgets/jenkins_build_status/jenkins_build_status.html create mode 100644 widgets/jenkins_build_status/jenkins_build_status.scss create mode 100644 widgets/list/list.coffee create mode 100644 widgets/list/list.html create mode 100644 widgets/list/list.scss create mode 100644 widgets/meter/meter.coffee create mode 100644 widgets/meter/meter.html create mode 100644 widgets/meter/meter.scss create mode 100644 widgets/nagios/nagios.coffee create mode 100644 widgets/nagios/nagios.html create mode 100644 widgets/nagios/nagios.scss create mode 100644 widgets/number/number.coffee create mode 100644 widgets/number/number.html create mode 100644 widgets/number/number.scss create mode 100644 widgets/text/text.coffee create mode 100644 widgets/text/text.html create mode 100644 widgets/text/text.scss diff --git a/.gitignore b/.gitignore index 65cbda8..65268e8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ log/ tmp/ .ruby-version history.yml +.*.swp +assets/images/piwik.png +credentials diff --git a/Gemfile b/Gemfile index cd8aa9e..01dc836 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,5 @@ source 'https://rubygems.org' -gemspec \ No newline at end of file +gemspec + +gem "nagiosharder" diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..769e8a4 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,143 @@ +GEM + remote: https://rubygems.org/ + specs: + activesupport (5.0.0) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (~> 0.7) + minitest (~> 5.1) + tzinfo (~> 1.1) + addressable (2.4.0) + backports (3.6.8) + buftok (0.2.0) + coffee-script (2.2.0) + coffee-script-source + execjs + coffee-script-source (1.10.0) + concurrent-ruby (1.0.2) + crack (0.4.3) + safe_yaml (~> 1.0.0) + daemons (1.2.3) + dashing (1.3.7) + coffee-script (~> 2.2.0) + execjs (~> 2.0.2) + rack (~> 1.5.4) + rufus-scheduler (~> 2.0.24) + sass (~> 3.2.12) + sinatra (~> 1.4.4) + sinatra-contrib (~> 1.4.2) + sprockets (~> 2.10.1) + thin (~> 1.6.1) + thor (> 0.18.1) + domain_name (0.5.20160615) + unf (>= 0.0.5, < 1.0.0) + equalizer (0.0.10) + eventmachine (1.2.0.1) + execjs (2.0.2) + faraday (0.9.2) + multipart-post (>= 1.2, < 3) + hashie (1.2.0) + hike (1.2.3) + http (1.0.4) + addressable (~> 2.3) + http-cookie (~> 1.0) + http-form_data (~> 1.0.1) + http_parser.rb (~> 0.6.0) + http-cookie (1.0.2) + domain_name (~> 0.5) + http-form_data (1.0.1) + http_parser.rb (0.6.0) + httparty (0.13.7) + json (~> 1.8) + multi_xml (>= 0.5.2) + i18n (0.7.0) + json (1.8.3) + memoizable (0.4.2) + thread_safe (~> 0.3, >= 0.3.1) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) + mini_portile2 (2.1.0) + minitest (5.9.0) + multi_json (1.12.1) + multi_xml (0.5.5) + multipart-post (2.0.0) + nagiosharder (0.5.0) + activesupport + hashie (~> 1.2.0) + httparty + i18n + nokogiri + rest-client + terminal-table + naught (1.1.0) + netrc (0.11.0) + nokogiri (1.6.8) + mini_portile2 (~> 2.1.0) + pkg-config (~> 1.1.7) + pkg-config (1.1.7) + rack (1.5.5) + rack-protection (1.5.3) + rack + rack-test (0.6.3) + rack (>= 1.0) + rest-client (2.0.0) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 4.0) + netrc (~> 0.8) + rufus-scheduler (2.0.24) + tzinfo (>= 0.3.22) + safe_yaml (1.0.4) + sass (3.2.19) + simple_oauth (0.3.1) + sinatra (1.4.7) + rack (~> 1.5) + rack-protection (~> 1.4) + tilt (>= 1.3, < 3) + sinatra-contrib (1.4.7) + backports (>= 2.0) + multi_json + rack-protection + rack-test + sinatra (~> 1.4.0) + tilt (>= 1.3, < 3) + sprockets (2.10.2) + hike (~> 1.2) + multi_json (~> 1.0) + rack (~> 1.0) + tilt (~> 1.1, != 1.3.0) + terminal-table (1.6.0) + thin (1.6.4) + daemons (~> 1.0, >= 1.0.9) + eventmachine (~> 1.0, >= 1.0.4) + rack (~> 1.0) + thor (0.19.1) + thread_safe (0.3.5) + tilt (1.4.1) + twitter (5.16.0) + addressable (~> 2.3) + buftok (~> 0.2.0) + equalizer (= 0.0.10) + faraday (~> 0.9.0) + http (~> 1.0) + http_parser.rb (~> 0.6.0) + json (~> 1.8) + memoizable (~> 0.4.0) + naught (~> 1.0) + simple_oauth (~> 0.3.0) + tzinfo (1.2.2) + thread_safe (~> 0.1) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.2) + +PLATFORMS + ruby + +DEPENDENCIES + crack + dashing + nagiosharder + twitter + +BUNDLED WITH + 1.11.2 diff --git a/README.md b/README.md index 6ff4065..8717cfa 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,41 @@ -[![Gitter chat](https://badges.gitter.im/smashing.png)](https://gitter.im/Smashing) +# LEAP Dashboard +Check out http://shopify.github.com/dashing for more information. -# [Smashing](https://github.com/Smashing/smashing/wiki) -Smashing, the spiritual successor to [Dashing](https://github.com/Shopify/dashing), is a Sinatra based framework that lets you build excellent dashboards. It looks especially great on TVs. +## Setup -## Community +Install bundler if required -Feel free to submit issues for bugs, new features, and enhancements in [GitHub](https://github.com/Smashing/smashing/issues). For more general questions, or help with widgets, please use the [gitter chatroom](https://gitter.im/Smashing). +``` +gem install bundler +``` + +Install app dependencies using bundler: + +``` +bundle install --path=vendor/bundle +``` + +If `bundle install` fails along the way, you're probably missing Xcode command line utilities: `xcode-select --install` + +If you're getting `fatal error: 'openssl/ssl.h' file not found` installing `eventmachine` dependency, you can try install it using the following command (make sure you have openssl installed): +```bash +gem install eventmachine -v '1.0.7' -- --with-cppflags='-I/usr/local/opt/openssl/include' +``` -## Installation +To gather the data, the dashboard needs access to your GoCI account & a GitHub token. You can store them and make them available to the app like so: +```bash +cp credentials_example credentials +source credentials +``` +For this dashboard you furthermore need nodejs installed, e.g. ```bash -# Install bundler -$ gem install bundler -# Install smashing -$ gem install smashing -# Create a new project -$ smashing new my-project -# Change Directory into the project -$ cd my-project -# Install the bundle of project specific gems -$ bundle -# Start the example dashboard! -$ smashing start -``` - -[Check out our wiki](https://github.com/Smashing/smashing/wiki). - -Note: This is a fork of the Dashing project, which is no longer being maintained. Read about that [here](https://github.com/Shopify/dashing/issues/711). - -## License -Distributed under the [MIT license](MIT-LICENSE). +nodenv local 0.10.36 +``` + +Use dashing to build the dashboard or start it locally. +``` +dashing start +``` +See `dashing --help` for usage. diff --git a/assets/fonts/fontawesome-webfont.eot b/assets/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..0662cb9 Binary files /dev/null and b/assets/fonts/fontawesome-webfont.eot differ diff --git a/assets/fonts/fontawesome-webfont.svg b/assets/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..2edb4ec --- /dev/null +++ b/assets/fonts/fontawesome-webfont.svg @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/fonts/fontawesome-webfont.ttf b/assets/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..d365924 Binary files /dev/null and b/assets/fonts/fontawesome-webfont.ttf differ diff --git a/assets/fonts/fontawesome-webfont.woff b/assets/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..b9bd17e Binary files /dev/null and b/assets/fonts/fontawesome-webfont.woff differ diff --git a/assets/images/logo.png b/assets/images/logo.png new file mode 100644 index 0000000..aabf199 Binary files /dev/null and b/assets/images/logo.png differ diff --git a/assets/javascripts/application.coffee b/assets/javascripts/application.coffee new file mode 100644 index 0000000..0f27bfd --- /dev/null +++ b/assets/javascripts/application.coffee @@ -0,0 +1,25 @@ +# dashing.js is located in the dashing framework +# It includes jquery & batman for you. +#= require dashing.js + +#= require_directory . +#= require_tree ../../widgets + +console.log("Yeah! The dashboard has started!") + +Dashing.on 'ready', -> + Dashing.widget_margins ||= [5, 5] + Dashing.widget_base_dimensions ||= [225, 345] + Dashing.numColumns ||= 8 + + contentWidth = (Dashing.widget_base_dimensions[0] + Dashing.widget_margins[0] * 2) * Dashing.numColumns + + Batman.setImmediate -> + $('.gridster').width(contentWidth) + $('.gridster ul:first').gridster + widget_margins: Dashing.widget_margins + widget_base_dimensions: Dashing.widget_base_dimensions + avoid_overlapped_widgets: !Dashing.customGridsterLayout + draggable: + stop: Dashing.showGridsterInstructions + start: -> Dashing.currentWidgetPositions = Dashing.getWidgetPositions() diff --git a/assets/javascripts/d3-3.2.8.js b/assets/javascripts/d3-3.2.8.js new file mode 100644 index 0000000..91fa2eb --- /dev/null +++ b/assets/javascripts/d3-3.2.8.js @@ -0,0 +1,5 @@ +d3=function(){function n(n){return null!=n&&!isNaN(n)}function t(n){return n.length}function e(n){for(var t=1;n*t%1;)t*=10;return t}function r(n,t){try{for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}catch(r){n.prototype=t}}function i(){}function u(){}function a(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function o(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.substring(1);for(var e=0,r=Ca.length;r>e;++e){var i=Ca[e]+t;if(i in n)return i}}function c(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}function l(n){return Array.prototype.slice.call(n)}function s(){}function f(){}function h(n){function t(){for(var t,r=e,i=-1,u=r.length;++ie;e++)for(var i,u=n[e],a=0,o=u.length;o>a;a++)(i=u[a])&&t(i,a,e);return n}function C(n){return La(n,Ua),n}function z(n){var t,e;return function(r,i,u){var a,o=n[u].update,c=o.length;for(u!=e&&(e=u,t=0),i>=t&&(t=i+1);!(a=o[t])&&++t0&&(n=n.substring(0,o));var l=Va.get(n);return l&&(n=l,c=L),o?t?i:r:t?s:u}function j(n,t){return function(e){var r=ya.event;ya.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{ya.event=r}}}function L(n,t){var e=j(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function H(){var n=".dragsuppress-"+ ++Za,t="touchmove"+n,e="selectstart"+n,r="dragstart"+n,i="click"+n,u=ya.select(ba).on(t,g).on(e,g).on(r,g),a=xa.style,o=a[Xa];return a[Xa]="none",function(t){function e(){u.on(i,null)}u.on(n,null),a[Xa]=o,t&&(u.on(i,function(){g(),e()},!0),setTimeout(e,0))}}function F(n,t){var e=n.ownerSVGElement||n;if(e.createSVGPoint){var r=e.createSVGPoint();if(0>Ba&&(ba.scrollX||ba.scrollY)){e=ya.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var i=e[0][0].getScreenCTM();Ba=!(i.f||i.e),e.remove()}return Ba?(r.x=t.pageX,r.y=t.pageY):(r.x=t.clientX,r.y=t.clientY),r=r.matrixTransform(n.getScreenCTM().inverse()),[r.x,r.y]}var u=n.getBoundingClientRect();return[t.clientX-u.left-n.clientLeft,t.clientY-u.top-n.clientTop]}function P(){}function O(n,t,e){return new Y(n,t,e)}function Y(n,t,e){this.h=n,this.s=t,this.l=e}function R(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?u+(a-u)*n/60:180>n?a:240>n?u+(a-u)*(240-n)/60:u}function i(n){return Math.round(255*r(n))}var u,a;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,a=.5>=e?e*(1+t):e+t-e*t,u=2*e-a,at(i(n+120),i(n),i(n-120))}function U(n){return n>0?1:0>n?-1:0}function I(n){return n>1?0:-1>n?Ka:Math.acos(n)}function V(n){return n>1?Ka/2:-1>n?-Ka/2:Math.asin(n)}function X(n){return(Math.exp(n)-Math.exp(-n))/2}function Z(n){return(Math.exp(n)+Math.exp(-n))/2}function B(n){return(n=Math.sin(n/2))*n}function $(n,t,e){return new W(n,t,e)}function W(n,t,e){this.h=n,this.c=t,this.l=e}function J(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),G(e,Math.cos(n*=to)*t,Math.sin(n)*t)}function G(n,t,e){return new K(n,t,e)}function K(n,t,e){this.l=n,this.a=t,this.b=e}function Q(n,t,e){var r=(n+16)/116,i=r+t/500,u=r-e/200;return i=tt(i)*uo,r=tt(r)*ao,u=tt(u)*oo,at(rt(3.2404542*i-1.5371385*r-.4985314*u),rt(-.969266*i+1.8760108*r+.041556*u),rt(.0556434*i-.2040259*r+1.0572252*u))}function nt(n,t,e){return n>0?$(Math.atan2(e,t)*eo,Math.sqrt(t*t+e*e),n):$(0/0,0/0,n)}function tt(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function et(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function rt(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function it(n){return at(n>>16,255&n>>8,255&n)}function ut(n){return it(n)+""}function at(n,t,e){return new ot(n,t,e)}function ot(n,t,e){this.r=n,this.g=t,this.b=e}function ct(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function lt(n,t,e){var r,i,u,a=0,o=0,c=0;if(r=/([a-z]+)\((.*)\)/i.exec(n))switch(i=r[2].split(","),r[1]){case"hsl":return e(parseFloat(i[0]),parseFloat(i[1])/100,parseFloat(i[2])/100);case"rgb":return t(gt(i[0]),gt(i[1]),gt(i[2]))}return(u=so.get(n))?t(u.r,u.g,u.b):(null!=n&&"#"===n.charAt(0)&&(4===n.length?(a=n.charAt(1),a+=a,o=n.charAt(2),o+=o,c=n.charAt(3),c+=c):7===n.length&&(a=n.substring(1,3),o=n.substring(3,5),c=n.substring(5,7)),a=parseInt(a,16),o=parseInt(o,16),c=parseInt(c,16)),t(a,o,c))}function st(n,t,e){var r,i,u=Math.min(n/=255,t/=255,e/=255),a=Math.max(n,t,e),o=a-u,c=(a+u)/2;return o?(i=.5>c?o/(a+u):o/(2-a-u),r=n==a?(t-e)/o+(e>t?6:0):t==a?(e-n)/o+2:(n-t)/o+4,r*=60):(r=0/0,i=c>0&&1>c?0:r),O(r,i,c)}function ft(n,t,e){n=ht(n),t=ht(t),e=ht(e);var r=et((.4124564*n+.3575761*t+.1804375*e)/uo),i=et((.2126729*n+.7151522*t+.072175*e)/ao),u=et((.0193339*n+.119192*t+.9503041*e)/oo);return G(116*i-16,500*(r-i),200*(i-u))}function ht(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function gt(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function pt(n){return"function"==typeof n?n:function(){return n}}function mt(n){return n}function dt(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),vt(t,e,n,r)}}function vt(n,t,e,r){function i(){var n,t=c.status;if(!t&&c.responseText||t>=200&&300>t||304===t){try{n=e.call(u,c)}catch(r){return a.error.call(u,r),void 0}a.load.call(u,n)}else a.error.call(u,c)}var u={},a=ya.dispatch("progress","load","error"),o={},c=new XMLHttpRequest,l=null;return!ba.XDomainRequest||"withCredentials"in c||!/^(http(s)?:)?\/\//.test(n)||(c=new XDomainRequest),"onload"in c?c.onload=c.onerror=i:c.onreadystatechange=function(){c.readyState>3&&i()},c.onprogress=function(n){var t=ya.event;ya.event=n;try{a.progress.call(u,c)}finally{ya.event=t}},u.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?o[n]:(null==t?delete o[n]:o[n]=t+"",u)},u.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",u):t},u.responseType=function(n){return arguments.length?(l=n,u):l},u.response=function(n){return e=n,u},["get","post"].forEach(function(n){u[n]=function(){return u.send.apply(u,[n].concat(za(arguments)))}}),u.send=function(e,r,i){if(2===arguments.length&&"function"==typeof r&&(i=r,r=null),c.open(e,n,!0),null==t||"accept"in o||(o.accept=t+",*/*"),c.setRequestHeader)for(var a in o)c.setRequestHeader(a,o[a]);return null!=t&&c.overrideMimeType&&c.overrideMimeType(t),null!=l&&(c.responseType=l),null!=i&&u.on("error",i).on("load",function(n){i(null,n)}),c.send(null==r?null:r),u},u.abort=function(){return c.abort(),u},ya.rebind(u,a,"on"),null==r?u:u.get(yt(r))}function yt(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function Mt(){var n=bt(),t=_t()-n;t>24?(isFinite(t)&&(clearTimeout(po),po=setTimeout(Mt,t)),go=0):(go=1,vo(Mt))}function xt(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now()),mo.callback=n,mo.time=e+t}function bt(){var n=Date.now();for(mo=fo;mo;)n>=mo.time&&(mo.flush=mo.callback(n-mo.time)),mo=mo.next;return n}function _t(){for(var n,t=fo,e=1/0;t;)t.flush?t=n?n.next=t.next:fo=t.next:(t.time8?function(n){return n/e}:function(n){return n*e},symbol:n}}function St(n,t){return t-(n?Math.ceil(Math.log(n)/Math.LN10):1)}function Et(n){return n+""}function kt(){}function At(n,t,e){var r=e.s=n+t,i=r-n,u=r-i;e.t=n-u+(t-i)}function Nt(n,t){n&&qo.hasOwnProperty(n.type)&&qo[n.type](n,t)}function qt(n,t,e){var r,i=-1,u=n.length-e;for(t.lineStart();++io;++o)i.point((e=n[o])[0],e[1]);return i.lineEnd(),void 0}var c={point:e,points:n,other:null,visited:!1,entry:!0,subject:!0},l={point:e,points:[e],other:c,visited:!1,entry:!1,subject:!1};c.other=l,u.push(c),a.push(l),c={point:r,points:[r],other:null,visited:!1,entry:!1,subject:!0},l={point:r,points:[r],other:c,visited:!1,entry:!0,subject:!1},c.other=l,u.push(c),a.push(l)}}),a.sort(t),Bt(u),Bt(a),u.length){if(e)for(var o=1,c=!e(a[0].point),l=a.length;l>o;++o)a[o].entry=c=!c;for(var s,f,h,g=u[0];;){for(s=g;s.visited;)if((s=s.next)===g)return;f=s.points,i.lineStart();do{if(s.visited=s.other.visited=!0,s.entry){if(s.subject)for(var o=0;o=0;)i.point((h=f[o])[0],h[1])}else r(s.point,s.prev.point,-1,i);s=s.prev}s=s.other,f=s.points}while(!s.visited);i.lineEnd()}}}function Bt(n){if(t=n.length){for(var t,e,r=0,i=n[0];++r1&&2&t&&e.push(e.pop().concat(e.shift())),h.push(e.filter(Wt))}}var h,g,p,m=t(i),d={point:u,lineStart:o,lineEnd:c,polygonStart:function(){d.point=l,d.lineStart=s,d.lineEnd=f,h=[],g=[],i.polygonStart()},polygonEnd:function(){d.point=u,d.lineStart=o,d.lineEnd=c,h=ya.merge(h),h.length?Zt(h,Gt,null,e,i):r(g)&&(i.lineStart(),e(null,null,1,i),i.lineEnd()),i.polygonEnd(),h=g=null},sphere:function(){i.polygonStart(),i.lineStart(),e(null,null,1,i),i.lineEnd(),i.polygonEnd()}},v=Jt(),y=t(v);return d}}function Wt(n){return n.length>1}function Jt(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:s,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function Gt(n,t){return((n=n.point)[0]<0?n[1]-Ka/2-Qa:Ka/2-n[1])-((t=t.point)[0]<0?t[1]-Ka/2-Qa:Ka/2-t[1])}function Kt(n,t){var e=n[0],r=n[1],i=[Math.sin(e),-Math.cos(e),0],u=0,a=!1,o=!1,c=0;Co.reset();for(var l=0,s=t.length;s>l;++l){var f=t[l],h=f.length;if(h){for(var g=f[0],p=g[0],m=g[1]/2+Ka/4,d=Math.sin(m),v=Math.cos(m),y=1;;){y===h&&(y=0),n=f[y];var M=n[0],x=n[1]/2+Ka/4,b=Math.sin(x),_=Math.cos(x),w=M-p,S=Math.abs(w)>Ka,E=d*b;if(Co.add(Math.atan2(E*Math.sin(w),v*_+E*Math.cos(w))),Math.abs(x)=0?2:-2)*Ka:w,S^p>=e^M>=e){var k=jt(zt(g),zt(n));Ft(k);var A=jt(i,k);Ft(A);var N=(S^w>=0?-1:1)*V(A[2]);r>N&&(c+=S^w>=0?1:-1)}if(!y++)break;p=M,d=b,v=_,g=n}Math.abs(u)>Qa&&(a=!0)}}return(!o&&!a&&0>Co||-Qa>u)^1&c}function Qt(n){var t,e=0/0,r=0/0,i=0/0;return{lineStart:function(){n.lineStart(),t=1},point:function(u,a){var o=u>0?Ka:-Ka,c=Math.abs(u-e);Math.abs(c-Ka)0?Ka/2:-Ka/2),n.point(i,r),n.lineEnd(),n.lineStart(),n.point(o,r),n.point(u,r),t=0):i!==o&&c>=Ka&&(Math.abs(e-i)Qa?Math.atan((Math.sin(t)*(u=Math.cos(r))*Math.sin(e)-Math.sin(r)*(i=Math.cos(t))*Math.sin(n))/(i*u*a)):(t+r)/2}function te(n,t,e,r){var i;if(null==n)i=e*Ka/2,r.point(-Ka,i),r.point(0,i),r.point(Ka,i),r.point(Ka,0),r.point(Ka,-i),r.point(0,-i),r.point(-Ka,-i),r.point(-Ka,0),r.point(-Ka,i);else if(Math.abs(n[0]-t[0])>Qa){var u=(n[0]a}function e(n){var e,u,a,c,s;return{lineStart:function(){c=a=!1,s=1},point:function(f,h){var g,p=[f,h],m=t(f,h),d=o?m?0:i(f,h):m?i(f+(0>f?Ka:-Ka),h):0;if(!e&&(c=a=m)&&n.lineStart(),m!==a&&(g=r(e,p),(Ot(e,g)||Ot(p,g))&&(p[0]+=Qa,p[1]+=Qa,m=t(p[0],p[1]))),m!==a)s=0,m?(n.lineStart(),g=r(p,e),n.point(g[0],g[1])):(g=r(e,p),n.point(g[0],g[1]),n.lineEnd()),e=g;else if(l&&e&&o^m){var v;d&u||!(v=r(p,e,!0))||(s=0,o?(n.lineStart(),n.point(v[0][0],v[0][1]),n.point(v[1][0],v[1][1]),n.lineEnd()):(n.point(v[1][0],v[1][1]),n.lineEnd(),n.lineStart(),n.point(v[0][0],v[0][1])))}!m||e&&Ot(e,p)||n.point(p[0],p[1]),e=p,a=m,u=d},lineEnd:function(){a&&n.lineEnd(),e=null},clean:function(){return s|(c&&a)<<1}}}function r(n,t,e){var r=zt(n),i=zt(t),u=[1,0,0],o=jt(r,i),c=Dt(o,o),l=o[0],s=c-l*l;if(!s)return!e&&n;var f=a*c/s,h=-a*l/s,g=jt(u,o),p=Ht(u,f),m=Ht(o,h);Lt(p,m);var d=g,v=Dt(p,d),y=Dt(d,d),M=v*v-y*(Dt(p,p)-1);if(!(0>M)){var x=Math.sqrt(M),b=Ht(d,(-v-x)/y);if(Lt(b,p),b=Pt(b),!e)return b;var _,w=n[0],S=t[0],E=n[1],k=t[1];w>S&&(_=w,w=S,S=_);var A=S-w,N=Math.abs(A-Ka)A;if(!N&&E>k&&(_=E,E=k,k=_),q?N?E+k>0^b[1]<(Math.abs(b[0]-w)Ka^(w<=b[0]&&b[0]<=S)){var T=Ht(d,(-v+x)/y);return Lt(T,p),[b,Pt(T)]}}}function i(t,e){var r=o?n:Ka-n,i=0;return-r>t?i|=1:t>r&&(i|=2),-r>e?i|=4:e>r&&(i|=8),i}function u(n){return Kt(c,n)}var a=Math.cos(n),o=a>0,c=[n,0],l=Math.abs(a)>Qa,s=Ne(n,6*to);return $t(t,e,s,u)}function ie(n,t,e,r){function i(r,i){return Math.abs(r[0]-n)0?0:3:Math.abs(r[0]-e)0?2:1:Math.abs(r[1]-t)0?1:0:i>0?3:2}function u(n,t){return a(n.point,t.point)}function a(n,t){var e=i(n,1),r=i(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}function o(i,u){var a=u[0]-i[0],o=u[1]-i[1],c=[0,1];return Math.abs(a)0&&(i[0]+=c[0]*a,i[1]+=c[0]*o),!0):!1}return function(c){function l(u){var a=i(u,-1),o=s([0===a||3===a?n:e,a>1?r:t]);return o}function s(n){for(var t=0,e=M.length,r=n[1],i=0;e>i;++i)for(var u,a=1,o=M[i],c=o.length,l=o[0];c>a;++a)u=o[a],l[1]<=r?u[1]>r&&f(l,u,n)>0&&++t:u[1]<=r&&f(l,u,n)<0&&--t,l=u;return 0!==t}function f(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(e[0]-n[0])*(t[1]-n[1])}function h(u,o,c,l){var s=0,f=0;if(null==u||(s=i(u,c))!==(f=i(o,c))||a(u,o)<0^c>0){do l.point(0===s||3===s?n:e,s>1?r:t);while((s=(s+c+4)%4)!==f)}else l.point(o[0],o[1])}function g(i,u){return i>=n&&e>=i&&u>=t&&r>=u}function p(n,t){g(n,t)&&c.point(n,t)}function m(){T.point=v,M&&M.push(x=[]),A=!0,k=!1,S=E=0/0}function d(){y&&(v(b,_),w&&k&&q.rejoin(),y.push(q.buffer())),T.point=p,k&&c.lineEnd()}function v(n,t){n=Math.max(-Bo,Math.min(Bo,n)),t=Math.max(-Bo,Math.min(Bo,t));var e=g(n,t);if(M&&x.push([n,t]),A)b=n,_=t,w=e,A=!1,e&&(c.lineStart(),c.point(n,t));else if(e&&k)c.point(n,t);else{var r=[S,E],i=[n,t];o(r,i)?(k||(c.lineStart(),c.point(r[0],r[1])),c.point(i[0],i[1]),e||c.lineEnd()):e&&(c.lineStart(),c.point(n,t))}S=n,E=t,k=e}var y,M,x,b,_,w,S,E,k,A,N=c,q=Jt(),T={point:p,lineStart:m,lineEnd:d,polygonStart:function(){c=q,y=[],M=[]},polygonEnd:function(){c=N,(y=ya.merge(y)).length?(c.polygonStart(),Zt(y,u,l,h,c),c.polygonEnd()):s([n,t])&&(c.polygonStart(),c.lineStart(),h(null,null,1,c),c.lineEnd(),c.polygonEnd()),y=M=x=null}};return T}}function ue(n,t,e){if(Math.abs(t)=n;var r=n/t;if(t>0){if(r>e[1])return!1;r>e[0]&&(e[0]=r)}else{if(rn&&(Jo=n),n>Ko&&(Ko=n),Go>t&&(Go=t),t>Qo&&(Qo=t)}function fe(){function n(n,t){a.push("M",n,",",t,u)}function t(n,t){a.push("M",n,",",t),o.point=e}function e(n,t){a.push("L",n,",",t)}function r(){o.point=n}function i(){a.push("Z")}var u=he(4.5),a=[],o={point:n,lineStart:function(){o.point=t},lineEnd:r,polygonStart:function(){o.lineEnd=i},polygonEnd:function(){o.lineEnd=r,o.point=n},pointRadius:function(n){return u=he(n),o},result:function(){if(a.length){var n=a.join("");return a=[],n}}};return o}function he(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function ge(n,t){Lo+=n,Ho+=t,++Fo}function pe(){function n(n,r){var i=n-t,u=r-e,a=Math.sqrt(i*i+u*u);Po+=a*(t+n)/2,Oo+=a*(e+r)/2,Yo+=a,ge(t=n,e=r)}var t,e;ec.point=function(r,i){ec.point=n,ge(t=r,e=i)}}function me(){ec.point=ge}function de(){function n(n,t){var e=n-r,u=t-i,a=Math.sqrt(e*e+u*u);Po+=a*(r+n)/2,Oo+=a*(i+t)/2,Yo+=a,a=i*n-r*t,Ro+=a*(r+n),Uo+=a*(i+t),Io+=3*a,ge(r=n,i=t)}var t,e,r,i;ec.point=function(u,a){ec.point=n,ge(t=r=u,e=i=a)},ec.lineEnd=function(){n(t,e)}}function ve(n){function t(t,e){n.moveTo(t,e),n.arc(t,e,a,0,2*Ka)}function e(t,e){n.moveTo(t,e),o.point=r}function r(t,e){n.lineTo(t,e)}function i(){o.point=t}function u(){n.closePath()}var a=4.5,o={point:t,lineStart:function(){o.point=e},lineEnd:i,polygonStart:function(){o.lineEnd=u},polygonEnd:function(){o.lineEnd=i,o.point=t},pointRadius:function(n){return a=n,o},result:s};return o}function ye(n){function t(t){function r(e,r){e=n(e,r),t.point(e[0],e[1])}function i(){M=0/0,S.point=a,t.lineStart()}function a(r,i){var a=zt([r,i]),o=n(r,i);e(M,x,y,b,_,w,M=o[0],x=o[1],y=r,b=a[0],_=a[1],w=a[2],u,t),t.point(M,x)}function o(){S.point=r,t.lineEnd()}function c(){i(),S.point=l,S.lineEnd=s}function l(n,t){a(f=n,h=t),g=M,p=x,m=b,d=_,v=w,S.point=a}function s(){e(M,x,y,b,_,w,g,p,f,m,d,v,u,t),S.lineEnd=o,o()}var f,h,g,p,m,d,v,y,M,x,b,_,w,S={point:r,lineStart:i,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=c},polygonEnd:function(){t.polygonEnd(),S.lineStart=i}};return S}function e(t,u,a,o,c,l,s,f,h,g,p,m,d,v){var y=s-t,M=f-u,x=y*y+M*M;if(x>4*r&&d--){var b=o+g,_=c+p,w=l+m,S=Math.sqrt(b*b+_*_+w*w),E=Math.asin(w/=S),k=Math.abs(Math.abs(w)-1)r||Math.abs((y*T+M*C)/x-.5)>.3||i>o*g+c*p+l*m)&&(e(t,u,a,o,c,l,N,q,k,b/=S,_/=S,w,d,v),v.point(N,q),e(N,q,k,b,_,w,s,f,h,g,p,m,d,v))}}var r=.5,i=Math.cos(30*to),u=16;return t.precision=function(n){return arguments.length?(u=(r=n*n)>0&&16,t):Math.sqrt(r)},t}function Me(n){var t=ye(function(t,e){return n([t*eo,e*eo])});return function(n){return n=t(n),{point:function(t,e){n.point(t*to,e*to)},sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}}function xe(n){return be(function(){return n})()}function be(n){function t(n){return n=o(n[0]*to,n[1]*to),[n[0]*h+c,l-n[1]*h]}function e(n){return n=o.invert((n[0]-c)/h,(l-n[1])/h),n&&[n[0]*eo,n[1]*eo]}function r(){o=ae(a=Se(v,y,M),u);var n=u(m,d);return c=g-n[0]*h,l=p+n[1]*h,i()}function i(){return s&&(s.valid=!1,s=null),t}var u,a,o,c,l,s,f=ye(function(n,t){return n=u(n,t),[n[0]*h+c,l-n[1]*h]}),h=150,g=480,p=250,m=0,d=0,v=0,y=0,M=0,x=Xo,b=mt,_=null,w=null;return t.stream=function(n){return s&&(s.valid=!1),s=_e(a,x(f(b(n)))),s.valid=!0,s},t.clipAngle=function(n){return arguments.length?(x=null==n?(_=n,Xo):re((_=+n)*to),i()):_},t.clipExtent=function(n){return arguments.length?(w=n,b=null==n?mt:ie(n[0][0],n[0][1],n[1][0],n[1][1]),i()):w},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(g=+n[0],p=+n[1],r()):[g,p]},t.center=function(n){return arguments.length?(m=n[0]%360*to,d=n[1]%360*to,r()):[m*eo,d*eo]},t.rotate=function(n){return arguments.length?(v=n[0]%360*to,y=n[1]%360*to,M=n.length>2?n[2]%360*to:0,r()):[v*eo,y*eo,M*eo]},ya.rebind(t,f,"precision"),function(){return u=n.apply(this,arguments),t.invert=u.invert&&e,r()}}function _e(n,t){return{point:function(e,r){r=n(e*to,r*to),e=r[0],t.point(e>Ka?e-2*Ka:-Ka>e?e+2*Ka:e,r[1])},sphere:function(){t.sphere()},lineStart:function(){t.lineStart()},lineEnd:function(){t.lineEnd()},polygonStart:function(){t.polygonStart()},polygonEnd:function(){t.polygonEnd()}}}function we(n,t){return[n,t]}function Se(n,t,e){return n?t||e?ae(ke(n),Ae(t,e)):ke(n):t||e?Ae(t,e):we}function Ee(n){return function(t,e){return t+=n,[t>Ka?t-2*Ka:-Ka>t?t+2*Ka:t,e]}}function ke(n){var t=Ee(n);return t.invert=Ee(-n),t}function Ae(n,t){function e(n,t){var e=Math.cos(t),o=Math.cos(n)*e,c=Math.sin(n)*e,l=Math.sin(t),s=l*r+o*i;return[Math.atan2(c*u-s*a,o*r-l*i),V(s*u+c*a)]}var r=Math.cos(n),i=Math.sin(n),u=Math.cos(t),a=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),o=Math.cos(n)*e,c=Math.sin(n)*e,l=Math.sin(t),s=l*u-c*a;return[Math.atan2(c*u+l*a,o*r+s*i),V(s*r-o*i)]},e}function Ne(n,t){var e=Math.cos(n),r=Math.sin(n);return function(i,u,a,o){null!=i?(i=qe(e,i),u=qe(e,u),(a>0?u>i:i>u)&&(i+=2*a*Ka)):(i=n+2*a*Ka,u=n);for(var c,l=a*t,s=i;a>0?s>u:u>s;s-=l)o.point((c=Pt([e,-r*Math.cos(s),-r*Math.sin(s)]))[0],c[1])}}function qe(n,t){var e=zt(t);e[0]-=n,Ft(e);var r=I(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Qa)%(2*Math.PI)}function Te(n,t,e){var r=ya.range(n,t-Qa,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function Ce(n,t,e){var r=ya.range(n,t-Qa,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function ze(n){return n.source}function De(n){return n.target}function je(n,t,e,r){var i=Math.cos(t),u=Math.sin(t),a=Math.cos(r),o=Math.sin(r),c=i*Math.cos(n),l=i*Math.sin(n),s=a*Math.cos(e),f=a*Math.sin(e),h=2*Math.asin(Math.sqrt(B(r-t)+i*a*B(e-n))),g=1/Math.sin(h),p=h?function(n){var t=Math.sin(n*=h)*g,e=Math.sin(h-n)*g,r=e*c+t*s,i=e*l+t*f,a=e*u+t*o;return[Math.atan2(i,r)*eo,Math.atan2(a,Math.sqrt(r*r+i*i))*eo]}:function(){return[n*eo,t*eo]};return p.distance=h,p}function Le(){function n(n,i){var u=Math.sin(i*=to),a=Math.cos(i),o=Math.abs((n*=to)-t),c=Math.cos(o);rc+=Math.atan2(Math.sqrt((o=a*Math.sin(o))*o+(o=r*u-e*a*c)*o),e*u+r*a*c),t=n,e=u,r=a}var t,e,r;ic.point=function(i,u){t=i*to,e=Math.sin(u*=to),r=Math.cos(u),ic.point=n},ic.lineEnd=function(){ic.point=ic.lineEnd=s}}function He(n,t){function e(t,e){var r=Math.cos(t),i=Math.cos(e),u=n(r*i);return[u*i*Math.sin(t),u*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),i=t(r),u=Math.sin(i),a=Math.cos(i);return[Math.atan2(n*u,r*a),Math.asin(r&&e*u/r)]},e}function Fe(n,t){function e(n,t){var e=Math.abs(Math.abs(t)-Ka/2)1&&i.push("H",r[0]),i.join("")}function $e(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1){o=t[1],u=n[c],c++,r+="C"+(i[0]+a[0])+","+(i[1]+a[1])+","+(u[0]-o[0])+","+(u[1]-o[1])+","+u[0]+","+u[1];for(var l=2;l9&&(i=3*t/Math.sqrt(i),a[o]=i*e,a[o+1]=i*r));for(o=-1;++o<=c;)i=(n[Math.min(c,o+1)][0]-n[Math.max(0,o-1)][0])/(6*(1+a[o]*a[o])),u.push([i||0,a[o]*i||0]);return u}function sr(n){return n.length<3?Xe(n):n[0]+Qe(n,lr(n))}function fr(n,t,e,r){var i,u,a,o,c,l,s;return i=r[n],u=i[0],a=i[1],i=r[t],o=i[0],c=i[1],i=r[e],l=i[0],s=i[1],(s-a)*(o-u)-(c-a)*(l-u)>0}function hr(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function gr(n,t,e,r){var i=n[0],u=e[0],a=t[0]-i,o=r[0]-u,c=n[1],l=e[1],s=t[1]-c,f=r[1]-l,h=(o*(c-l)-f*(i-u))/(f*a-o*s);return[i+h*a,c+h*s]}function pr(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function mr(n,t){var e={list:n.map(function(n,t){return{index:t,x:n[0],y:n[1]}}).sort(function(n,t){return n.yt.y?1:n.xt.x?1:0}),bottomSite:null},r={list:[],leftEnd:null,rightEnd:null,init:function(){r.leftEnd=r.createHalfEdge(null,"l"),r.rightEnd=r.createHalfEdge(null,"l"),r.leftEnd.r=r.rightEnd,r.rightEnd.l=r.leftEnd,r.list.unshift(r.leftEnd,r.rightEnd)},createHalfEdge:function(n,t){return{edge:n,side:t,vertex:null,l:null,r:null}},insert:function(n,t){t.l=n,t.r=n.r,n.r.l=t,n.r=t},leftBound:function(n){var t=r.leftEnd;do t=t.r;while(t!=r.rightEnd&&i.rightOf(t,n));return t=t.l},del:function(n){n.l.r=n.r,n.r.l=n.l,n.edge=null},right:function(n){return n.r},left:function(n){return n.l},leftRegion:function(n){return null==n.edge?e.bottomSite:n.edge.region[n.side]},rightRegion:function(n){return null==n.edge?e.bottomSite:n.edge.region[mc[n.side]]}},i={bisect:function(n,t){var e={region:{l:n,r:t},ep:{l:null,r:null}},r=t.x-n.x,i=t.y-n.y,u=r>0?r:-r,a=i>0?i:-i;return e.c=n.x*r+n.y*i+.5*(r*r+i*i),u>a?(e.a=1,e.b=i/r,e.c/=r):(e.b=1,e.a=r/i,e.c/=i),e},intersect:function(n,t){var e=n.edge,r=t.edge;if(!e||!r||e.region.r==r.region.r)return null;var i=e.a*r.b-e.b*r.a;if(Math.abs(i)<1e-10)return null;var u,a,o=(e.c*r.b-r.c*e.b)/i,c=(r.c*e.a-e.c*r.a)/i,l=e.region.r,s=r.region.r;l.y=a.region.r.x;return f&&"l"===u.side||!f&&"r"===u.side?null:{x:o,y:c}},rightOf:function(n,t){var e=n.edge,r=e.region.r,i=t.x>r.x;if(i&&"l"===n.side)return 1;if(!i&&"r"===n.side)return 0;if(1===e.a){var u=t.y-r.y,a=t.x-r.x,o=0,c=0;if(!i&&e.b<0||i&&e.b>=0?c=o=u>=e.b*a:(c=t.x+t.y*e.b>e.c,e.b<0&&(c=!c),c||(o=1)),!o){var l=r.x-e.region.l.x;c=e.b*(a*a-u*u)h*h+g*g}return"l"===n.side?c:!c},endPoint:function(n,e,r){n.ep[e]=r,n.ep[mc[e]]&&t(n)},distance:function(n,t){var e=n.x-t.x,r=n.y-t.y;return Math.sqrt(e*e+r*r)}},u={list:[],insert:function(n,t,e){n.vertex=t,n.ystar=t.y+e;for(var r=0,i=u.list,a=i.length;a>r;r++){var o=i[r];if(!(n.ystar>o.ystar||n.ystar==o.ystar&&t.x>o.vertex.x))break}i.splice(r,0,n)},del:function(n){for(var t=0,e=u.list,r=e.length;r>t&&e[t]!=n;++t);e.splice(t,1)},empty:function(){return 0===u.list.length},nextEvent:function(n){for(var t=0,e=u.list,r=e.length;r>t;++t)if(e[t]==n)return e[t+1];return null},min:function(){var n=u.list[0];return{x:n.vertex.x,y:n.ystar}},extractMin:function(){return u.list.shift()}};r.init(),e.bottomSite=e.list.shift();for(var a,o,c,l,s,f,h,g,p,m,d,v,y,M=e.list.shift();;)if(u.empty()||(a=u.min()),M&&(u.empty()||M.yg.y&&(p=h,h=g,g=p,y="r"),v=i.bisect(h,g),f=r.createHalfEdge(v,y),r.insert(l,f),i.endPoint(v,mc[y],d),m=i.intersect(l,f),m&&(u.del(l),u.insert(l,m,i.distance(m,h))),m=i.intersect(f,s),m&&u.insert(f,m,i.distance(m,h))}for(o=r.right(r.leftEnd);o!=r.rightEnd;o=r.right(o))t(o.edge)}function dr(n){return n.x}function vr(n){return n.y}function yr(){return{leaf:!0,nodes:[],point:null,x:null,y:null}}function Mr(n,t,e,r,i,u){if(!n(t,e,r,i,u)){var a=.5*(e+i),o=.5*(r+u),c=t.nodes;c[0]&&Mr(n,c[0],e,r,a,o),c[1]&&Mr(n,c[1],a,r,i,o),c[2]&&Mr(n,c[2],e,o,a,u),c[3]&&Mr(n,c[3],a,o,i,u)}}function xr(n,t){n=ya.rgb(n),t=ya.rgb(t);var e=n.r,r=n.g,i=n.b,u=t.r-e,a=t.g-r,o=t.b-i;return function(n){return"#"+ct(Math.round(e+u*n))+ct(Math.round(r+a*n))+ct(Math.round(i+o*n))}}function br(n,t){var e,r={},i={};for(e in n)e in t?r[e]=Sr(n[e],t[e]):i[e]=n[e];for(e in t)e in n||(i[e]=t[e]);return function(n){for(e in r)i[e]=r[e](n);return i}}function _r(n,t){return t-=n=+n,function(e){return n+t*e}}function wr(n,t){var e,r,i,u,a,o=0,c=0,l=[],s=[];for(n+="",t+="",dc.lastIndex=0,r=0;e=dc.exec(t);++r)e.index&&l.push(t.substring(o,c=e.index)),s.push({i:l.length,x:e[0]}),l.push(null),o=dc.lastIndex;for(or;++r)if(a=s[r],a.x==e[0]){if(a.i)if(null==l[a.i+1])for(l[a.i-1]+=a.x,l.splice(a.i,1),i=r+1;u>i;++i)s[i].i--;else for(l[a.i-1]+=a.x+l[a.i+1],l.splice(a.i,2),i=r+1;u>i;++i)s[i].i-=2;else if(null==l[a.i+1])l[a.i]=a.x;else for(l[a.i]=a.x+l[a.i+1],l.splice(a.i+1,1),i=r+1;u>i;++i)s[i].i--;s.splice(r,1),u--,r--}else a.x=_r(parseFloat(e[0]),parseFloat(a.x));for(;u>r;)a=s.pop(),null==l[a.i+1]?l[a.i]=a.x:(l[a.i]=a.x+l[a.i+1],l.splice(a.i+1,1)),u--;return 1===l.length?null==l[0]?(a=s[0].x,function(n){return a(n)+""}):function(){return t}:function(n){for(r=0;u>r;++r)l[(a=s[r]).i]=a.x(n);return l.join("")}}function Sr(n,t){for(var e,r=ya.interpolators.length;--r>=0&&!(e=ya.interpolators[r](n,t)););return e}function Er(n,t){var e,r=[],i=[],u=n.length,a=t.length,o=Math.min(n.length,t.length);for(e=0;o>e;++e)r.push(Sr(n[e],t[e]));for(;u>e;++e)i[e]=n[e];for(;a>e;++e)i[e]=t[e];return function(n){for(e=0;o>e;++e)i[e]=r[e](n);return i}}function kr(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function Ar(n){return function(t){return 1-n(1-t)}}function Nr(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function qr(n){return n*n}function Tr(n){return n*n*n}function Cr(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function zr(n){return function(t){return Math.pow(t,n)}}function Dr(n){return 1-Math.cos(n*Ka/2)}function jr(n){return Math.pow(2,10*(n-1))}function Lr(n){return 1-Math.sqrt(1-n*n)}function Hr(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/(2*Ka)*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,10*-r)*Math.sin(2*(r-e)*Ka/t)}}function Fr(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function Pr(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Or(n,t){n=ya.hcl(n),t=ya.hcl(t);var e=n.h,r=n.c,i=n.l,u=t.h-e,a=t.c-r,o=t.l-i;return isNaN(a)&&(a=0,r=isNaN(r)?t.c:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return J(e+u*n,r+a*n,i+o*n)+""}}function Yr(n,t){n=ya.hsl(n),t=ya.hsl(t);var e=n.h,r=n.s,i=n.l,u=t.h-e,a=t.s-r,o=t.l-i;return isNaN(a)&&(a=0,r=isNaN(r)?t.s:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return R(e+u*n,r+a*n,i+o*n)+""}}function Rr(n,t){n=ya.lab(n),t=ya.lab(t);var e=n.l,r=n.a,i=n.b,u=t.l-e,a=t.a-r,o=t.b-i;return function(n){return Q(e+u*n,r+a*n,i+o*n)+""}}function Ur(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function Ir(n){var t=[n.a,n.b],e=[n.c,n.d],r=Xr(t),i=Vr(t,e),u=Xr(Zr(e,t,-i))||0;t[0]*e[1]180?s+=360:s-l>180&&(l+=360),i.push({i:r.push(r.pop()+"rotate(",null,")")-2,x:_r(l,s)})):s&&r.push(r.pop()+"rotate("+s+")"),f!=h?i.push({i:r.push(r.pop()+"skewX(",null,")")-2,x:_r(f,h)}):h&&r.push(r.pop()+"skewX("+h+")"),g[0]!=p[0]||g[1]!=p[1]?(e=r.push(r.pop()+"scale(",null,",",null,")"),i.push({i:e-4,x:_r(g[0],p[0])},{i:e-2,x:_r(g[1],p[1])})):(1!=p[0]||1!=p[1])&&r.push(r.pop()+"scale("+p+")"),e=i.length,function(n){for(var t,u=-1;++ue;++e)(t=n[e][1])>i&&(r=e,i=t);return r}function mi(n){return n.reduce(di,0)}function di(n,t){return n+t[1]}function vi(n,t){return yi(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function yi(n,t){for(var e=-1,r=+n[0],i=(n[1]-r)/t,u=[];++e<=t;)u[e]=i*e+r;return u}function Mi(n){return[ya.min(n),ya.max(n)]}function xi(n,t){return n.parent==t.parent?1:2}function bi(n){var t=n.children;return t&&t.length?t[0]:n._tree.thread}function _i(n){var t,e=n.children;return e&&(t=e.length)?e[t-1]:n._tree.thread}function wi(n,t){var e=n.children;if(e&&(i=e.length))for(var r,i,u=-1;++u0&&(n=r);return n}function Si(n,t){return n.x-t.x}function Ei(n,t){return t.x-n.x}function ki(n,t){return n.depth-t.depth}function Ai(n,t){function e(n,r){var i=n.children;if(i&&(a=i.length))for(var u,a,o=null,c=-1;++c=0;)t=i[u]._tree,t.prelim+=e,t.mod+=e,e+=t.shift+(r+=t.change)}function qi(n,t,e){n=n._tree,t=t._tree;var r=e/(t.number-n.number);n.change+=r,t.change-=r,t.shift+=e,t.prelim+=e,t.mod+=e}function Ti(n,t,e){return n._tree.ancestor.parent==t.parent?n._tree.ancestor:e}function Ci(n,t){return n.value-t.value}function zi(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Di(n,t){n._pack_next=t,t._pack_prev=n}function ji(n,t){var e=t.x-n.x,r=t.y-n.y,i=n.r+t.r;return.999*i*i>e*e+r*r}function Li(n){function t(n){s=Math.min(n.x-n.r,s),f=Math.max(n.x+n.r,f),h=Math.min(n.y-n.r,h),g=Math.max(n.y+n.r,g)}if((e=n.children)&&(l=e.length)){var e,r,i,u,a,o,c,l,s=1/0,f=-1/0,h=1/0,g=-1/0;if(e.forEach(Hi),r=e[0],r.x=-r.r,r.y=0,t(r),l>1&&(i=e[1],i.x=i.r,i.y=0,t(i),l>2))for(u=e[2],Oi(r,i,u),t(u),zi(r,u),r._pack_prev=u,zi(u,i),i=r._pack_next,a=3;l>a;a++){Oi(r,i,u=e[a]);var p=0,m=1,d=1;for(o=i._pack_next;o!==i;o=o._pack_next,m++)if(ji(o,u)){p=1;break}if(1==p)for(c=r._pack_prev;c!==o._pack_prev&&!ji(c,u);c=c._pack_prev,d++);p?(d>m||m==d&&i.ra;a++)u=e[a],u.x-=v,u.y-=y,M=Math.max(M,u.r+Math.sqrt(u.x*u.x+u.y*u.y));n.r=M,e.forEach(Fi)}}function Hi(n){n._pack_next=n._pack_prev=n}function Fi(n){delete n._pack_next,delete n._pack_prev}function Pi(n,t,e,r){var i=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,i)for(var u=-1,a=i.length;++ui&&(e+=i/2,i=0),0>u&&(r+=u/2,u=0),{x:e,y:r,dx:i,dy:u}}function Zi(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Bi(n){return n.rangeExtent?n.rangeExtent():Zi(n.range())}function $i(n,t,e,r){var i=e(n[0],n[1]),u=r(t[0],t[1]);return function(n){return u(i(n))}}function Wi(n,t){var e,r=0,i=n.length-1,u=n[r],a=n[i];return u>a&&(e=r,r=i,i=e,e=u,u=a,a=e),n[r]=t.floor(u),n[i]=t.ceil(a),n}function Ji(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:kc}function Gi(n,t,e,r){var i=[],u=[],a=0,o=Math.min(n.length,t.length)-1;for(n[o]2?Gi:$i,c=r?Wr:$r;return a=i(n,t,c,e),o=i(t,n,c,Sr),u}function u(n){return a(n)}var a,o;return u.invert=function(n){return o(n)},u.domain=function(t){return arguments.length?(n=t.map(Number),i()):n},u.range=function(n){return arguments.length?(t=n,i()):t},u.rangeRound=function(n){return u.range(n).interpolate(Ur)},u.clamp=function(n){return arguments.length?(r=n,i()):r},u.interpolate=function(n){return arguments.length?(e=n,i()):e},u.ticks=function(t){return ru(n,t)},u.tickFormat=function(t,e){return iu(n,t,e)},u.nice=function(t){return nu(n,t),i()},u.copy=function(){return Ki(n,t,e,r)},i()}function Qi(n,t){return ya.rebind(n,t,"range","rangeRound","interpolate","clamp")}function nu(n,t){return Wi(n,Ji(t?eu(n,t)[2]:tu(n)))}function tu(n){var t=Zi(n),e=t[1]-t[0];return Math.pow(10,Math.round(Math.log(e)/Math.LN10)-1)}function eu(n,t){var e=Zi(n),r=e[1]-e[0],i=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),u=t/r*i;return.15>=u?i*=10:.35>=u?i*=5:.75>=u&&(i*=2),e[0]=Math.ceil(e[0]/i)*i,e[1]=Math.floor(e[1]/i)*i+.5*i,e[2]=i,e}function ru(n,t){return ya.range.apply(ya,eu(n,t))}function iu(n,t,e){var r=-Math.floor(Math.log(eu(n,t)[2])/Math.LN10+.01);return ya.format(e?e.replace(wo,function(n,t,e,i,u,a,o,c,l,s){return[t,e,i,u,a,o,c,l||"."+(r-2*("%"===s)),s].join("")}):",."+r+"f")}function uu(n,t,e,r){function i(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function u(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function a(t){return n(i(t))}return a.invert=function(t){return u(n.invert(t))},a.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(i)),a):r},a.base=function(e){return arguments.length?(t=+e,n.domain(r.map(i)),a):t},a.nice=function(){var t=Wi(r.map(i),e?Math:Nc);return n.domain(t),r=t.map(u),a},a.ticks=function(){var n=Zi(r),a=[],o=n[0],c=n[1],l=Math.floor(i(o)),s=Math.ceil(i(c)),f=t%1?2:t;if(isFinite(s-l)){if(e){for(;s>l;l++)for(var h=1;f>h;h++)a.push(u(l)*h);a.push(u(l))}else for(a.push(u(l));l++0;h--)a.push(u(l)*h);for(l=0;a[l]c;s--);a=a.slice(l,s)}return a},a.tickFormat=function(n,t){if(!arguments.length)return Ac;arguments.length<2?t=Ac:"function"!=typeof t&&(t=ya.format(t));var r,o=Math.max(.1,n/a.ticks().length),c=e?(r=1e-12,Math.ceil):(r=-1e-12,Math.floor);return function(n){return n/u(c(i(n)+r))<=o?t(n):""}},a.copy=function(){return uu(n.copy(),t,e,r)},Qi(a,n)}function au(n,t,e){function r(t){return n(i(t))}var i=ou(t),u=ou(1/t);return r.invert=function(t){return u(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(i)),r):e},r.ticks=function(n){return ru(e,n)},r.tickFormat=function(n,t){return iu(e,n,t)},r.nice=function(n){return r.domain(nu(e,n))},r.exponent=function(a){return arguments.length?(i=ou(t=a),u=ou(1/t),n.domain(e.map(i)),r):t},r.copy=function(){return au(n.copy(),t,e)},Qi(r,n)}function ou(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function cu(n,t){function e(t){return a[((u.get(t)||u.set(t,n.push(t)))-1)%a.length]}function r(t,e){return ya.range(n.length).map(function(n){return t+e*n})}var u,a,o;return e.domain=function(r){if(!arguments.length)return n;n=[],u=new i;for(var a,o=-1,c=r.length;++oe?[0/0,0/0]:[e>0?i[e-1]:n[0],et?0/0:t/u+n,[t,t+1/u]},r.copy=function(){return su(n,t,e)},i()}function fu(n,t){function e(e){return e>=e?t[ya.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return fu(n,t)},e}function hu(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return ru(n,t)},t.tickFormat=function(t,e){return iu(n,t,e)},t.copy=function(){return hu(n)},t}function gu(n){return n.innerRadius}function pu(n){return n.outerRadius}function mu(n){return n.startAngle}function du(n){return n.endAngle}function vu(n){for(var t,e,r,i=-1,u=n.length;++ie?l():(u.active=e,a.event&&a.event.start.call(n,s,t),a.tween.forEach(function(e,r){(r=r.call(n,s,t))&&p.push(r)}),c(r)?1:(xt(c,0,o),void 0))}function c(r){if(u.active!==e)return l();for(var i=(r-h)/g,o=f(i),c=p.length;c>0;)p[--c].call(n,o);return i>=1?(l(),a.event&&a.event.end.call(n,s,t),1):void 0}function l(){return--u.count?delete u[e]:delete n.__transition__,1}var s=n.__data__,f=a.ease,h=a.delay,g=a.duration,p=[];return r>=h?i(r):(xt(i,h,o),void 0)},0,o)}}function qu(n,t){n.attr("transform",function(n){return"translate("+t(n)+",0)"})}function Tu(n,t){n.attr("transform",function(n){return"translate(0,"+t(n)+")"})}function Cu(n,t,e){if(r=[],e&&t.length>1){for(var r,i,u,a=Zi(n.domain()),o=-1,c=t.length,l=(t[1]-t[0])/++e;++o0;)(u=+t[o]-i*l)>=a[0]&&r.push(u);for(--o,i=0;++i1?Date.UTC.apply(this,arguments):arguments[0])}function Du(n,t,e){function r(t){var e=n(t),r=u(e,1);return r-t>t-e?e:r}function i(e){return t(e=n(new Zc(e-1)),1),e}function u(n,e){return t(n=new Zc(+n),e),n}function a(n,r,u){var a=i(n),o=[];if(u>1)for(;r>a;)e(a)%u||o.push(new Date(+a)),t(a,1);else for(;r>a;)o.push(new Date(+a)),t(a,1);return o}function o(n,t,e){try{Zc=zu;var r=new zu;return r._=n,a(r,t,e)}finally{Zc=Date}}n.floor=n,n.round=r,n.ceil=i,n.offset=u,n.range=a;var c=n.utc=ju(n);return c.floor=c,c.round=ju(r),c.ceil=ju(i),c.offset=ju(u),c.range=o,n}function ju(n){return function(t,e){try{Zc=zu;var r=new zu;return r._=t,n(r,e)._}finally{Zc=Date}}}function Lu(n,t,e,r){for(var i,u,a=0,o=t.length,c=e.length;o>a;){if(r>=c)return-1;if(i=t.charCodeAt(a++),37===i){if(u=gl[t.charAt(a++)],!u||(r=u(n,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}function Hu(n){return new RegExp("^(?:"+n.map(ya.requote).join("|")+")","i")}function Fu(n){for(var t=new i,e=-1,r=n.length;++en?"-":"",i=(r?-n:n)+"",u=i.length;return r+(e>u?new Array(e-u+1).join(t)+i:i)}function Ou(n,t,e){il.lastIndex=0;var r=il.exec(t.substring(e));return r?(n.w=ul.get(r[0].toLowerCase()),e+r[0].length):-1}function Yu(n,t,e){el.lastIndex=0;var r=el.exec(t.substring(e));return r?(n.w=rl.get(r[0].toLowerCase()),e+r[0].length):-1}function Ru(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e,e+1));return r?(n.w=+r[0],e+r[0].length):-1}function Uu(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e));return r?(n.U=+r[0],e+r[0].length):-1}function Iu(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e));return r?(n.W=+r[0],e+r[0].length):-1}function Vu(n,t,e){cl.lastIndex=0;var r=cl.exec(t.substring(e));return r?(n.m=ll.get(r[0].toLowerCase()),e+r[0].length):-1}function Xu(n,t,e){al.lastIndex=0;var r=al.exec(t.substring(e));return r?(n.m=ol.get(r[0].toLowerCase()),e+r[0].length):-1}function Zu(n,t,e){return Lu(n,hl.c.toString(),t,e)}function Bu(n,t,e){return Lu(n,hl.x.toString(),t,e)}function $u(n,t,e){return Lu(n,hl.X.toString(),t,e)}function Wu(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e,e+4));return r?(n.y=+r[0],e+r[0].length):-1}function Ju(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e,e+2));return r?(n.y=Gu(+r[0]),e+r[0].length):-1}function Gu(n){return n+(n>68?1900:2e3)}function Ku(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function Qu(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function na(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function ta(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function ea(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function ra(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function ia(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function ua(n,t,e){var r=ml.get(t.substring(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}function aa(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=~~(Math.abs(t)/60),i=Math.abs(t)%60;return e+Pu(r,"0",2)+Pu(i,"0",2)}function oa(n,t,e){sl.lastIndex=0;var r=sl.exec(t.substring(e,e+1));return r?e+r[0].length:-1}function ca(n){return n.toISOString()}function la(n,t,e){function r(t){return n(t)}return r.invert=function(t){return sa(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain(t),r):n.domain().map(sa)},r.nice=function(n){return r.domain(Wi(r.domain(),n))},r.ticks=function(e,i){var u=Zi(r.domain());if("function"!=typeof e){var a=u[1]-u[0],o=a/e,c=ya.bisect(vl,o);if(c==vl.length)return t.year(u,e);if(!c)return n.ticks(e).map(sa);o/vl[c-1]n?-1:n>t?1:n>=t?0:0/0},ya.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:0/0},ya.min=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=e);)e=void 0;for(;++ir&&(e=r)}else{for(;++i=e);)e=void 0;for(;++ir&&(e=r)}return e},ya.max=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=e);)e=void 0;for(;++ie&&(e=r)}else{for(;++i=e);)e=void 0;for(;++ie&&(e=r)}return e},ya.extent=function(n,t){var e,r,i,u=-1,a=n.length;if(1===arguments.length){for(;++u=e);)e=i=void 0;for(;++ur&&(e=r),r>i&&(i=r))}else{for(;++u=e);)e=void 0;for(;++ur&&(e=r),r>i&&(i=r))}return[e,i]},ya.sum=function(n,t){var e,r=0,i=n.length,u=-1;if(1===arguments.length)for(;++u1&&(t=t.map(e)),t=t.filter(n),t.length?ya.quantile(t.sort(ya.ascending),.5):void 0},ya.bisector=function(n){return{left:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n.call(t,t[u],u)r;){var u=r+i>>>1;er)for(;(i=n+r*++o)>t;)u.push(i/a);else for(;(i=n+r*++o)=a.length)return r?r.call(u,o):e?o.sort(e):o;for(var l,s,f,h,g=-1,p=o.length,m=a[c++],d=new i;++g=a.length)return n;var r=[],i=o[e++];return n.forEach(function(n,i){r.push({key:n,values:t(i,e)})}),i?r.sort(function(n,t){return i(n.key,t.key)}):r}var e,r,u={},a=[],o=[];return u.map=function(t,e){return n(e,t,0)},u.entries=function(e){return t(n(ya.map,e,0),0)},u.key=function(n){return a.push(n),u},u.sortKeys=function(n){return o[a.length-1]=n,u},u.sortValues=function(n){return e=n,u},u.rollup=function(n){return r=n,u},u},ya.set=function(n){var t=new u;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},r(u,{has:function(n){return qa+n in this},add:function(n){return this[qa+n]=!0,n},remove:function(n){return n=qa+n,n in this&&delete this[n]},values:function(){var n=[];return this.forEach(function(t){n.push(t)}),n},forEach:function(n){for(var t in this)t.charCodeAt(0)===Ta&&n.call(this,t.substring(1))}}),ya.behavior={},ya.rebind=function(n,t){for(var e,r=1,i=arguments.length;++r=0&&(r=n.substring(e+1),n=n.substring(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},ya.event=null,ya.requote=function(n){return n.replace(ja,"\\$&")};var ja=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,La={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},Ha=function(n,t){return t.querySelector(n)},Fa=function(n,t){return t.querySelectorAll(n)},Pa=xa[o(xa,"matchesSelector")],Oa=function(n,t){return Pa.call(n,t)};"function"==typeof Sizzle&&(Ha=function(n,t){return Sizzle(n,t)[0]||null},Fa=function(n,t){return Sizzle.uniqueSort(Sizzle(n,t))},Oa=Sizzle.matchesSelector),ya.selection=function(){return Ia};var Ya=ya.selection.prototype=[];Ya.select=function(n){var t,e,r,i,u=[];n=v(n);for(var a=-1,o=this.length;++a=0&&(e=n.substring(0,t),n=n.substring(t+1)),Ra.hasOwnProperty(e)?{space:Ra[e],local:n}:n}},Ya.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=ya.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(M(t,n[t]));return this}return this.each(M(n,t))},Ya.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=n.trim().split(/^|\s+/g)).length,i=-1;if(t=e.classList){for(;++ir){if("string"!=typeof n){2>r&&(t="");for(e in n)this.each(S(e,n[e],t));return this}if(2>r)return ba.getComputedStyle(this.node(),null).getPropertyValue(n);e=""}return this.each(S(n,t,e))},Ya.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(E(t,n[t]));return this}return this.each(E(n,t))},Ya.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},Ya.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},Ya.append=function(n){return n=k(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},Ya.insert=function(n,t){return n=k(n),t=v(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments))})},Ya.remove=function(){return this.each(function(){var n=this.parentNode;n&&n.removeChild(this)})},Ya.data=function(n,t){function e(n,e){var r,u,a,o=n.length,f=e.length,h=Math.min(o,f),g=new Array(f),p=new Array(f),m=new Array(o);if(t){var d,v=new i,y=new i,M=[];for(r=-1;++rr;++r)p[r]=A(e[r]);for(;o>r;++r)m[r]=n[r]}p.update=g,p.parentNode=g.parentNode=m.parentNode=n.parentNode,c.push(p),l.push(g),s.push(m)}var r,u,a=-1,o=this.length;if(!arguments.length){for(n=new Array(o=(r=this[0]).length);++au;u++){i.push(t=[]),t.parentNode=(e=this[u]).parentNode;for(var o=0,c=e.length;c>o;o++)(r=e[o])&&n.call(r,r.__data__,o)&&t.push(r)}return d(i)},Ya.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[i])&&(u&&u!==e.nextSibling&&u.parentNode.insertBefore(e,u),u=e);return this},Ya.sort=function(n){n=q.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,i=e.length;i>r;r++){var u=e[r];if(u)return u}return null},Ya.size=function(){var n=0;return this.each(function(){++n}),n};var Ua=[];ya.selection.enter=C,ya.selection.enter.prototype=Ua,Ua.append=Ya.append,Ua.empty=Ya.empty,Ua.node=Ya.node,Ua.call=Ya.call,Ua.size=Ya.size,Ua.select=function(n){for(var t,e,r,i,u,a=[],o=-1,c=this.length;++or){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(D(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(D(n,t,e))};var Va=ya.map({mouseenter:"mouseover",mouseleave:"mouseout"});Va.forEach(function(n){"on"+n in Ma&&Va.remove(n)});var Xa=o(xa.style,"userSelect"),Za=0;ya.mouse=function(n){return F(n,p())};var Ba=/WebKit/.test(ba.navigator.userAgent)?-1:0;ya.touches=function(n,t){return arguments.length<2&&(t=p().touches),t?za(t).map(function(t){var e=F(n,t);return e.identifier=t.identifier,e}):[]},ya.behavior.drag=function(){function n(){this.on("mousedown.drag",a).on("touchstart.drag",o)}function t(){return ya.event.changedTouches[0].identifier}function e(n,t){return ya.touches(n).filter(function(n){return n.identifier===t})[0]}function r(n,t,e,r){return function(){function a(){if(!s)return o();var n=t(s,g),e=n[0]-m[0],r=n[1]-m[1];d|=e|r,m=n,f({type:"drag",x:n[0]+c[0],y:n[1]+c[1],dx:e,dy:r})}function o(){v.on(e+"."+p,null).on(r+"."+p,null),y(d&&ya.event.target===h),f({type:"dragend"})}var c,l=this,s=l.parentNode,f=i.of(l,arguments),h=ya.event.target,g=n(),p=null==g?"drag":"drag-"+g,m=t(s,g),d=0,v=ya.select(ba).on(e+"."+p,a).on(r+"."+p,o),y=H();u?(c=u.apply(l,arguments),c=[c.x-m[0],c.y-m[1]]):c=[0,0],f({type:"dragstart"})}}var i=m(n,"drag","dragstart","dragend"),u=null,a=r(s,ya.mouse,"mousemove","mouseup"),o=r(t,e,"touchmove","touchend");return n.origin=function(t){return arguments.length?(u=t,n):u},ya.rebind(n,i,"on")},ya.behavior.zoom=function(){function n(){this.on(w,o).on(Ja+".zoom",l).on(S,s).on("dblclick.zoom",f).on(k,c)}function t(n){return[(n[0]-x[0])/b,(n[1]-x[1])/b]}function e(n){return[n[0]*b+x[0],n[1]*b+x[1]]}function r(n){b=Math.max(_[0],Math.min(_[1],n))}function i(n,t){t=e(t),x[0]+=n[0]-t[0],x[1]+=n[1]-t[1]}function u(){v&&v.domain(d.range().map(function(n){return(n-x[0])/b}).map(d.invert)),M&&M.domain(y.range().map(function(n){return(n-x[1])/b}).map(y.invert))}function a(n){u(),n({type:"zoom",scale:b,translate:x})}function o(){function n(){c=1,i(ya.mouse(r),f),a(u)}function e(){l.on(S,ba===r?s:null).on(E,null),h(c&&ya.event.target===o)}var r=this,u=q.of(r,arguments),o=ya.event.target,c=0,l=ya.select(ba).on(S,n).on(E,e),f=t(ya.mouse(r)),h=H()}function c(){function n(){var n=ya.touches(h);return f=b,s={},n.forEach(function(n){s[n.identifier]=t(n)}),n}function e(){var t=Date.now(),e=n();if(1===e.length){if(500>t-p){var u=e[0],o=s[u.identifier];r(2*b),i(u,o),g(),a(m)}p=t}else if(e.length>1){var u=e[0],c=e[1],l=u[0]-c[0],f=u[1]-c[1];d=l*l+f*f}}function u(){var n=ya.touches(h),t=n[0],e=s[t.identifier];if(u=n[1]){var u,o=s[u.identifier],c=ya.event.scale;if(null==c){var l=(l=u[0]-t[0])*l+(l=u[1]-t[1])*l;c=d&&Math.sqrt(l/d)}t=[(t[0]+u[0])/2,(t[1]+u[1])/2],e=[(e[0]+o[0])/2,(e[1]+o[1])/2],r(c*f)}p=null,i(t,e),a(m)}function l(){ya.event.touches.length?n():(v.on(A,null).on(N,null),y.on(w,o).on(k,c),M())}var s,f,h=this,m=q.of(h,arguments),d=0,v=ya.select(ba).on(A,u).on(N,l),y=ya.select(h).on(w,null).on(k,e),M=H();e()}function l(){g(),h||(h=t(ya.mouse(this))),r(Math.pow(2,.002*$a())*b),i(ya.mouse(this),h),a(q.of(this,arguments))}function s(){h=null}function f(){var n=ya.mouse(this),e=t(n),u=Math.log(b)/Math.LN2;r(Math.pow(2,ya.event.shiftKey?Math.ceil(u)-1:Math.floor(u)+1)),i(n,e),a(q.of(this,arguments))}var h,p,d,v,y,M,x=[0,0],b=1,_=Wa,w="mousedown.zoom",S="mousemove.zoom",E="mouseup.zoom",k="touchstart.zoom",A="touchmove.zoom",N="touchend.zoom",q=m(n,"zoom");return n.translate=function(t){return arguments.length?(x=t.map(Number),u(),n):x},n.scale=function(t){return arguments.length?(b=+t,u(),n):b},n.scaleExtent=function(t){return arguments.length?(_=null==t?Wa:t.map(Number),n):_},n.x=function(t){return arguments.length?(v=t,d=t.copy(),x=[0,0],b=1,n):v},n.y=function(t){return arguments.length?(M=t,y=t.copy(),x=[0,0],b=1,n):M},ya.rebind(n,q,"on")};var $a,Wa=[0,1/0],Ja="onwheel"in Ma?($a=function(){return-ya.event.deltaY*(ya.event.deltaMode?120:1)},"wheel"):"onmousewheel"in Ma?($a=function(){return ya.event.wheelDelta},"mousewheel"):($a=function(){return-ya.event.detail},"MozMousePixelScroll");P.prototype.toString=function(){return this.rgb()+""},ya.hsl=function(n,t,e){return 1===arguments.length?n instanceof Y?O(n.h,n.s,n.l):lt(""+n,st,O):O(+n,+t,+e)};var Ga=Y.prototype=new P;Ga.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),O(this.h,this.s,this.l/n)},Ga.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),O(this.h,this.s,n*this.l)},Ga.rgb=function(){return R(this.h,this.s,this.l)};var Ka=Math.PI,Qa=1e-6,no=Qa*Qa,to=Ka/180,eo=180/Ka;ya.hcl=function(n,t,e){return 1===arguments.length?n instanceof W?$(n.h,n.c,n.l):n instanceof K?nt(n.l,n.a,n.b):nt((n=ft((n=ya.rgb(n)).r,n.g,n.b)).l,n.a,n.b):$(+n,+t,+e)};var ro=W.prototype=new P;ro.brighter=function(n){return $(this.h,this.c,Math.min(100,this.l+io*(arguments.length?n:1)))},ro.darker=function(n){return $(this.h,this.c,Math.max(0,this.l-io*(arguments.length?n:1)))},ro.rgb=function(){return J(this.h,this.c,this.l).rgb()},ya.lab=function(n,t,e){return 1===arguments.length?n instanceof K?G(n.l,n.a,n.b):n instanceof W?J(n.l,n.c,n.h):ft((n=ya.rgb(n)).r,n.g,n.b):G(+n,+t,+e)};var io=18,uo=.95047,ao=1,oo=1.08883,co=K.prototype=new P;co.brighter=function(n){return G(Math.min(100,this.l+io*(arguments.length?n:1)),this.a,this.b)},co.darker=function(n){return G(Math.max(0,this.l-io*(arguments.length?n:1)),this.a,this.b)},co.rgb=function(){return Q(this.l,this.a,this.b)},ya.rgb=function(n,t,e){return 1===arguments.length?n instanceof ot?at(n.r,n.g,n.b):lt(""+n,at,R):at(~~n,~~t,~~e)};var lo=ot.prototype=new P;lo.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,i=30;return t||e||r?(t&&i>t&&(t=i),e&&i>e&&(e=i),r&&i>r&&(r=i),at(Math.min(255,~~(t/n)),Math.min(255,~~(e/n)),Math.min(255,~~(r/n)))):at(i,i,i)},lo.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),at(~~(n*this.r),~~(n*this.g),~~(n*this.b))},lo.hsl=function(){return st(this.r,this.g,this.b)},lo.toString=function(){return"#"+ct(this.r)+ct(this.g)+ct(this.b)};var so=ya.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});so.forEach(function(n,t){so.set(n,it(t))}),ya.functor=pt,ya.xhr=dt(mt),ya.dsv=function(n,t){function e(n,e,u){arguments.length<3&&(u=e,e=null);var a=ya.xhr(n,t,u);return a.row=function(n){return arguments.length?a.response(null==(e=n)?r:i(n)):e},a.row(e)}function r(n){return e.parse(n.responseText)}function i(n){return function(t){return e.parse(t.responseText,n)}}function a(t){return t.map(o).join(n)}function o(n){return c.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var c=new RegExp('["'+n+"\n]"),l=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var i=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(i(n),e)}:i})},e.parseRows=function(n,t){function e(){if(s>=c)return a;if(i)return i=!1,u;var t=s;if(34===n.charCodeAt(t)){for(var e=t;e++s;){var r=n.charCodeAt(s++),o=1;if(10===r)i=!0;else if(13===r)i=!0,10===n.charCodeAt(s)&&(++s,++o);else if(r!==l)continue;return n.substring(t,s-o)}return n.substring(t)}for(var r,i,u={},a={},o=[],c=n.length,s=0,f=0;(r=e())!==a;){for(var h=[];r!==u&&r!==a;)h.push(r),r=e();(!t||(h=t(h,f++)))&&o.push(h)}return o},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new u,i=[];return t.forEach(function(n){for(var t in n)r.has(t)||i.push(r.add(t))}),[i.map(o).join(n)].concat(t.map(function(t){return i.map(function(n){return o(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(a).join("\n")},e},ya.csv=ya.dsv(",","text/csv"),ya.tsv=ya.dsv(" ","text/tab-separated-values");var fo,ho,go,po,mo,vo=ba[o(ba,"requestAnimationFrame")]||function(n){setTimeout(n,17)};ya.timer=function(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var i=e+t,u={callback:n,time:i,next:null};ho?ho.next=u:fo=u,ho=u,go||(po=clearTimeout(po),go=1,vo(Mt))},ya.timer.flush=function(){bt(),_t()};var yo=".",Mo=",",xo=[3,3],bo="$",_o=["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"].map(wt);ya.formatPrefix=function(n,t){var e=0;return n&&(0>n&&(n*=-1),t&&(n=ya.round(n,St(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((0>=e?e+1:e-1)/3)))),_o[8+e/3]},ya.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)},ya.format=function(n){var t=wo.exec(n),e=t[1]||" ",r=t[2]||">",i=t[3]||"",u=t[4]||"",a=t[5],o=+t[6],c=t[7],l=t[8],s=t[9],f=1,h="",g=!1;switch(l&&(l=+l.substring(1)),(a||"0"===e&&"="===r)&&(a=e="0",r="=",c&&(o-=Math.floor((o-1)/4))),s){case"n":c=!0,s="g";break;case"%":f=100,h="%",s="f";break;case"p":f=100,h="%",s="r";break;case"b":case"o":case"x":case"X":"#"===u&&(u="0"+s.toLowerCase());case"c":case"d":g=!0,l=0;break;case"s":f=-1,s="r"}"#"===u?u="":"$"===u&&(u=bo),"r"!=s||l||(s="g"),null!=l&&("g"==s?l=Math.max(1,Math.min(21,l)):("e"==s||"f"==s)&&(l=Math.max(0,Math.min(20,l)))),s=So.get(s)||Et;var p=a&&c;return function(n){if(g&&n%1)return"";var t=0>n||0===n&&0>1/n?(n=-n,"-"):i;if(0>f){var m=ya.formatPrefix(n,l);n=m.scale(n),h=m.symbol}else n*=f;n=s(n,l);var d=n.lastIndexOf("."),v=0>d?n:n.substring(0,d),y=0>d?"":yo+n.substring(d+1);!a&&c&&(v=Eo(v));var M=u.length+v.length+y.length+(p?0:t.length),x=o>M?new Array(M=o-M+1).join(e):"";return p&&(v=Eo(x+v)),t+=u,n=v+y,("<"===r?t+n+x:">"===r?x+t+n:"^"===r?x.substring(0,M>>=1)+t+n+x.substring(M):t+(p?n:x+n))+h}};var wo=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,So=ya.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=ya.round(n,St(n,t))).toFixed(Math.max(0,Math.min(20,St(n*(1+1e-15),t))))}}),Eo=mt;if(xo){var ko=xo.length;Eo=function(n){for(var t=n.length,e=[],r=0,i=xo[0];t>0&&i>0;)e.push(n.substring(t-=i,t+i)),i=xo[r=(r+1)%ko];return e.reverse().join(Mo)}}ya.geo={},kt.prototype={s:0,t:0,add:function(n){At(n,this.t,Ao),At(Ao.s,this.s,this),this.s?this.t+=Ao.t:this.s=Ao.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var Ao=new kt;ya.geo.stream=function(n,t){n&&No.hasOwnProperty(n.type)?No[n.type](n,t):Nt(n,t)};var No={Feature:function(n,t){Nt(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,i=e.length;++rn?4*Ka+n:n,zo.lineStart=zo.lineEnd=zo.point=s}};ya.geo.bounds=function(){function n(n,t){M.push(x=[s=n,h=n]),f>t&&(f=t),t>g&&(g=t)}function t(t,e){var r=zt([t*to,e*to]);if(v){var i=jt(v,r),u=[i[1],-i[0],0],a=jt(u,i);Ft(a),a=Pt(a);var c=t-p,l=c>0?1:-1,m=a[0]*eo*l,d=Math.abs(c)>180;if(d^(m>l*p&&l*t>m)){var y=a[1]*eo;y>g&&(g=y)}else if(m=(m+360)%360-180,d^(m>l*p&&l*t>m)){var y=-a[1]*eo;f>y&&(f=y)}else f>e&&(f=e),e>g&&(g=e);d?p>t?o(s,t)>o(s,h)&&(h=t):o(t,h)>o(s,h)&&(s=t):h>=s?(s>t&&(s=t),t>h&&(h=t)):t>p?o(s,t)>o(s,h)&&(h=t):o(t,h)>o(s,h)&&(s=t)}else n(t,e);v=r,p=t}function e(){b.point=t}function r(){x[0]=s,x[1]=h,b.point=n,v=null}function i(n,e){if(v){var r=n-p;y+=Math.abs(r)>180?r+(r>0?360:-360):r}else m=n,d=e;zo.point(n,e),t(n,e)}function u(){zo.lineStart()}function a(){i(m,d),zo.lineEnd(),Math.abs(y)>Qa&&(s=-(h=180)),x[0]=s,x[1]=h,v=null}function o(n,t){return(t-=n)<0?t+360:t}function c(n,t){return n[0]-t[0]}function l(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nCo?(s=-(h=180),f=-(g=90)):y>Qa?g=90:-Qa>y&&(f=-90),x[0]=s,x[1]=h}};return function(n){g=h=-(s=f=1/0),M=[],ya.geo.stream(n,b);var t=M.length;if(t){M.sort(c);for(var e,r=1,i=M[0],u=[i];t>r;++r)e=M[r],l(e[0],i)||l(e[1],i)?(o(i[0],e[1])>o(i[0],i[1])&&(i[1]=e[1]),o(e[0],i[1])>o(i[0],i[1])&&(i[0]=e[0])):u.push(i=e);for(var a,e,p=-1/0,t=u.length-1,r=0,i=u[t];t>=r;i=e,++r)e=u[r],(a=o(i[1],e[0]))>p&&(p=a,s=e[0],h=i[1])}return M=x=null,1/0===s||1/0===f?[[0/0,0/0],[0/0,0/0]]:[[s,f],[h,g]]}}(),ya.geo.centroid=function(n){Do=jo=Lo=Ho=Fo=Po=Oo=Yo=Ro=Uo=Io=0,ya.geo.stream(n,Vo);var t=Ro,e=Uo,r=Io,i=t*t+e*e+r*r;return no>i&&(t=Po,e=Oo,r=Yo,Qa>jo&&(t=Lo,e=Ho,r=Fo),i=t*t+e*e+r*r,no>i)?[0/0,0/0]:[Math.atan2(e,t)*eo,V(r/Math.sqrt(i))*eo]};var Do,jo,Lo,Ho,Fo,Po,Oo,Yo,Ro,Uo,Io,Vo={sphere:s,point:Yt,lineStart:Ut,lineEnd:It,polygonStart:function(){Vo.lineStart=Vt},polygonEnd:function(){Vo.lineStart=Ut}},Xo=$t(Xt,Qt,te,ee),Zo=[-Ka,0],Bo=1e9;(ya.geo.conicEqualArea=function(){return oe(ce)}).raw=ce,ya.geo.albers=function(){return ya.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},ya.geo.albersUsa=function(){function n(n){var u=n[0],a=n[1];return t=null,e(u,a),t||(r(u,a),t)||i(u,a),t}var t,e,r,i,u=ya.geo.albers(),a=ya.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),o=ya.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),c={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=u.scale(),e=u.translate(),r=(n[0]-e[0])/t,i=(n[1]-e[1])/t;return(i>=.12&&.234>i&&r>=-.425&&-.214>r?a:i>=.166&&.234>i&&r>=-.214&&-.115>r?o:u).invert(n)},n.stream=function(n){var t=u.stream(n),e=a.stream(n),r=o.stream(n);return{point:function(n,i){t.point(n,i),e.point(n,i),r.point(n,i)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(u.precision(t),a.precision(t),o.precision(t),n):u.precision()},n.scale=function(t){return arguments.length?(u.scale(t),a.scale(.35*t),o.scale(t),n.translate(u.translate())):u.scale()},n.translate=function(t){if(!arguments.length)return u.translate();var l=u.scale(),s=+t[0],f=+t[1];return e=u.translate(t).clipExtent([[s-.455*l,f-.238*l],[s+.455*l,f+.238*l]]).stream(c).point,r=a.translate([s-.307*l,f+.201*l]).clipExtent([[s-.425*l+Qa,f+.12*l+Qa],[s-.214*l-Qa,f+.234*l-Qa]]).stream(c).point,i=o.translate([s-.205*l,f+.212*l]).clipExtent([[s-.214*l+Qa,f+.166*l+Qa],[s-.115*l-Qa,f+.234*l-Qa]]).stream(c).point,n},n.scale(1070)};var $o,Wo,Jo,Go,Ko,Qo,nc={point:s,lineStart:s,lineEnd:s,polygonStart:function(){Wo=0,nc.lineStart=le},polygonEnd:function(){nc.lineStart=nc.lineEnd=nc.point=s,$o+=Math.abs(Wo/2)}},tc={point:se,lineStart:s,lineEnd:s,polygonStart:s,polygonEnd:s},ec={point:ge,lineStart:pe,lineEnd:me,polygonStart:function(){ec.lineStart=de},polygonEnd:function(){ec.point=ge,ec.lineStart=pe,ec.lineEnd=me}};ya.geo.path=function(){function n(n){return n&&("function"==typeof o&&u.pointRadius(+o.apply(this,arguments)),a&&a.valid||(a=i(u)),ya.geo.stream(n,a)),u.result()}function t(){return a=null,n}var e,r,i,u,a,o=4.5;return n.area=function(n){return $o=0,ya.geo.stream(n,i(nc)),$o},n.centroid=function(n){return Lo=Ho=Fo=Po=Oo=Yo=Ro=Uo=Io=0,ya.geo.stream(n,i(ec)),Io?[Ro/Io,Uo/Io]:Yo?[Po/Yo,Oo/Yo]:Fo?[Lo/Fo,Ho/Fo]:[0/0,0/0]},n.bounds=function(n){return Ko=Qo=-(Jo=Go=1/0),ya.geo.stream(n,i(tc)),[[Jo,Go],[Ko,Qo]]},n.projection=function(n){return arguments.length?(i=(e=n)?n.stream||Me(n):mt,t()):e},n.context=function(n){return arguments.length?(u=null==(r=n)?new fe:new ve(n),"function"!=typeof o&&u.pointRadius(o),t()):r},n.pointRadius=function(t){return arguments.length?(o="function"==typeof t?t:(u.pointRadius(+t),+t),n):o},n.projection(ya.geo.albersUsa()).context(null)},ya.geo.projection=xe,ya.geo.projectionMutator=be,(ya.geo.equirectangular=function(){return xe(we)}).raw=we.invert=we,ya.geo.rotation=function(n){function t(t){return t=n(t[0]*to,t[1]*to),t[0]*=eo,t[1]*=eo,t}return n=Se(n[0]%360*to,n[1]*to,n.length>2?n[2]*to:0),t.invert=function(t){return t=n.invert(t[0]*to,t[1]*to),t[0]*=eo,t[1]*=eo,t},t},ya.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=Se(-n[0]*to,-n[1]*to,0).invert,i=[];return e(null,null,1,{point:function(n,e){i.push(n=t(n,e)),n[0]*=eo,n[1]*=eo}}),{type:"Polygon",coordinates:[i]}}var t,e,r=[0,0],i=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=Ne((t=+r)*to,i*to),n):t},n.precision=function(r){return arguments.length?(e=Ne(t*to,(i=+r)*to),n):i},n.angle(90)},ya.geo.distance=function(n,t){var e,r=(t[0]-n[0])*to,i=n[1]*to,u=t[1]*to,a=Math.sin(r),o=Math.cos(r),c=Math.sin(i),l=Math.cos(i),s=Math.sin(u),f=Math.cos(u);return Math.atan2(Math.sqrt((e=f*a)*e+(e=l*s-c*f*o)*e),c*s+l*f*o)},ya.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return ya.range(Math.ceil(u/d)*d,i,d).map(h).concat(ya.range(Math.ceil(l/v)*v,c,v).map(g)).concat(ya.range(Math.ceil(r/p)*p,e,p).filter(function(n){return Math.abs(n%d)>Qa}).map(s)).concat(ya.range(Math.ceil(o/m)*m,a,m).filter(function(n){return Math.abs(n%v)>Qa}).map(f))}var e,r,i,u,a,o,c,l,s,f,h,g,p=10,m=p,d=90,v=360,y=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(u).concat(g(c).slice(1),h(i).reverse().slice(1),g(l).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(u=+t[0][0],i=+t[1][0],l=+t[0][1],c=+t[1][1],u>i&&(t=u,u=i,i=t),l>c&&(t=l,l=c,c=t),n.precision(y)):[[u,l],[i,c]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],o=+t[0][1],a=+t[1][1],r>e&&(t=r,r=e,e=t),o>a&&(t=o,o=a,a=t),n.precision(y)):[[r,o],[e,a]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],v=+t[1],n):[d,v]},n.minorStep=function(t){return arguments.length?(p=+t[0],m=+t[1],n):[p,m]},n.precision=function(t){return arguments.length?(y=+t,s=Te(o,a,90),f=Ce(r,e,y),h=Te(l,c,90),g=Ce(u,i,y),n):y},n.majorExtent([[-180,-90+Qa],[180,90-Qa]]).minorExtent([[-180,-80-Qa],[180,80+Qa]])},ya.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||i.apply(this,arguments)]}}var t,e,r=ze,i=De;return n.distance=function(){return ya.geo.distance(t||r.apply(this,arguments),e||i.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(i=t,e="function"==typeof t?null:t,n):i},n.precision=function(){return arguments.length?n:0},n},ya.geo.interpolate=function(n,t){return je(n[0]*to,n[1]*to,t[0]*to,t[1]*to)},ya.geo.length=function(n){return rc=0,ya.geo.stream(n,ic),rc};var rc,ic={sphere:s,point:s,lineStart:Le,lineEnd:s,polygonStart:s,polygonEnd:s},uc=He(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(ya.geo.azimuthalEqualArea=function(){return xe(uc)}).raw=uc;var ac=He(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},mt);(ya.geo.azimuthalEquidistant=function(){return xe(ac)}).raw=ac,(ya.geo.conicConformal=function(){return oe(Fe)}).raw=Fe,(ya.geo.conicEquidistant=function(){return oe(Pe)}).raw=Pe;var oc=He(function(n){return 1/n},Math.atan);(ya.geo.gnomonic=function(){return xe(oc)}).raw=oc,Oe.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Ka/2]},(ya.geo.mercator=function(){return Ye(Oe)}).raw=Oe;var cc=He(function(){return 1},Math.asin);(ya.geo.orthographic=function(){return xe(cc)}).raw=cc;var lc=He(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(ya.geo.stereographic=function(){return xe(lc)}).raw=lc,Re.invert=function(n,t){return[Math.atan2(X(n),Math.cos(t)),V(Math.sin(t)/Z(n))]},(ya.geo.transverseMercator=function(){return Ye(Re)}).raw=Re,ya.geom={},ya.svg={},ya.svg.line=function(){return Ue(mt)};var sc=ya.map({linear:Xe,"linear-closed":Ze,step:Be,"step-before":$e,"step-after":We,basis:tr,"basis-open":er,"basis-closed":rr,bundle:ir,cardinal:Ke,"cardinal-open":Je,"cardinal-closed":Ge,monotone:sr}); +sc.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var fc=[0,2/3,1/3,0],hc=[0,1/3,2/3,0],gc=[0,1/6,2/3,1/6];ya.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,i,u,a,o,c,l,s,f,h,g,p,m=pt(e),d=pt(r),v=n.length,y=v-1,M=[],x=[],b=0;if(m===Ie&&r===Ve)t=n;else for(u=0,t=[];v>u;++u)t.push([+m.call(this,i=n[u],u),+d.call(this,i,u)]);for(u=1;v>u;++u)(t[u][1]u;++u)u!==b&&(c=t[u][1]-t[b][1],o=t[u][0]-t[b][0],M.push({angle:Math.atan2(c,o),index:u}));for(M.sort(function(n,t){return n.angle-t.angle}),g=M[0].angle,h=M[0].index,f=0,u=1;y>u;++u){if(a=M[u].index,g==M[u].angle){if(o=t[h][0]-t[b][0],c=t[h][1]-t[b][1],l=t[a][0]-t[b][0],s=t[a][1]-t[b][1],o*o+c*c>=l*l+s*s){M[u].index=-1;continue}M[f].index=-1}g=M[u].angle,f=u,h=a}for(x.push(b),u=0,a=0;2>u;++a)M[a].index>-1&&(x.push(M[a].index),u++);for(p=x.length;y>a;++a)if(!(M[a].index<0)){for(;!fr(x[p-2],x[p-1],M[a].index,t);)--p;x[p++]=M[a].index}var _=[];for(u=p-1;u>=0;--u)_.push(n[x[u]]);return _}var e=Ie,r=Ve;return arguments.length?t(n):(t.x=function(n){return arguments.length?(e=n,t):e},t.y=function(n){return arguments.length?(r=n,t):r},t)},ya.geom.polygon=function(n){return La(n,pc),n};var pc=ya.geom.polygon.prototype=[];pc.area=function(){for(var n,t=-1,e=this.length,r=this[e-1],i=0;++to;o++)e.push([i,t[o],t[o+1]])}),e},ya.geom.voronoi=function(n){function t(n){var t,u,a,o=n.map(function(){return[]}),c=pt(e),l=pt(r),s=n.length,f=1e6;if(c===Ie&&l===Ve)t=n;else for(t=new Array(s),a=0;s>a;++a)t[a]=[+c.call(this,u=n[a],a),+l.call(this,u,a)];if(mr(t,function(n){var t,e,r,i,u,a;1===n.a&&n.b>=0?(t=n.ep.r,e=n.ep.l):(t=n.ep.l,e=n.ep.r),1===n.a?(u=t?t.y:-f,r=n.c-n.b*u,a=e?e.y:f,i=n.c-n.b*a):(r=t?t.x:-f,u=n.c-n.a*r,i=e?e.x:f,a=n.c-n.a*i);var c=[r,u],l=[i,a];o[n.region.l.index].push(c,l),o[n.region.r.index].push(c,l)}),o=o.map(function(n,e){var r=t[e][0],i=t[e][1],u=n.map(function(n){return Math.atan2(n[0]-r,n[1]-i)}),a=ya.range(n.length).sort(function(n,t){return u[n]-u[t]});return a.filter(function(n,t){return!t||u[n]-u[a[t-1]]>Qa}).map(function(t){return n[t]})}),o.forEach(function(n,e){var r=n.length;if(!r)return n.push([-f,-f],[-f,f],[f,f],[f,-f]);if(!(r>2)){var i=t[e],u=n[0],a=n[1],o=i[0],c=i[1],l=u[0],s=u[1],h=a[0],g=a[1],p=Math.abs(h-l),m=g-s;if(Math.abs(m)c?-f:f;n.push([-f,d],[f,d])}else if(Qa>p){var v=l>o?-f:f;n.push([v,-f],[v,f])}else{var d=(l-o)*(g-s)>(h-l)*(s-c)?f:-f,y=Math.abs(m)-p;Math.abs(y)m?d:-d,d]):(y>0&&(d*=-1),n.push([-f,d],[f,d]))}}}),i)for(a=0;s>a;++a)i.clip(o[a]);for(a=0;s>a;++a)o[a].point=n[a];return o}var e=Ie,r=Ve,i=null;return arguments.length?t(n):(t.x=function(n){return arguments.length?(e=n,t):e},t.y=function(n){return arguments.length?(r=n,t):r},t.clipExtent=function(n){if(!arguments.length)return i&&[i[0],i[2]];if(null==n)i=null;else{var e=+n[0][0],r=+n[0][1],u=+n[1][0],a=+n[1][1];i=ya.geom.polygon([[e,r],[e,a],[u,a],[u,r]])}return t},t.size=function(n){return arguments.length?t.clipExtent(n&&[[0,0],n]):i&&i[2]},t.links=function(n){var t,i,u,a=n.map(function(){return[]}),o=[],c=pt(e),l=pt(r),s=n.length;if(c===Ie&&l===Ve)t=n;else for(t=new Array(s),u=0;s>u;++u)t[u]=[+c.call(this,i=n[u],u),+l.call(this,i,u)];return mr(t,function(t){var e=t.region.l.index,r=t.region.r.index;a[e][r]||(a[e][r]=a[r][e]=!0,o.push({source:n[e],target:n[r]}))}),o},t.triangles=function(n){if(e===Ie&&r===Ve)return ya.geom.delaunay(n);for(var t,i=new Array(c),u=pt(e),a=pt(r),o=-1,c=n.length;++o=l,h=r>=s,g=(h<<1)+f;n.leaf=!1,n=n.nodes[g]||(n.nodes[g]=yr()),f?i=l:o=l,h?a=s:c=s,u(n,t,e,r,i,a,o,c)}var s,f,h,g,p,m,d,v,y,M=pt(o),x=pt(c);if(null!=t)m=t,d=e,v=r,y=i;else if(v=y=-(m=d=1/0),f=[],h=[],p=n.length,a)for(g=0;p>g;++g)s=n[g],s.xv&&(v=s.x),s.y>y&&(y=s.y),f.push(s.x),h.push(s.y);else for(g=0;p>g;++g){var b=+M(s=n[g],g),_=+x(s,g);m>b&&(m=b),d>_&&(d=_),b>v&&(v=b),_>y&&(y=_),f.push(b),h.push(_)}var w=v-m,S=y-d;w>S?y=d+w:v=m+S;var E=yr();if(E.add=function(n){u(E,n,+M(n,++g),+x(n,g),m,d,v,y)},E.visit=function(n){Mr(n,E,m,d,v,y)},g=-1,null==t){for(;++g=0?n.substring(0,t):n,r=t>=0?n.substring(t+1):"in";return e=yc.get(e)||vc,r=Mc.get(r)||mt,kr(r(e.apply(null,Array.prototype.slice.call(arguments,1))))},ya.interpolateHcl=Or,ya.interpolateHsl=Yr,ya.interpolateLab=Rr,ya.interpolateRound=Ur,ya.transform=function(n){var t=Ma.createElementNS(ya.ns.prefix.svg,"g");return(ya.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new Ir(e?e.matrix:xc)})(n)},Ir.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var xc={a:1,b:0,c:0,d:1,e:0,f:0};ya.interpolateTransform=Br,ya.layout={},ya.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++e(i-e)*o){var c=t.charge*o*o;return n.px-=u*c,n.py-=a*c,!0}if(t.point&&isFinite(o)){var c=t.pointCharge*o*o;n.px-=u*c,n.py-=a*c}}return!t.charge}}function t(n){n.px=ya.event.x,n.py=ya.event.y,o.resume()}var e,r,i,u,a,o={},c=ya.dispatch("start","tick","end"),l=[1,1],s=.9,f=bc,h=_c,g=-30,p=.1,m=.8,d=[],v=[];return o.tick=function(){if((r*=.99)<.005)return c.end({type:"end",alpha:r=0}),!0;var t,e,o,f,h,m,y,M,x,b=d.length,_=v.length;for(e=0;_>e;++e)o=v[e],f=o.source,h=o.target,M=h.x-f.x,x=h.y-f.y,(m=M*M+x*x)&&(m=r*u[e]*((m=Math.sqrt(m))-i[e])/m,M*=m,x*=m,h.x-=M*(y=f.weight/(h.weight+f.weight)),h.y-=x*y,f.x+=M*(y=1-y),f.y+=x*y);if((y=r*p)&&(M=l[0]/2,x=l[1]/2,e=-1,y))for(;++e0?n:0:n>0&&(c.start({type:"start",alpha:r=n}),ya.timer(o.tick)),o):r},o.start=function(){function n(n,r){for(var i,u=t(e),a=-1,o=u.length;++ar;++r)c[r]=[];for(r=0;m>r;++r){var n=v[r];c[n.source.index].push(n.target),c[n.target.index].push(n.source)}}return c[e]}var e,r,c,s,p=d.length,m=v.length,y=l[0],M=l[1];for(e=0;p>e;++e)(s=d[e]).index=e,s.weight=0;for(e=0;m>e;++e)s=v[e],"number"==typeof s.source&&(s.source=d[s.source]),"number"==typeof s.target&&(s.target=d[s.target]),++s.source.weight,++s.target.weight;for(e=0;p>e;++e)s=d[e],isNaN(s.x)&&(s.x=n("x",y)),isNaN(s.y)&&(s.y=n("y",M)),isNaN(s.px)&&(s.px=s.x),isNaN(s.py)&&(s.py=s.y);if(i=[],"function"==typeof f)for(e=0;m>e;++e)i[e]=+f.call(this,v[e],e);else for(e=0;m>e;++e)i[e]=f;if(u=[],"function"==typeof h)for(e=0;m>e;++e)u[e]=+h.call(this,v[e],e);else for(e=0;m>e;++e)u[e]=h;if(a=[],"function"==typeof g)for(e=0;p>e;++e)a[e]=+g.call(this,d[e],e);else for(e=0;p>e;++e)a[e]=g;return o.resume()},o.resume=function(){return o.alpha(.1)},o.stop=function(){return o.alpha(0)},o.drag=function(){return e||(e=ya.behavior.drag().origin(mt).on("dragstart.force",Qr).on("drag.force",t).on("dragend.force",ni)),arguments.length?(this.on("mouseover.force",ti).on("mouseout.force",ei).call(e),void 0):e},ya.rebind(o,c,"on")};var bc=20,_c=1;ya.layout.hierarchy=function(){function n(t,a,o){var c=i.call(e,t,a);if(t.depth=a,o.push(t),c&&(l=c.length)){for(var l,s,f=-1,h=t.children=[],g=0,p=a+1;++fg;++g)for(i.call(n,l[0][g],p=m[g],s[0][g][1]),h=1;d>h;++h)i.call(n,l[h][g],p+=s[h-1][g][1],s[h][g][1]);return o}var t=mt,e=hi,r=gi,i=fi,u=li,a=si;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:Sc.get(t)||hi,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:Ec.get(t)||gi,n):r},n.x=function(t){return arguments.length?(u=t,n):u},n.y=function(t){return arguments.length?(a=t,n):a},n.out=function(t){return arguments.length?(i=t,n):i},n};var Sc=ya.map({"inside-out":function(n){var t,e,r=n.length,i=n.map(pi),u=n.map(mi),a=ya.range(r).sort(function(n,t){return i[n]-i[t]}),o=0,c=0,l=[],s=[];for(t=0;r>t;++t)e=a[t],c>o?(o+=u[e],l.push(e)):(c+=u[e],s.push(e));return s.reverse().concat(l)},reverse:function(n){return ya.range(n.length).reverse()},"default":hi}),Ec=ya.map({silhouette:function(n){var t,e,r,i=n.length,u=n[0].length,a=[],o=0,c=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];r>o&&(o=r),a.push(r)}for(e=0;u>e;++e)c[e]=(o-a[e])/2;return c},wiggle:function(n){var t,e,r,i,u,a,o,c,l,s=n.length,f=n[0],h=f.length,g=[];for(g[0]=c=l=0,e=1;h>e;++e){for(t=0,i=0;s>t;++t)i+=n[t][e][1];for(t=0,u=0,o=f[e][0]-f[e-1][0];s>t;++t){for(r=0,a=(n[t][e][1]-n[t][e-1][1])/(2*o);t>r;++r)a+=(n[r][e][1]-n[r][e-1][1])/o;u+=a*n[t][e][1]}g[e]=c-=i?u/i*o:0,l>c&&(l=c)}for(e=0;h>e;++e)g[e]-=l;return g},expand:function(n){var t,e,r,i=n.length,u=n[0].length,a=1/i,o=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];if(r)for(t=0;i>t;t++)n[t][e][1]/=r;else for(t=0;i>t;t++)n[t][e][1]=a}for(e=0;u>e;++e)o[e]=0;return o},zero:gi});ya.layout.histogram=function(){function n(n,u){for(var a,o,c=[],l=n.map(e,this),s=r.call(this,l,u),f=i.call(this,s,l,u),u=-1,h=l.length,g=f.length-1,p=t?1:1/h;++u0)for(u=-1;++u=s[0]&&o<=s[1]&&(a=c[ya.bisect(f,o,1,g)-1],a.y+=p,a.push(n[u]));return c}var t=!0,e=Number,r=Mi,i=vi;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=pt(t),n):r},n.bins=function(t){return arguments.length?(i="number"==typeof t?function(n){return yi(n,t)}:pt(t),n):i},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},ya.layout.tree=function(){function n(n,u){function a(n,t){var r=n.children,i=n._tree;if(r&&(u=r.length)){for(var u,o,l,s=r[0],f=s,h=-1;++h0&&(qi(Ti(o,n,r),n,i),l+=i,s+=i),f+=o._tree.mod,l+=u._tree.mod,h+=c._tree.mod,s+=a._tree.mod;o&&!_i(a)&&(a._tree.thread=o,a._tree.mod+=f-s),u&&!bi(c)&&(c._tree.thread=u,c._tree.mod+=l-h,r=n)}return r}var l=t.call(this,n,u),s=l[0];Ai(s,function(n,t){n._tree={ancestor:n,prelim:0,mod:0,change:0,shift:0,number:t?t._tree.number+1:0}}),a(s),o(s,-s._tree.prelim);var f=wi(s,Ei),h=wi(s,Si),g=wi(s,ki),p=f.x-e(f,h)/2,m=h.x+e(h,f)/2,d=g.depth||1;return Ai(s,i?function(n){n.x*=r[0],n.y=n.depth*r[1],delete n._tree}:function(n){n.x=(n.x-p)/(m-p)*r[0],n.y=n.depth/d*r[1],delete n._tree}),l}var t=ya.layout.hierarchy().sort(null).value(null),e=xi,r=[1,1],i=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(i=null==(r=t),n):i?null:r},n.nodeSize=function(t){return arguments.length?(i=null!=(r=t),n):i?r:null},ii(n,t)},ya.layout.pack=function(){function n(n,u){var a=e.call(this,n,u),o=a[0],c=i[0],l=i[1],s=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(o.x=o.y=0,Ai(o,function(n){n.r=+s(n.value)}),Ai(o,Li),r){var f=r*(t?1:Math.max(2*o.r/c,2*o.r/l))/2;Ai(o,function(n){n.r+=f}),Ai(o,Li),Ai(o,function(n){n.r-=f})}return Pi(o,c/2,l/2,t?1:1/Math.max(2*o.r/c,2*o.r/l)),a}var t,e=ya.layout.hierarchy().sort(Ci),r=0,i=[1,1];return n.size=function(t){return arguments.length?(i=t,n):i},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},ii(n,e)},ya.layout.cluster=function(){function n(n,u){var a,o=t.call(this,n,u),c=o[0],l=0;Ai(c,function(n){var t=n.children;t&&t.length?(n.x=Ri(t),n.y=Yi(t)):(n.x=a?l+=e(n,a):0,n.y=0,a=n)});var s=Ui(c),f=Ii(c),h=s.x-e(s,f)/2,g=f.x+e(f,s)/2;return Ai(c,i?function(n){n.x=(n.x-c.x)*r[0],n.y=(c.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(g-h)*r[0],n.y=(1-(c.y?n.y/c.y:1))*r[1]}),o}var t=ya.layout.hierarchy().sort(null).value(null),e=xi,r=[1,1],i=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(i=null==(r=t),n):i?null:r},n.nodeSize=function(t){return arguments.length?(i=null!=(r=t),n):i?r:null},ii(n,t)},ya.layout.treemap=function(){function n(n,t){for(var e,r,i=-1,u=n.length;++it?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var u=e.children;if(u&&u.length){var a,o,c,l=f(e),s=[],h=u.slice(),p=1/0,m="slice"===g?l.dx:"dice"===g?l.dy:"slice-dice"===g?1&e.depth?l.dy:l.dx:Math.min(l.dx,l.dy);for(n(h,l.dx*l.dy/e.value),s.area=0;(c=h.length)>0;)s.push(a=h[c-1]),s.area+=a.area,"squarify"!==g||(o=r(s,m))<=p?(h.pop(),p=o):(s.area-=s.pop().area,i(s,m,l,!1),m=Math.min(l.dx,l.dy),s.length=s.area=0,p=1/0);s.length&&(i(s,m,l,!0),s.length=s.area=0),u.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var u,a=f(t),o=r.slice(),c=[];for(n(o,a.dx*a.dy/t.value),c.area=0;u=o.pop();)c.push(u),c.area+=u.area,null!=u.z&&(i(c,u.z?a.dx:a.dy,a,!o.length),c.length=c.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,i=0,u=1/0,a=-1,o=n.length;++ae&&(u=e),e>i&&(i=e));return r*=r,t*=t,r?Math.max(t*i*p/r,r/(t*u*p)):1/0}function i(n,t,e,r){var i,u=-1,a=n.length,o=e.x,l=e.y,s=t?c(n.area/t):0;if(t==e.dx){for((r||s>e.dy)&&(s=e.dy);++ue.dx)&&(s=e.dx);++ue&&(t=1),1>e&&(n=0),function(){var e,r,i;do e=2*Math.random()-1,r=2*Math.random()-1,i=e*e+r*r;while(!i||i>1);return n+t*e*Math.sqrt(-2*Math.log(i)/i)}},logNormal:function(){var n=ya.random.normal.apply(ya,arguments);return function(){return Math.exp(n())}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t/n}}},ya.scale={};var kc={floor:mt,ceil:mt};ya.scale.linear=function(){return Ki([0,1],[0,1],Sr,!1)},ya.scale.log=function(){return uu(ya.scale.linear().domain([0,1]),10,!0,[1,10])};var Ac=ya.format(".0e"),Nc={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};ya.scale.pow=function(){return au(ya.scale.linear(),1,[0,1])},ya.scale.sqrt=function(){return ya.scale.pow().exponent(.5)},ya.scale.ordinal=function(){return cu([],{t:"range",a:[[]]})},ya.scale.category10=function(){return ya.scale.ordinal().range(qc)},ya.scale.category20=function(){return ya.scale.ordinal().range(Tc)},ya.scale.category20b=function(){return ya.scale.ordinal().range(Cc)},ya.scale.category20c=function(){return ya.scale.ordinal().range(zc)};var qc=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(ut),Tc=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(ut),Cc=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(ut),zc=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(ut);ya.scale.quantile=function(){return lu([],[])},ya.scale.quantize=function(){return su(0,1,[0,1])},ya.scale.threshold=function(){return fu([.5],[0,1])},ya.scale.identity=function(){return hu([0,1])},ya.svg.arc=function(){function n(){var n=t.apply(this,arguments),u=e.apply(this,arguments),a=r.apply(this,arguments)+Dc,o=i.apply(this,arguments)+Dc,c=(a>o&&(c=a,a=o,o=c),o-a),l=Ka>c?"0":"1",s=Math.cos(a),f=Math.sin(a),h=Math.cos(o),g=Math.sin(o);return c>=jc?n?"M0,"+u+"A"+u+","+u+" 0 1,1 0,"+-u+"A"+u+","+u+" 0 1,1 0,"+u+"M0,"+n+"A"+n+","+n+" 0 1,0 0,"+-n+"A"+n+","+n+" 0 1,0 0,"+n+"Z":"M0,"+u+"A"+u+","+u+" 0 1,1 0,"+-u+"A"+u+","+u+" 0 1,1 0,"+u+"Z":n?"M"+u*s+","+u*f+"A"+u+","+u+" 0 "+l+",1 "+u*h+","+u*g+"L"+n*h+","+n*g+"A"+n+","+n+" 0 "+l+",0 "+n*s+","+n*f+"Z":"M"+u*s+","+u*f+"A"+u+","+u+" 0 "+l+",1 "+u*h+","+u*g+"L0,0"+"Z"}var t=gu,e=pu,r=mu,i=du;return n.innerRadius=function(e){return arguments.length?(t=pt(e),n):t},n.outerRadius=function(t){return arguments.length?(e=pt(t),n):e},n.startAngle=function(t){return arguments.length?(r=pt(t),n):r},n.endAngle=function(t){return arguments.length?(i=pt(t),n):i},n.centroid=function(){var n=(t.apply(this,arguments)+e.apply(this,arguments))/2,u=(r.apply(this,arguments)+i.apply(this,arguments))/2+Dc;return[Math.cos(u)*n,Math.sin(u)*n]},n};var Dc=-Ka/2,jc=2*Ka-1e-6;ya.svg.line.radial=function(){var n=Ue(vu);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},$e.reverse=We,We.reverse=$e,ya.svg.area=function(){return yu(mt)},ya.svg.area.radial=function(){var n=yu(vu);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},ya.svg.chord=function(){function n(n,o){var c=t(this,u,n,o),l=t(this,a,n,o);return"M"+c.p0+r(c.r,c.p1,c.a1-c.a0)+(e(c,l)?i(c.r,c.p1,c.r,c.p0):i(c.r,c.p1,l.r,l.p0)+r(l.r,l.p1,l.a1-l.a0)+i(l.r,l.p1,c.r,c.p0))+"Z"}function t(n,t,e,r){var i=t.call(n,e,r),u=o.call(n,i,r),a=c.call(n,i,r)+Dc,s=l.call(n,i,r)+Dc;return{r:u,a0:a,a1:s,p0:[u*Math.cos(a),u*Math.sin(a)],p1:[u*Math.cos(s),u*Math.sin(s)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>Ka)+",1 "+t}function i(n,t,e,r){return"Q 0,0 "+r}var u=ze,a=De,o=Mu,c=mu,l=du;return n.radius=function(t){return arguments.length?(o=pt(t),n):o},n.source=function(t){return arguments.length?(u=pt(t),n):u},n.target=function(t){return arguments.length?(a=pt(t),n):a},n.startAngle=function(t){return arguments.length?(c=pt(t),n):c},n.endAngle=function(t){return arguments.length?(l=pt(t),n):l},n},ya.svg.diagonal=function(){function n(n,i){var u=t.call(this,n,i),a=e.call(this,n,i),o=(u.y+a.y)/2,c=[u,{x:u.x,y:o},{x:a.x,y:o},a];return c=c.map(r),"M"+c[0]+"C"+c[1]+" "+c[2]+" "+c[3]}var t=ze,e=De,r=xu;return n.source=function(e){return arguments.length?(t=pt(e),n):t},n.target=function(t){return arguments.length?(e=pt(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},ya.svg.diagonal.radial=function(){var n=ya.svg.diagonal(),t=xu,e=n.projection;return n.projection=function(n){return arguments.length?e(bu(t=n)):t},n},ya.svg.symbol=function(){function n(n,r){return(Lc.get(t.call(this,n,r))||Su)(e.call(this,n,r))}var t=wu,e=_u;return n.type=function(e){return arguments.length?(t=pt(e),n):t},n.size=function(t){return arguments.length?(e=pt(t),n):e},n};var Lc=ya.map({circle:Su,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Oc)),e=t*Oc;return"M0,"+-t+"L"+e+",0"+" 0,"+t+" "+-e+",0"+"Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/Pc),e=t*Pc/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/Pc),e=t*Pc/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});ya.svg.symbolTypes=Lc.keys();var Hc,Fc,Pc=Math.sqrt(3),Oc=Math.tan(30*to),Yc=[],Rc=0;Yc.call=Ya.call,Yc.empty=Ya.empty,Yc.node=Ya.node,Yc.size=Ya.size,ya.transition=function(n){return arguments.length?Hc?n.transition():n:Ia.transition()},ya.transition.prototype=Yc,Yc.select=function(n){var t,e,r,i=this.id,u=[];n=v(n);for(var a=-1,o=this.length;++au;u++){i.push(t=[]);for(var e=this[u],o=0,c=e.length;c>o;o++)(r=e[o])&&n.call(r,r.__data__,o)&&t.push(r)}return Eu(i,this.id)},Yc.tween=function(n,t){var e=this.id;return arguments.length<2?this.node().__transition__[e].tween.get(n):T(this,null==t?function(t){t.__transition__[e].tween.remove(n)}:function(r){r.__transition__[e].tween.set(n,t)})},Yc.attr=function(n,t){function e(){this.removeAttribute(o)}function r(){this.removeAttributeNS(o.space,o.local)}function i(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(o);return e!==n&&(t=a(e,n),function(n){this.setAttribute(o,t(n))})})}function u(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(o.space,o.local);return e!==n&&(t=a(e,n),function(n){this.setAttributeNS(o.space,o.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var a="transform"==n?Br:Sr,o=ya.ns.qualify(n);return ku(this,"attr."+n,t,o.local?u:i)},Yc.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(i));return r&&function(n){this.setAttribute(i,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(i.space,i.local));return r&&function(n){this.setAttributeNS(i.space,i.local,r(n))}}var i=ya.ns.qualify(n);return this.tween("attr."+n,i.local?r:e)},Yc.style=function(n,t,e){function r(){this.style.removeProperty(n)}function i(t){return null==t?r:(t+="",function(){var r,i=ba.getComputedStyle(this,null).getPropertyValue(n);return i!==t&&(r=Sr(i,t),function(t){this.style.setProperty(n,r(t),e)})})}var u=arguments.length;if(3>u){if("string"!=typeof n){2>u&&(t="");for(e in n)this.style(e,n[e],t);return this}e=""}return ku(this,"style."+n,t,i)},Yc.styleTween=function(n,t,e){function r(r,i){var u=t.call(this,r,i,ba.getComputedStyle(this,null).getPropertyValue(n));return u&&function(t){this.style.setProperty(n,u(t),e)}}return arguments.length<3&&(e=""),this.tween("style."+n,r)},Yc.text=function(n){return ku(this,"text",n,Au)},Yc.remove=function(){return this.each("end.transition",function(){var n;!this.__transition__&&(n=this.parentNode)&&n.removeChild(this)})},Yc.ease=function(n){var t=this.id;return arguments.length<1?this.node().__transition__[t].ease:("function"!=typeof n&&(n=ya.ease.apply(ya,arguments)),T(this,function(e){e.__transition__[t].ease=n}))},Yc.delay=function(n){var t=this.id;return T(this,"function"==typeof n?function(e,r,i){e.__transition__[t].delay=0|n.call(e,e.__data__,r,i)}:(n|=0,function(e){e.__transition__[t].delay=n}))},Yc.duration=function(n){var t=this.id;return T(this,"function"==typeof n?function(e,r,i){e.__transition__[t].duration=Math.max(1,0|n.call(e,e.__data__,r,i))}:(n=Math.max(1,0|n),function(e){e.__transition__[t].duration=n}))},Yc.each=function(n,t){var e=this.id;if(arguments.length<2){var r=Fc,i=Hc;Hc=e,T(this,function(t,r,i){Fc=t.__transition__[e],n.call(t,t.__data__,r,i)}),Fc=r,Hc=i}else T(this,function(r){var i=r.__transition__[e];(i.event||(i.event=ya.dispatch("start","end"))).on(n,t)});return this},Yc.transition=function(){for(var n,t,e,r,i=this.id,u=++Rc,a=[],o=0,c=this.length;c>o;o++){a.push(n=[]);for(var t=this[o],l=0,s=t.length;s>l;l++)(e=t[l])&&(r=Object.create(e.__transition__[i]),r.delay+=r.duration,Nu(e,l,u,r)),n.push(e)}return Eu(a,u)},ya.svg.axis=function(){function n(n){n.each(function(){var n,f=ya.select(this),h=null==l?e.ticks?e.ticks.apply(e,c):e.domain():l,g=null==t?e.tickFormat?e.tickFormat.apply(e,c):String:t,p=Cu(e,h,s),m=f.selectAll(".tick.minor").data(p,String),d=m.enter().insert("line",".tick").attr("class","tick minor").style("opacity",1e-6),v=ya.transition(m.exit()).style("opacity",1e-6).remove(),y=ya.transition(m).style("opacity",1),M=f.selectAll(".tick.major").data(h,String),x=M.enter().insert("g",".domain").attr("class","tick major").style("opacity",1e-6),b=ya.transition(M.exit()).style("opacity",1e-6).remove(),_=ya.transition(M).style("opacity",1),w=Bi(e),S=f.selectAll(".domain").data([0]),E=(S.enter().append("path").attr("class","domain"),ya.transition(S)),k=e.copy(),A=this.__chart__||k; +this.__chart__=k,x.append("line"),x.append("text");var N=x.select("line"),q=_.select("line"),T=M.select("text").text(g),C=x.select("text"),z=_.select("text");switch(r){case"bottom":n=qu,d.attr("y2",u),y.attr("x2",0).attr("y2",u),N.attr("y2",i),C.attr("y",Math.max(i,0)+o),q.attr("x2",0).attr("y2",i),z.attr("x",0).attr("y",Math.max(i,0)+o),T.attr("dy",".71em").style("text-anchor","middle"),E.attr("d","M"+w[0]+","+a+"V0H"+w[1]+"V"+a);break;case"top":n=qu,d.attr("y2",-u),y.attr("x2",0).attr("y2",-u),N.attr("y2",-i),C.attr("y",-(Math.max(i,0)+o)),q.attr("x2",0).attr("y2",-i),z.attr("x",0).attr("y",-(Math.max(i,0)+o)),T.attr("dy","0em").style("text-anchor","middle"),E.attr("d","M"+w[0]+","+-a+"V0H"+w[1]+"V"+-a);break;case"left":n=Tu,d.attr("x2",-u),y.attr("x2",-u).attr("y2",0),N.attr("x2",-i),C.attr("x",-(Math.max(i,0)+o)),q.attr("x2",-i).attr("y2",0),z.attr("x",-(Math.max(i,0)+o)).attr("y",0),T.attr("dy",".32em").style("text-anchor","end"),E.attr("d","M"+-a+","+w[0]+"H0V"+w[1]+"H"+-a);break;case"right":n=Tu,d.attr("x2",u),y.attr("x2",u).attr("y2",0),N.attr("x2",i),C.attr("x",Math.max(i,0)+o),q.attr("x2",i).attr("y2",0),z.attr("x",Math.max(i,0)+o).attr("y",0),T.attr("dy",".32em").style("text-anchor","start"),E.attr("d","M"+a+","+w[0]+"H0V"+w[1]+"H"+a)}if(e.rangeBand){var D=k.rangeBand()/2,j=function(n){return k(n)+D};x.call(n,j),_.call(n,j)}else x.call(n,A),_.call(n,k),b.call(n,k),d.call(n,A),y.call(n,k),v.call(n,k)})}var t,e=ya.scale.linear(),r=Uc,i=6,u=6,a=6,o=3,c=[10],l=null,s=0;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Ic?t+"":Uc,n):r},n.ticks=function(){return arguments.length?(c=arguments,n):c},n.tickValues=function(t){return arguments.length?(l=t,n):l},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t,e){if(!arguments.length)return i;var r=arguments.length-1;return i=+t,u=r>1?+e:i,a=r>0?+arguments[r]:i,n},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(t){return arguments.length?(s=+t,n):s},n};var Uc="bottom",Ic={top:1,right:1,bottom:1,left:1};ya.svg.brush=function(){function n(u){u.each(function(){var u,a=ya.select(this),s=a.selectAll(".background").data([0]),f=a.selectAll(".extent").data([0]),h=a.selectAll(".resize").data(l,String);a.style("pointer-events","all").on("mousedown.brush",i).on("touchstart.brush",i),s.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),f.enter().append("rect").attr("class","extent").style("cursor","move"),h.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return Vc[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),h.style("display",n.empty()?"none":null),h.exit().remove(),o&&(u=Bi(o),s.attr("x",u[0]).attr("width",u[1]-u[0]),e(a)),c&&(u=Bi(c),s.attr("y",u[0]).attr("height",u[1]-u[0]),r(a)),t(a)})}function t(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+s[+/e$/.test(n)][0]+","+s[+/^s/.test(n)][1]+")"})}function e(n){n.select(".extent").attr("x",s[0][0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",s[1][0]-s[0][0])}function r(n){n.select(".extent").attr("y",s[0][1]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",s[1][1]-s[0][1])}function i(){function i(){var n=ya.event.changedTouches;return n?ya.touches(M,n)[0]:ya.mouse(M)}function l(){32==ya.event.keyCode&&(k||(v=null,N[0]-=s[1][0],N[1]-=s[1][1],k=2),g())}function h(){32==ya.event.keyCode&&2==k&&(N[0]+=s[1][0],N[1]+=s[1][1],k=0,g())}function p(){var n=i(),u=!1;y&&(n[0]+=y[0],n[1]+=y[1]),k||(ya.event.altKey?(v||(v=[(s[0][0]+s[1][0])/2,(s[0][1]+s[1][1])/2]),N[0]=s[+(n[0]l?(i=r,r=l):i=l),s[0][e]!==r||s[1][e]!==i?(u=null,s[0][e]=r,s[1][e]=i,!0):void 0}function d(){p(),_.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),ya.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),A(),b({type:"brushend"})}var v,y,M=this,x=ya.select(ya.event.target),b=a.of(M,arguments),_=ya.select(M),w=x.datum(),S=!/^(n|s)$/.test(w)&&o,E=!/^(e|w)$/.test(w)&&c,k=x.classed("extent"),A=H(),N=i(),q=ya.select(ba).on("keydown.brush",l).on("keyup.brush",h);if(ya.event.changedTouches?q.on("touchmove.brush",p).on("touchend.brush",d):q.on("mousemove.brush",p).on("mouseup.brush",d),k)N[0]=s[0][0]-N[0],N[1]=s[0][1]-N[1];else if(w){var T=+/w$/.test(w),C=+/^n/.test(w);y=[s[1-T][0]-N[0],s[1-C][1]-N[1]],N[0]=s[T][0],N[1]=s[C][1]}else ya.event.altKey&&(v=N.slice());_.style("pointer-events","none").selectAll(".resize").style("display",null),ya.select("body").style("cursor",x.style("cursor")),b({type:"brushstart"}),p()}var u,a=m(n,"brushstart","brush","brushend"),o=null,c=null,l=Xc[0],s=[[0,0],[0,0]],f=[!0,!0];return n.x=function(t){return arguments.length?(o=t,l=Xc[!o<<1|!c],n):o},n.y=function(t){return arguments.length?(c=t,l=Xc[!o<<1|!c],n):c},n.clamp=function(t){return arguments.length?(o&&c?f=[!!t[0],!!t[1]]:(o||c)&&(f[+!o]=!!t),n):o&&c?f:o||c?f[+!o]:null},n.extent=function(t){var e,r,i,a,l;return arguments.length?(u=[[0,0],[0,0]],o&&(e=t[0],r=t[1],c&&(e=e[0],r=r[0]),u[0][0]=e,u[1][0]=r,o.invert&&(e=o(e),r=o(r)),e>r&&(l=e,e=r,r=l),s[0][0]=0|e,s[1][0]=0|r),c&&(i=t[0],a=t[1],o&&(i=i[1],a=a[1]),u[0][1]=i,u[1][1]=a,c.invert&&(i=c(i),a=c(a)),i>a&&(l=i,i=a,a=l),s[0][1]=0|i,s[1][1]=0|a),n):(t=u||s,o&&(e=t[0][0],r=t[1][0],u||(e=s[0][0],r=s[1][0],o.invert&&(e=o.invert(e),r=o.invert(r)),e>r&&(l=e,e=r,r=l))),c&&(i=t[0][1],a=t[1][1],u||(i=s[0][1],a=s[1][1],c.invert&&(i=c.invert(i),a=c.invert(a)),i>a&&(l=i,i=a,a=l))),o&&c?[[e,i],[r,a]]:o?[e,r]:c&&[i,a])},n.clear=function(){return u=null,s[0][0]=s[0][1]=s[1][0]=s[1][1]=0,n},n.empty=function(){return o&&s[0][0]===s[1][0]||c&&s[0][1]===s[1][1]},ya.rebind(n,a,"on")};var Vc={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Xc=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]];ya.time={};var Zc=Date,Bc=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];zu.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(){$c.setUTCDate.apply(this._,arguments)},setDay:function(){$c.setUTCDay.apply(this._,arguments)},setFullYear:function(){$c.setUTCFullYear.apply(this._,arguments)},setHours:function(){$c.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){$c.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){$c.setUTCMinutes.apply(this._,arguments)},setMonth:function(){$c.setUTCMonth.apply(this._,arguments)},setSeconds:function(){$c.setUTCSeconds.apply(this._,arguments)},setTime:function(){$c.setTime.apply(this._,arguments)}};var $c=Date.prototype,Wc="%a %b %e %X %Y",Jc="%m/%d/%Y",Gc="%H:%M:%S",Kc=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],Qc=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],nl=["January","February","March","April","May","June","July","August","September","October","November","December"],tl=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];ya.time.year=Du(function(n){return n=ya.time.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),ya.time.years=ya.time.year.range,ya.time.years.utc=ya.time.year.utc.range,ya.time.day=Du(function(n){var t=new Zc(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),ya.time.days=ya.time.day.range,ya.time.days.utc=ya.time.day.utc.range,ya.time.dayOfYear=function(n){var t=ya.time.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},Bc.forEach(function(n,t){n=n.toLowerCase(),t=7-t;var e=ya.time[n]=Du(function(n){return(n=ya.time.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=ya.time.year(n).getDay();return Math.floor((ya.time.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});ya.time[n+"s"]=e.range,ya.time[n+"s"].utc=e.utc.range,ya.time[n+"OfYear"]=function(n){var e=ya.time.year(n).getDay();return Math.floor((ya.time.dayOfYear(n)+(e+t)%7)/7)}}),ya.time.week=ya.time.sunday,ya.time.weeks=ya.time.sunday.range,ya.time.weeks.utc=ya.time.sunday.utc.range,ya.time.weekOfYear=ya.time.sundayOfYear,ya.time.format=function(n){function t(t){for(var r,i,u,a=[],o=-1,c=0;++o=12?"PM":"AM"},S:function(n,t){return Pu(n.getSeconds(),t,2)},U:function(n,t){return Pu(ya.time.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Pu(ya.time.mondayOfYear(n),t,2)},x:ya.time.format(Jc),X:ya.time.format(Gc),y:function(n,t){return Pu(n.getFullYear()%100,t,2)},Y:function(n,t){return Pu(n.getFullYear()%1e4,t,4)},Z:aa,"%":function(){return"%"}},gl={a:Ou,A:Yu,b:Vu,B:Xu,c:Zu,d:Qu,e:Qu,H:ta,I:ta,j:na,L:ia,m:Ku,M:ea,p:ua,S:ra,U:Uu,w:Ru,W:Iu,x:Bu,X:$u,y:Ju,Y:Wu,"%":oa},pl=/^\s*\d+/,ml=ya.map({am:0,pm:1});ya.time.format.utc=function(n){function t(n){try{Zc=zu;var t=new Zc;return t._=n,e(t)}finally{Zc=Date}}var e=ya.time.format(n);return t.parse=function(n){try{Zc=zu;var t=e.parse(n);return t&&t._}finally{Zc=Date}},t.toString=e.toString,t};var dl=ya.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ");ya.time.format.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?ca:dl,ca.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},ca.toString=dl.toString,ya.time.second=Du(function(n){return new Zc(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),ya.time.seconds=ya.time.second.range,ya.time.seconds.utc=ya.time.second.utc.range,ya.time.minute=Du(function(n){return new Zc(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),ya.time.minutes=ya.time.minute.range,ya.time.minutes.utc=ya.time.minute.utc.range,ya.time.hour=Du(function(n){var t=n.getTimezoneOffset()/60;return new Zc(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),ya.time.hours=ya.time.hour.range,ya.time.hours.utc=ya.time.hour.utc.range,ya.time.month=Du(function(n){return n=ya.time.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),ya.time.months=ya.time.month.range,ya.time.months.utc=ya.time.month.utc.range;var vl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],yl=[[ya.time.second,1],[ya.time.second,5],[ya.time.second,15],[ya.time.second,30],[ya.time.minute,1],[ya.time.minute,5],[ya.time.minute,15],[ya.time.minute,30],[ya.time.hour,1],[ya.time.hour,3],[ya.time.hour,6],[ya.time.hour,12],[ya.time.day,1],[ya.time.day,2],[ya.time.week,1],[ya.time.month,1],[ya.time.month,3],[ya.time.year,1]],Ml=[[ya.time.format("%Y"),Xt],[ya.time.format("%B"),function(n){return n.getMonth()}],[ya.time.format("%b %d"),function(n){return 1!=n.getDate()}],[ya.time.format("%a %d"),function(n){return n.getDay()&&1!=n.getDate()}],[ya.time.format("%I %p"),function(n){return n.getHours()}],[ya.time.format("%I:%M"),function(n){return n.getMinutes()}],[ya.time.format(":%S"),function(n){return n.getSeconds()}],[ya.time.format(".%L"),function(n){return n.getMilliseconds()}]],xl=ya.scale.linear(),bl=fa(Ml);yl.year=function(n,t){return xl.domain(n.map(ga)).ticks(t).map(ha)},ya.time.scale=function(){return la(ya.scale.linear(),yl,bl)};var _l=yl.map(function(n){return[n[0].utc,n[1]]}),wl=[[ya.time.format.utc("%Y"),Xt],[ya.time.format.utc("%B"),function(n){return n.getUTCMonth()}],[ya.time.format.utc("%b %d"),function(n){return 1!=n.getUTCDate()}],[ya.time.format.utc("%a %d"),function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],[ya.time.format.utc("%I %p"),function(n){return n.getUTCHours()}],[ya.time.format.utc("%I:%M"),function(n){return n.getUTCMinutes()}],[ya.time.format.utc(":%S"),function(n){return n.getUTCSeconds()}],[ya.time.format.utc(".%L"),function(n){return n.getUTCMilliseconds()}]],Sl=fa(wl);return _l.year=function(n,t){return xl.domain(n.map(ma)).ticks(t).map(pa)},ya.time.scale.utc=function(){return la(ya.scale.linear(),_l,Sl)},ya.text=dt(function(n){return n.responseText}),ya.json=function(n,t){return vt(n,"application/json",da,t)},ya.html=function(n,t){return vt(n,"text/html",va,t)},ya.xml=dt(function(n){return n.responseXML}),ya}(); \ No newline at end of file diff --git a/assets/javascripts/dashing.gridster.coffee b/assets/javascripts/dashing.gridster.coffee new file mode 100644 index 0000000..e25c561 --- /dev/null +++ b/assets/javascripts/dashing.gridster.coffee @@ -0,0 +1,37 @@ +#= require_directory ./gridster + +# This file enables gridster integration (http://gridster.net/) +# Delete it if you'd rather handle the layout yourself. +# You'll miss out on a lot if you do, but we won't hold it against you. + +Dashing.gridsterLayout = (positions) -> + Dashing.customGridsterLayout = true + positions = positions.replace(/^"|"$/g, '') + positions = $.parseJSON(positions) + widgets = $("[data-row^=]") + for widget, index in widgets + $(widget).attr('data-row', positions[index].row) + $(widget).attr('data-col', positions[index].col) + +Dashing.getWidgetPositions = -> + $(".gridster ul:first").gridster().data('gridster').serialize() + +Dashing.showGridsterInstructions = -> + newWidgetPositions = Dashing.getWidgetPositions() + + unless JSON.stringify(newWidgetPositions) == JSON.stringify(Dashing.currentWidgetPositions) + Dashing.currentWidgetPositions = newWidgetPositions + $('#save-gridster').slideDown() + $('#gridster-code').text(" + + ") + +$ -> + $('#save-gridster').leanModal() + + $('#save-gridster').click -> + $('#save-gridster').slideUp() diff --git a/assets/javascripts/gridster/jquery.gridster.js b/assets/javascripts/gridster/jquery.gridster.js new file mode 100644 index 0000000..9451c9a --- /dev/null +++ b/assets/javascripts/gridster/jquery.gridster.js @@ -0,0 +1,3263 @@ +/*! gridster.js - v0.1.0 - 2013-02-15 +* http://gridster.net/ +* Copyright (c) 2013 ducksboard; Licensed MIT */ + +;(function($, window, document, undefined){ + /** + * Creates objects with coordinates (x1, y1, x2, y2, cx, cy, width, height) + * to simulate DOM elements on the screen. + * Coords is used by Gridster to create a faux grid with any DOM element can + * collide. + * + * @class Coords + * @param {HTMLElement|Object} obj The jQuery HTMLElement or a object with: left, + * top, width and height properties. + * @return {Object} Coords instance. + * @constructor + */ + function Coords(obj) { + if (obj[0] && $.isPlainObject(obj[0])) { + this.data = obj[0]; + }else { + this.el = obj; + } + + this.isCoords = true; + this.coords = {}; + this.init(); + return this; + } + + + var fn = Coords.prototype; + + + fn.init = function(){ + this.set(); + this.original_coords = this.get(); + }; + + + fn.set = function(update, not_update_offsets) { + var el = this.el; + + if (el && !update) { + this.data = el.offset(); + this.data.width = el.width(); + this.data.height = el.height(); + } + + if (el && update && !not_update_offsets) { + var offset = el.offset(); + this.data.top = offset.top; + this.data.left = offset.left; + } + + var d = this.data; + + this.coords.x1 = d.left; + this.coords.y1 = d.top; + this.coords.x2 = d.left + d.width; + this.coords.y2 = d.top + d.height; + this.coords.cx = d.left + (d.width / 2); + this.coords.cy = d.top + (d.height / 2); + this.coords.width = d.width; + this.coords.height = d.height; + this.coords.el = el || false ; + + return this; + }; + + + fn.update = function(data){ + if (!data && !this.el) { + return this; + } + + if (data) { + var new_data = $.extend({}, this.data, data); + this.data = new_data; + return this.set(true, true); + } + + this.set(true); + return this; + }; + + + fn.get = function(){ + return this.coords; + }; + + + //jQuery adapter + $.fn.coords = function() { + if (this.data('coords') ) { + return this.data('coords'); + } + + var ins = new Coords(this, arguments[0]); + this.data('coords', ins); + return ins; + }; + +}(jQuery, window, document)); + +;(function($, window, document, undefined){ + + var defaults = { + colliders_context: document.body + // ,on_overlap: function(collider_data){}, + // on_overlap_start : function(collider_data){}, + // on_overlap_stop : function(collider_data){} + }; + + + /** + * Detects collisions between a DOM element against other DOM elements or + * Coords objects. + * + * @class Collision + * @uses Coords + * @param {HTMLElement} el The jQuery wrapped HTMLElement. + * @param {HTMLElement|Array} colliders Can be a jQuery collection + * of HTMLElements or an Array of Coords instances. + * @param {Object} [options] An Object with all options you want to + * overwrite: + * @param {Function} [options.on_overlap_start] Executes a function the first + * time each `collider ` is overlapped. + * @param {Function} [options.on_overlap_stop] Executes a function when a + * `collider` is no longer collided. + * @param {Function} [options.on_overlap] Executes a function when the + * mouse is moved during the collision. + * @return {Object} Collision instance. + * @constructor + */ + function Collision(el, colliders, options) { + this.options = $.extend(defaults, options); + this.$element = el; + this.last_colliders = []; + this.last_colliders_coords = []; + if (typeof colliders === 'string' || colliders instanceof jQuery) { + this.$colliders = $(colliders, + this.options.colliders_context).not(this.$element); + }else{ + this.colliders = $(colliders); + } + + this.init(); + } + + + var fn = Collision.prototype; + + + fn.init = function() { + this.find_collisions(); + }; + + + fn.overlaps = function(a, b) { + var x = false; + var y = false; + + if ((b.x1 >= a.x1 && b.x1 <= a.x2) || + (b.x2 >= a.x1 && b.x2 <= a.x2) || + (a.x1 >= b.x1 && a.x2 <= b.x2) + ) { x = true; } + + if ((b.y1 >= a.y1 && b.y1 <= a.y2) || + (b.y2 >= a.y1 && b.y2 <= a.y2) || + (a.y1 >= b.y1 && a.y2 <= b.y2) + ) { y = true; } + + return (x && y); + }; + + + fn.detect_overlapping_region = function(a, b){ + var regionX = ''; + var regionY = ''; + + if (a.y1 > b.cy && a.y1 < b.y2) { regionX = 'N'; } + if (a.y2 > b.y1 && a.y2 < b.cy) { regionX = 'S'; } + if (a.x1 > b.cx && a.x1 < b.x2) { regionY = 'W'; } + if (a.x2 > b.x1 && a.x2 < b.cx) { regionY = 'E'; } + + return (regionX + regionY) || 'C'; + }; + + + fn.calculate_overlapped_area_coords = function(a, b){ + var x1 = Math.max(a.x1, b.x1); + var y1 = Math.max(a.y1, b.y1); + var x2 = Math.min(a.x2, b.x2); + var y2 = Math.min(a.y2, b.y2); + + return $({ + left: x1, + top: y1, + width : (x2 - x1), + height: (y2 - y1) + }).coords().get(); + }; + + + fn.calculate_overlapped_area = function(coords){ + return (coords.width * coords.height); + }; + + + fn.manage_colliders_start_stop = function(new_colliders_coords, start_callback, stop_callback){ + var last = this.last_colliders_coords; + + for (var i = 0, il = last.length; i < il; i++) { + if ($.inArray(last[i], new_colliders_coords) === -1) { + start_callback.call(this, last[i]); + } + } + + for (var j = 0, jl = new_colliders_coords.length; j < jl; j++) { + if ($.inArray(new_colliders_coords[j], last) === -1) { + stop_callback.call(this, new_colliders_coords[j]); + } + + } + }; + + + fn.find_collisions = function(player_data_coords){ + var self = this; + var colliders_coords = []; + var colliders_data = []; + var $colliders = (this.colliders || this.$colliders); + var count = $colliders.length; + var player_coords = self.$element.coords() + .update(player_data_coords || false).get(); + + while(count--){ + var $collider = self.$colliders ? + $($colliders[count]) : $colliders[count]; + var $collider_coords_ins = ($collider.isCoords) ? + $collider : $collider.coords(); + var collider_coords = $collider_coords_ins.get(); + var overlaps = self.overlaps(player_coords, collider_coords); + + if (!overlaps) { + continue; + } + + var region = self.detect_overlapping_region( + player_coords, collider_coords); + + //todo: make this an option + if (region === 'C'){ + var area_coords = self.calculate_overlapped_area_coords( + player_coords, collider_coords); + var area = self.calculate_overlapped_area(area_coords); + var collider_data = { + area: area, + area_coords : area_coords, + region: region, + coords: collider_coords, + player_coords: player_coords, + el: $collider + }; + + if (self.options.on_overlap) { + self.options.on_overlap.call(this, collider_data); + } + colliders_coords.push($collider_coords_ins); + colliders_data.push(collider_data); + } + } + + if (self.options.on_overlap_stop || self.options.on_overlap_start) { + this.manage_colliders_start_stop(colliders_coords, + self.options.on_overlap_stop, self.options.on_overlap_start); + } + + this.last_colliders_coords = colliders_coords; + + return colliders_data; + }; + + + fn.get_closest_colliders = function(player_data_coords){ + var colliders = this.find_collisions(player_data_coords); + + colliders.sort(function(a, b) { + /* if colliders are being overlapped by the "C" (center) region, + * we have to set a lower index in the array to which they are placed + * above in the grid. */ + if (a.region === 'C' && b.region === 'C') { + if (a.coords.y1 < b.coords.y1 || a.coords.x1 < b.coords.x1) { + return - 1; + }else{ + return 1; + } + } + + if (a.area < b.area) { + return 1; + } + + return 1; + }); + return colliders; + }; + + + //jQuery adapter + $.fn.collision = function(collider, options) { + return new Collision( this, collider, options ); + }; + + +}(jQuery, window, document)); + +;(function(window, undefined) { + /* Debounce and throttle functions taken from underscore.js */ + window.debounce = function(func, wait, immediate) { + var timeout; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) func.apply(context, args); + }; + if (immediate && !timeout) func.apply(context, args); + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; + }; + + + window.throttle = function(func, wait) { + var context, args, timeout, throttling, more, result; + var whenDone = debounce( + function(){ more = throttling = false; }, wait); + return function() { + context = this; args = arguments; + var later = function() { + timeout = null; + if (more) func.apply(context, args); + whenDone(); + }; + if (!timeout) timeout = setTimeout(later, wait); + if (throttling) { + more = true; + } else { + result = func.apply(context, args); + } + whenDone(); + throttling = true; + return result; + }; + }; + +})(window); + +;(function($, window, document, undefined){ + + var defaults = { + items: '.gs_w', + distance: 1, + limit: true, + offset_left: 0, + autoscroll: true, + ignore_dragging: ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON'], + handle: null + // drag: function(e){}, + // start : function(e, ui){}, + // stop : function(e){} + }; + + var $window = $(window); + var isTouch = !!('ontouchstart' in window); + var pointer_events = { + start: isTouch ? 'touchstart' : 'mousedown.draggable', + move: isTouch ? 'touchmove' : 'mousemove.draggable', + end: isTouch ? 'touchend' : 'mouseup.draggable' + }; + + /** + * Basic drag implementation for DOM elements inside a container. + * Provide start/stop/drag callbacks. + * + * @class Draggable + * @param {HTMLElement} el The HTMLelement that contains all the widgets + * to be dragged. + * @param {Object} [options] An Object with all options you want to + * overwrite: + * @param {HTMLElement|String} [options.items] Define who will + * be the draggable items. Can be a CSS Selector String or a + * collection of HTMLElements. + * @param {Number} [options.distance] Distance in pixels after mousedown + * the mouse must move before dragging should start. + * @param {Boolean} [options.limit] Constrains dragging to the width of + * the container + * @param {offset_left} [options.offset_left] Offset added to the item + * that is being dragged. + * @param {Number} [options.drag] Executes a callback when the mouse is + * moved during the dragging. + * @param {Number} [options.start] Executes a callback when the drag + * starts. + * @param {Number} [options.stop] Executes a callback when the drag stops. + * @return {Object} Returns `el`. + * @constructor + */ + function Draggable(el, options) { + this.options = $.extend({}, defaults, options); + this.$body = $(document.body); + this.$container = $(el); + this.$dragitems = $(this.options.items, this.$container); + this.is_dragging = false; + this.player_min_left = 0 + this.options.offset_left; + this.init(); + } + + var fn = Draggable.prototype; + + fn.init = function() { + this.calculate_positions(); + this.$container.css('position', 'relative'); + this.disabled = false; + this.events(); + + this.on_window_resize = throttle($.proxy(this.calculate_positions, this), 200); + $(window).bind('resize', this.on_window_resize); + }; + + fn.events = function() { + this.proxied_on_select_start = $.proxy(this.on_select_start, this); + this.$container.on('selectstart', this.proxied_on_select_start); + + this.proxied_drag_handler = $.proxy(this.drag_handler, this); + this.$container.on(pointer_events.start, this.options.items, this.proxied_drag_handler); + + this.proxied_pointer_events_end = $.proxy(function(e) { + this.is_dragging = false; + if (this.disabled) { return; } + this.$body.off(pointer_events.move); + if (this.drag_start) { + this.on_dragstop(e); + } + }, this); + this.$body.on(pointer_events.end, this.proxied_pointer_events_end); + }; + + fn.get_actual_pos = function($el) { + var pos = $el.position(); + return pos; + }; + + + fn.get_mouse_pos = function(e) { + if (isTouch) { + var oe = e.originalEvent; + e = oe.touches.length ? oe.touches[0] : oe.changedTouches[0]; + } + + return { + left: e.clientX, + top: e.clientY + }; + }; + + + fn.get_offset = function(e) { + e.preventDefault(); + var mouse_actual_pos = this.get_mouse_pos(e); + var diff_x = Math.round( + mouse_actual_pos.left - this.mouse_init_pos.left); + var diff_y = Math.round(mouse_actual_pos.top - this.mouse_init_pos.top); + + var left = Math.round(this.el_init_offset.left + diff_x - this.baseX); + var top = Math.round( + this.el_init_offset.top + diff_y - this.baseY + this.scrollOffset); + + if (this.options.limit) { + if (left > this.player_max_left) { + left = this.player_max_left; + }else if(left < this.player_min_left) { + left = this.player_min_left; + } + } + + return { + left: left, + top: top, + mouse_left: mouse_actual_pos.left, + mouse_top: mouse_actual_pos.top + }; + }; + + + fn.manage_scroll = function(offset) { + /* scroll document */ + var nextScrollTop; + var scrollTop = $window.scrollTop(); + var min_window_y = scrollTop; + var max_window_y = min_window_y + this.window_height; + + var mouse_down_zone = max_window_y - 50; + var mouse_up_zone = min_window_y + 50; + + var abs_mouse_left = offset.mouse_left; + var abs_mouse_top = min_window_y + offset.mouse_top; + + var max_player_y = (this.doc_height - this.window_height + + this.player_height); + + if (abs_mouse_top >= mouse_down_zone) { + nextScrollTop = scrollTop + 30; + if (nextScrollTop < max_player_y) { + $window.scrollTop(nextScrollTop); + this.scrollOffset = this.scrollOffset + 30; + } + } + + if (abs_mouse_top <= mouse_up_zone) { + nextScrollTop = scrollTop - 30; + if (nextScrollTop > 0) { + $window.scrollTop(nextScrollTop); + this.scrollOffset = this.scrollOffset - 30; + } + } + }; + + + fn.calculate_positions = function(e) { + this.window_height = $window.height(); + }; + + + fn.drag_handler = function(e) { + var node = e.target.nodeName; + if (this.disabled || e.which !== 1 && !isTouch) { + return; + } + + if (this.ignore_drag(e)) { + return; + } + + var self = this; + var first = true; + this.$player = $(e.currentTarget); + + this.el_init_pos = this.get_actual_pos(this.$player); + this.mouse_init_pos = this.get_mouse_pos(e); + this.offsetY = this.mouse_init_pos.top - this.el_init_pos.top; + + this.on_pointer_events_move = function(mme){ + var mouse_actual_pos = self.get_mouse_pos(mme); + var diff_x = Math.abs( + mouse_actual_pos.left - self.mouse_init_pos.left); + var diff_y = Math.abs( + mouse_actual_pos.top - self.mouse_init_pos.top); + if (!(diff_x > self.options.distance || + diff_y > self.options.distance) + ) { + return false; + } + + if (first) { + first = false; + self.on_dragstart.call(self, mme); + return false; + } + + if (self.is_dragging === true) { + self.on_dragmove.call(self, mme); + } + + return false; + }; + + this.$body.on(pointer_events.move, this.on_pointer_events_move); + + return false; + }; + + + fn.on_dragstart = function(e) { + e.preventDefault(); + this.drag_start = true; + this.is_dragging = true; + var offset = this.$container.offset(); + this.baseX = Math.round(offset.left); + this.baseY = Math.round(offset.top); + this.doc_height = $(document).height(); + + if (this.options.helper === 'clone') { + this.$helper = this.$player.clone() + .appendTo(this.$container).addClass('helper'); + this.helper = true; + }else{ + this.helper = false; + } + this.scrollOffset = 0; + this.el_init_offset = this.$player.offset(); + this.player_width = this.$player.width(); + this.player_height = this.$player.height(); + this.player_max_left = (this.$container.width() - this.player_width + + this.options.offset_left); + + if (this.options.start) { + this.options.start.call(this.$player, e, { + helper: this.helper ? this.$helper : this.$player + }); + } + return false; + }; + + + fn.on_dragmove = function(e) { + var offset = this.get_offset(e); + + this.options.autoscroll && this.manage_scroll(offset); + + (this.helper ? this.$helper : this.$player).css({ + 'position': 'absolute', + 'left' : offset.left, + 'top' : offset.top + }); + + var ui = { + 'position': { + 'left': offset.left, + 'top': offset.top + } + }; + + if (this.options.drag) { + this.options.drag.call(this.$player, e, ui); + } + return false; + }; + + + fn.on_dragstop = function(e) { + var offset = this.get_offset(e); + this.drag_start = false; + + var ui = { + 'position': { + 'left': offset.left, + 'top': offset.top + } + }; + + if (this.options.stop) { + this.options.stop.call(this.$player, e, ui); + } + + if (this.helper) { + this.$helper.remove(); + } + + return false; + }; + + fn.on_select_start = function(e) { + if (this.disabled) { return; } + + if (this.ignore_drag(e)) { + return; + } + + return false; + }; + + fn.enable = function() { + this.disabled = false; + }; + + fn.disable = function() { + this.disabled = true; + }; + + + fn.destroy = function(){ + this.disable(); + + this.$container.off('selectstart', this.proxied_on_select_start); + this.$container.off(pointer_events.start, this.proxied_drag_handler); + this.$body.off(pointer_events.end, this.proxied_pointer_events_end); + this.$body.off(pointer_events.move, this.on_pointer_events_move); + $(window).unbind('resize', this.on_window_resize); + + $.removeData(this.$container, 'drag'); + }; + + fn.ignore_drag = function(event) { + if (this.options.handle) { + return !$(event.target).is(this.options.handle); + } + + return $.inArray(event.target.nodeName, this.options.ignore_dragging) >= 0; + }; + + //jQuery adapter + $.fn.drag = function ( options ) { + return this.each(function () { + if (!$.data(this, 'drag')) { + $.data(this, 'drag', new Draggable( this, options )); + } + }); + }; + + +}(jQuery, window, document)); + +;(function($, window, document, undefined) { + + var defaults = { + namespace: '', + widget_selector: 'li', + widget_margins: [10, 10], + widget_base_dimensions: [400, 225], + extra_rows: 0, + extra_cols: 0, + min_cols: 1, + min_rows: 15, + max_size_x: 6, + autogenerate_stylesheet: true, + avoid_overlapped_widgets: true, + serialize_params: function($w, wgd) { + return { + col: wgd.col, + row: wgd.row, + size_x: wgd.size_x, + size_y: wgd.size_y + }; + }, + collision: {}, + draggable: { + distance: 4 + } + }; + + + /** + * @class Gridster + * @uses Draggable + * @uses Collision + * @param {HTMLElement} el The HTMLelement that contains all the widgets. + * @param {Object} [options] An Object with all options you want to + * overwrite: + * @param {HTMLElement|String} [options.widget_selector] Define who will + * be the draggable widgets. Can be a CSS Selector String or a + * collection of HTMLElements + * @param {Array} [options.widget_margins] Margin between widgets. + * The first index for the horizontal margin (left, right) and + * the second for the vertical margin (top, bottom). + * @param {Array} [options.widget_base_dimensions] Base widget dimensions + * in pixels. The first index for the width and the second for the + * height. + * @param {Number} [options.extra_cols] Add more columns in addition to + * those that have been calculated. + * @param {Number} [options.extra_rows] Add more rows in addition to + * those that have been calculated. + * @param {Number} [options.min_cols] The minimum required columns. + * @param {Number} [options.min_rows] The minimum required rows. + * @param {Number} [options.max_size_x] The maximum number of columns + * that a widget can span. + * @param {Boolean} [options.autogenerate_stylesheet] If true, all the + * CSS required to position all widgets in their respective columns + * and rows will be generated automatically and injected to the + * `` of the document. You can set this to false, and write + * your own CSS targeting rows and cols via data-attributes like so: + * `[data-col="1"] { left: 10px; }` + * @param {Boolean} [options.avoid_overlapped_widgets] Avoid that widgets loaded + * from the DOM can be overlapped. It is helpful if the positions were + * bad stored in the database or if there was any conflict. + * @param {Function} [options.serialize_params] Return the data you want + * for each widget in the serialization. Two arguments are passed: + * `$w`: the jQuery wrapped HTMLElement, and `wgd`: the grid + * coords object (`col`, `row`, `size_x`, `size_y`). + * @param {Object} [options.collision] An Object with all options for + * Collision class you want to overwrite. See Collision docs for + * more info. + * @param {Object} [options.draggable] An Object with all options for + * Draggable class you want to overwrite. See Draggable docs for more + * info. + * + * @constructor + */ + function Gridster(el, options) { + this.options = $.extend(true, defaults, options); + this.$el = $(el); + this.$wrapper = this.$el.parent(); + this.$widgets = this.$el.children(this.options.widget_selector).addClass('gs_w'); + this.widgets = []; + this.$changed = $([]); + this.wrapper_width = this.$wrapper.width(); + this.min_widget_width = (this.options.widget_margins[0] * 2) + + this.options.widget_base_dimensions[0]; + this.min_widget_height = (this.options.widget_margins[1] * 2) + + this.options.widget_base_dimensions[1]; + this.init(); + } + + Gridster.generated_stylesheets = []; + + var fn = Gridster.prototype; + + fn.init = function() { + this.generate_grid_and_stylesheet(); + this.get_widgets_from_DOM(); + this.set_dom_grid_height(); + this.$wrapper.addClass('ready'); + this.draggable(); + + this.on_window_resize = throttle($.proxy(this.recalculate_faux_grid, this), 200); + + $(window).bind('resize', this.on_window_resize); + }; + + + /** + * Disables dragging. + * + * @method disable + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.disable = function() { + this.$wrapper.find('.player-revert').removeClass('player-revert'); + this.drag_api.disable(); + return this; + }; + + + /** + * Enables dragging. + * + * @method enable + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.enable = function() { + this.drag_api.enable(); + return this; + }; + + + /** + * Add a new widget to the grid. + * + * @method add_widget + * @param {String|HTMLElement} html The string representing the HTML of the widget + * or the HTMLElement. + * @param {Number} [size_x] The nº of rows the widget occupies horizontally. + * @param {Number} [size_y] The nº of columns the widget occupies vertically. + * @param {Number} [col] The column the widget should start in. + * @param {Number} [row] The row the widget should start in. + * @return {HTMLElement} Returns the jQuery wrapped HTMLElement representing. + * the widget that was just created. + */ + fn.add_widget = function(html, size_x, size_y, col, row) { + var pos; + size_x || (size_x = 1); + size_y || (size_y = 1); + + if (!col & !row) { + pos = this.next_position(size_x, size_y); + }else{ + pos = { + col: col, + row: row + }; + + this.empty_cells(col, row, size_x, size_y); + } + + var $w = $(html).attr({ + 'data-col': pos.col, + 'data-row': pos.row, + 'data-sizex' : size_x, + 'data-sizey' : size_y + }).addClass('gs_w').appendTo(this.$el).hide(); + + this.$widgets = this.$widgets.add($w); + + this.register_widget($w); + + this.add_faux_rows(pos.size_y); + //this.add_faux_cols(pos.size_x); + + this.set_dom_grid_height(); + + return $w.fadeIn(); + }; + + + + /** + * Change the size of a widget. + * + * @method resize_widget + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement + * representing the widget. + * @param {Number} size_x The number of columns that will occupy the widget. + * @param {Number} size_y The number of rows that will occupy the widget. + * @return {HTMLElement} Returns $widget. + */ + fn.resize_widget = function($widget, size_x, size_y) { + var wgd = $widget.coords().grid; + size_x || (size_x = wgd.size_x); + size_y || (size_y = wgd.size_y); + + if (size_x > this.cols) { + size_x = this.cols; + } + + var old_cells_occupied = this.get_cells_occupied(wgd); + var old_size_x = wgd.size_x; + var old_size_y = wgd.size_y; + var old_col = wgd.col; + var new_col = old_col; + var wider = size_x > old_size_x; + var taller = size_y > old_size_y; + + if (old_col + size_x - 1 > this.cols) { + var diff = old_col + (size_x - 1) - this.cols; + var c = old_col - diff; + new_col = Math.max(1, c); + } + + var new_grid_data = { + col: new_col, + row: wgd.row, + size_x: size_x, + size_y: size_y + }; + + var new_cells_occupied = this.get_cells_occupied(new_grid_data); + + var empty_cols = []; + $.each(old_cells_occupied.cols, function(i, col) { + if ($.inArray(col, new_cells_occupied.cols) === -1) { + empty_cols.push(col); + } + }); + + var occupied_cols = []; + $.each(new_cells_occupied.cols, function(i, col) { + if ($.inArray(col, old_cells_occupied.cols) === -1) { + occupied_cols.push(col); + } + }); + + var empty_rows = []; + $.each(old_cells_occupied.rows, function(i, row) { + if ($.inArray(row, new_cells_occupied.rows) === -1) { + empty_rows.push(row); + } + }); + + var occupied_rows = []; + $.each(new_cells_occupied.rows, function(i, row) { + if ($.inArray(row, old_cells_occupied.rows) === -1) { + occupied_rows.push(row); + } + }); + + this.remove_from_gridmap(wgd); + + if (occupied_cols.length) { + var cols_to_empty = [ + new_col, wgd.row, size_x, Math.min(old_size_y, size_y), $widget + ]; + this.empty_cells.apply(this, cols_to_empty); + } + + if (occupied_rows.length) { + var rows_to_empty = [new_col, wgd.row, size_x, size_y, $widget]; + this.empty_cells.apply(this, rows_to_empty); + } + + wgd.col = new_col; + wgd.size_x = size_x; + wgd.size_y = size_y; + this.add_to_gridmap(new_grid_data, $widget); + + //update coords instance attributes + $widget.data('coords').update({ + width: (size_x * this.options.widget_base_dimensions[0] + + ((size_x - 1) * this.options.widget_margins[0]) * 2), + height: (size_y * this.options.widget_base_dimensions[1] + + ((size_y - 1) * this.options.widget_margins[1]) * 2) + }); + + if (size_y > old_size_y) { + this.add_faux_rows(size_y - old_size_y); + } + + if (size_x > old_size_x) { + this.add_faux_cols(size_x - old_size_x); + } + + $widget.attr({ + 'data-col': new_col, + 'data-sizex': size_x, + 'data-sizey': size_y + }); + + if (empty_cols.length) { + var cols_to_remove_holes = [ + empty_cols[0], wgd.row, + empty_cols.length, + Math.min(old_size_y, size_y), + $widget + ]; + + this.remove_empty_cells.apply(this, cols_to_remove_holes); + } + + if (empty_rows.length) { + var rows_to_remove_holes = [ + new_col, wgd.row, size_x, size_y, $widget + ]; + this.remove_empty_cells.apply(this, rows_to_remove_holes); + } + + return $widget; + }; + + /** + * Move down widgets in cells represented by the arguments col, row, size_x, + * size_y + * + * @method empty_cells + * @param {Number} col The column where the group of cells begin. + * @param {Number} row The row where the group of cells begin. + * @param {Number} size_x The number of columns that the group of cells + * occupy. + * @param {Number} size_y The number of rows that the group of cells + * occupy. + * @param {HTMLElement} $exclude Exclude widgets from being moved. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.empty_cells = function(col, row, size_x, size_y, $exclude) { + var $nexts = this.widgets_below({ + col: col, + row: row - size_y, + size_x: size_x, + size_y: size_y + }); + + $nexts.not($exclude).each($.proxy(function(i, w) { + var wgd = $(w).coords().grid; + if (!(wgd.row <= (row + size_y - 1))) { return; } + var diff = (row + size_y) - wgd.row; + this.move_widget_down($(w), diff); + }, this)); + + this.set_dom_grid_height(); + + return this; + }; + + + /** + * Move up widgets below cells represented by the arguments col, row, size_x, + * size_y. + * + * @method remove_empty_cells + * @param {Number} col The column where the group of cells begin. + * @param {Number} row The row where the group of cells begin. + * @param {Number} size_x The number of columns that the group of cells + * occupy. + * @param {Number} size_y The number of rows that the group of cells + * occupy. + * @param {HTMLElement} exclude Exclude widgets from being moved. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.remove_empty_cells = function(col, row, size_x, size_y, exclude) { + var $nexts = this.widgets_below({ + col: col, + row: row, + size_x: size_x, + size_y: size_y + }); + + $nexts.not(exclude).each($.proxy(function(i, widget) { + this.move_widget_up( $(widget), size_y ); + }, this)); + + this.set_dom_grid_height(); + + return this; + }; + + + /** + * Get the most left column below to add a new widget. + * + * @method next_position + * @param {Number} size_x The nº of rows the widget occupies horizontally. + * @param {Number} size_y The nº of columns the widget occupies vertically. + * @return {Object} Returns a grid coords object representing the future + * widget coords. + */ + fn.next_position = function(size_x, size_y) { + size_x || (size_x = 1); + size_y || (size_y = 1); + var ga = this.gridmap; + var cols_l = ga.length; + var valid_pos = []; + var rows_l; + + for (var c = 1; c < cols_l; c++) { + rows_l = ga[c].length; + for (var r = 1; r <= rows_l; r++) { + var can_move_to = this.can_move_to({ + size_x: size_x, + size_y: size_y + }, c, r); + + if (can_move_to) { + valid_pos.push({ + col: c, + row: r, + size_y: size_y, + size_x: size_x + }); + } + } + } + + if (valid_pos.length) { + return this.sort_by_row_and_col_asc(valid_pos)[0]; + } + return false; + }; + + + /** + * Remove a widget from the grid. + * + * @method remove_widget + * @param {HTMLElement} el The jQuery wrapped HTMLElement you want to remove. + * @param {Boolean|Function} silent If true, widgets below the removed one + * will not move up. If a Function is passed it will be used as callback. + * @param {Function} callback Function executed when the widget is removed. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.remove_widget = function(el, silent, callback) { + var $el = el instanceof jQuery ? el : $(el); + var wgd = $el.coords().grid; + + // if silent is a function assume it's a callback + if ($.isFunction(silent)) { + callback = silent; + silent = false; + } + + this.cells_occupied_by_placeholder = {}; + this.$widgets = this.$widgets.not($el); + + var $nexts = this.widgets_below($el); + + this.remove_from_gridmap(wgd); + + $el.fadeOut($.proxy(function() { + $el.remove(); + + if (!silent) { + $nexts.each($.proxy(function(i, widget) { + this.move_widget_up( $(widget), wgd.size_y ); + }, this)); + } + + this.set_dom_grid_height(); + + if (callback) { + callback.call(this, el); + } + }, this)); + }; + + + /** + * Remove all widgets from the grid. + * + * @method remove_all_widgets + * @param {Function} callback Function executed for each widget removed. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.remove_all_widgets = function(callback) { + this.$widgets.each($.proxy(function(i, el){ + this.remove_widget(el, true, callback); + }, this)); + + return this; + }; + + + /** + * Returns a serialized array of the widgets in the grid. + * + * @method serialize + * @param {HTMLElement} [$widgets] The collection of jQuery wrapped + * HTMLElements you want to serialize. If no argument is passed all widgets + * will be serialized. + * @return {Array} Returns an Array of Objects with the data specified in + * the serialize_params option. + */ + fn.serialize = function($widgets) { + $widgets || ($widgets = this.$widgets); + var result = []; + $widgets.each($.proxy(function(i, widget) { + result.push(this.options.serialize_params( + $(widget), $(widget).coords().grid ) ); + }, this)); + + return result; + }; + + + /** + * Returns a serialized array of the widgets that have changed their + * position. + * + * @method serialize_changed + * @return {Array} Returns an Array of Objects with the data specified in + * the serialize_params option. + */ + fn.serialize_changed = function() { + return this.serialize(this.$changed); + }; + + + /** + * Creates the grid coords object representing the widget a add it to the + * mapped array of positions. + * + * @method register_widget + * @return {Array} Returns the instance of the Gridster class. + */ + fn.register_widget = function($el) { + + var wgd = { + 'col': parseInt($el.attr('data-col'), 10), + 'row': parseInt($el.attr('data-row'), 10), + 'size_x': parseInt($el.attr('data-sizex'), 10), + 'size_y': parseInt($el.attr('data-sizey'), 10), + 'el': $el + }; + + if (this.options.avoid_overlapped_widgets && + !this.can_move_to( + {size_x: wgd.size_x, size_y: wgd.size_y}, wgd.col, wgd.row) + ) { + wgd = this.next_position(wgd.size_x, wgd.size_y); + wgd.el = $el; + $el.attr({ + 'data-col': wgd.col, + 'data-row': wgd.row, + 'data-sizex': wgd.size_x, + 'data-sizey': wgd.size_y + }); + } + + // attach Coord object to player data-coord attribute + $el.data('coords', $el.coords()); + + // Extend Coord object with grid position info + $el.data('coords').grid = wgd; + + this.add_to_gridmap(wgd, $el); + + return this; + }; + + + /** + * Update in the mapped array of positions the value of cells represented by + * the grid coords object passed in the `grid_data` param. + * + * @param {Object} grid_data The grid coords object representing the cells + * to update in the mapped array. + * @param {HTMLElement|Boolean} value Pass `false` or the jQuery wrapped + * HTMLElement, depends if you want to delete an existing position or add + * a new one. + * @method update_widget_position + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.update_widget_position = function(grid_data, value) { + this.for_each_cell_occupied(grid_data, function(col, row) { + if (!this.gridmap[col]) { return this; } + this.gridmap[col][row] = value; + }); + return this; + }; + + + /** + * Remove a widget from the mapped array of positions. + * + * @method remove_from_gridmap + * @param {Object} grid_data The grid coords object representing the cells + * to update in the mapped array. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.remove_from_gridmap = function(grid_data) { + return this.update_widget_position(grid_data, false); + }; + + + /** + * Add a widget to the mapped array of positions. + * + * @method add_to_gridmap + * @param {Object} grid_data The grid coords object representing the cells + * to update in the mapped array. + * @param {HTMLElement|Boolean} value The value to set in the specified + * position . + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.add_to_gridmap = function(grid_data, value) { + this.update_widget_position(grid_data, value || grid_data.el); + + if (grid_data.el) { + var $widgets = this.widgets_below(grid_data.el); + $widgets.each($.proxy(function(i, widget) { + this.move_widget_up( $(widget)); + }, this)); + } + }; + + + /** + * Make widgets draggable. + * + * @uses Draggable + * @method draggable + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.draggable = function() { + var self = this; + var draggable_options = $.extend(true, {}, this.options.draggable, { + offset_left: this.options.widget_margins[0], + start: function(event, ui) { + self.$widgets.filter('.player-revert') + .removeClass('player-revert'); + + self.$player = $(this); + self.$helper = self.options.draggable.helper === 'clone' ? + $(ui.helper) : self.$player; + self.helper = !self.$helper.is(self.$player); + + self.on_start_drag.call(self, event, ui); + self.$el.trigger('gridster:dragstart'); + }, + stop: function(event, ui) { + self.on_stop_drag.call(self, event, ui); + self.$el.trigger('gridster:dragstop'); + }, + drag: throttle(function(event, ui) { + self.on_drag.call(self, event, ui); + self.$el.trigger('gridster:drag'); + }, 60) + }); + + this.drag_api = this.$el.drag(draggable_options).data('drag'); + return this; + }; + + + /** + * This function is executed when the player begins to be dragged. + * + * @method on_start_drag + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object. + */ + fn.on_start_drag = function(event, ui) { + + this.$helper.add(this.$player).add(this.$wrapper).addClass('dragging'); + + this.$player.addClass('player'); + this.player_grid_data = this.$player.coords().grid; + this.placeholder_grid_data = $.extend({}, this.player_grid_data); + + //set new grid height along the dragging period + this.$el.css('height', this.$el.height() + + (this.player_grid_data.size_y * this.min_widget_height)); + + var colliders = this.faux_grid; + var coords = this.$player.data('coords').coords; + + this.cells_occupied_by_player = this.get_cells_occupied( + this.player_grid_data); + this.cells_occupied_by_placeholder = this.get_cells_occupied( + this.placeholder_grid_data); + + this.last_cols = []; + this.last_rows = []; + + + // see jquery.collision.js + this.collision_api = this.$helper.collision( + colliders, this.options.collision); + + this.$preview_holder = $('
  • ', { + 'class': 'preview-holder', + 'data-row': this.$player.attr('data-row'), + 'data-col': this.$player.attr('data-col'), + css: { + width: coords.width, + height: coords.height + } + }).appendTo(this.$el); + + if (this.options.draggable.start) { + this.options.draggable.start.call(this, event, ui); + } + }; + + + /** + * This function is executed when the player is being dragged. + * + * @method on_drag + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object. + */ + fn.on_drag = function(event, ui) { + //break if dragstop has been fired + if (this.$player === null) { + return false; + } + + var abs_offset = { + left: ui.position.left + this.baseX, + top: ui.position.top + this.baseY + }; + + this.colliders_data = this.collision_api.get_closest_colliders( + abs_offset); + + this.on_overlapped_column_change( + this.on_start_overlapping_column, + this.on_stop_overlapping_column + ); + + this.on_overlapped_row_change( + this.on_start_overlapping_row, + this.on_stop_overlapping_row + ); + + if (this.helper && this.$player) { + this.$player.css({ + 'left': ui.position.left, + 'top': ui.position.top + }); + } + + if (this.options.draggable.drag) { + this.options.draggable.drag.call(this, event, ui); + } + }; + + /** + * This function is executed when the player stops being dragged. + * + * @method on_stop_drag + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object. + */ + fn.on_stop_drag = function(event, ui) { + this.$helper.add(this.$player).add(this.$wrapper) + .removeClass('dragging'); + + ui.position.left = ui.position.left + this.baseX; + ui.position.top = ui.position.top + this.baseY; + this.colliders_data = this.collision_api.get_closest_colliders(ui.position); + + this.on_overlapped_column_change( + this.on_start_overlapping_column, + this.on_stop_overlapping_column + ); + + this.on_overlapped_row_change( + this.on_start_overlapping_row, + this.on_stop_overlapping_row + ); + + this.$player.addClass('player-revert').removeClass('player') + .attr({ + 'data-col': this.placeholder_grid_data.col, + 'data-row': this.placeholder_grid_data.row + }).css({ + 'left': '', + 'top': '' + }); + + this.$changed = this.$changed.add(this.$player); + + this.cells_occupied_by_player = this.get_cells_occupied( + this.placeholder_grid_data); + this.set_cells_player_occupies( + this.placeholder_grid_data.col, this.placeholder_grid_data.row); + + this.$player.coords().grid.row = this.placeholder_grid_data.row; + this.$player.coords().grid.col = this.placeholder_grid_data.col; + + if (this.options.draggable.stop) { + this.options.draggable.stop.call(this, event, ui); + } + + this.$preview_holder.remove(); + + this.$player = null; + this.$helper = null; + this.placeholder_grid_data = {}; + this.player_grid_data = {}; + this.cells_occupied_by_placeholder = {}; + this.cells_occupied_by_player = {}; + + this.set_dom_grid_height(); + }; + + + /** + * Executes the callbacks passed as arguments when a column begins to be + * overlapped or stops being overlapped. + * + * @param {Function} start_callback Function executed when a new column + * begins to be overlapped. The column is passed as first argument. + * @param {Function} stop_callback Function executed when a column stops + * being overlapped. The column is passed as first argument. + * @method on_overlapped_column_change + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.on_overlapped_column_change = function(start_callback, stop_callback) { + if (!this.colliders_data.length) { + return this; + } + var cols = this.get_targeted_columns( + this.colliders_data[0].el.data.col); + + var last_n_cols = this.last_cols.length; + var n_cols = cols.length; + var i; + + for (i = 0; i < n_cols; i++) { + if ($.inArray(cols[i], this.last_cols) === -1) { + (start_callback || $.noop).call(this, cols[i]); + } + } + + for (i = 0; i< last_n_cols; i++) { + if ($.inArray(this.last_cols[i], cols) === -1) { + (stop_callback || $.noop).call(this, this.last_cols[i]); + } + } + + this.last_cols = cols; + + return this; + }; + + + /** + * Executes the callbacks passed as arguments when a row starts to be + * overlapped or stops being overlapped. + * + * @param {Function} start_callback Function executed when a new row begins + * to be overlapped. The row is passed as first argument. + * @param {Function} end_callback Function executed when a row stops being + * overlapped. The row is passed as first argument. + * @method on_overlapped_row_change + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.on_overlapped_row_change = function(start_callback, end_callback) { + if (!this.colliders_data.length) { + return this; + } + var rows = this.get_targeted_rows(this.colliders_data[0].el.data.row); + var last_n_rows = this.last_rows.length; + var n_rows = rows.length; + var i; + + for (i = 0; i < n_rows; i++) { + if ($.inArray(rows[i], this.last_rows) === -1) { + (start_callback || $.noop).call(this, rows[i]); + } + } + + for (i = 0; i < last_n_rows; i++) { + if ($.inArray(this.last_rows[i], rows) === -1) { + (end_callback || $.noop).call(this, this.last_rows[i]); + } + } + + this.last_rows = rows; + }; + + + /** + * Sets the current position of the player + * + * @param {Number} col + * @param {Number} row + * @param {Boolean} no_player + * @method set_player + * @return {object} + */ + fn.set_player = function(col, row, no_player) { + var self = this; + if (!no_player) { + this.empty_cells_player_occupies(); + } + var cell = !no_player ? self.colliders_data[0].el.data : {col: col}; + var to_col = cell.col; + var to_row = row || cell.row; + + this.player_grid_data = { + col: to_col, + row: to_row, + size_y : this.player_grid_data.size_y, + size_x : this.player_grid_data.size_x + }; + + this.cells_occupied_by_player = this.get_cells_occupied( + this.player_grid_data); + + var $overlapped_widgets = this.get_widgets_overlapped( + this.player_grid_data); + + var constraints = this.widgets_constraints($overlapped_widgets); + + this.manage_movements(constraints.can_go_up, to_col, to_row); + this.manage_movements(constraints.can_not_go_up, to_col, to_row); + + /* if there is not widgets overlapping in the new player position, + * update the new placeholder position. */ + if (!$overlapped_widgets.length) { + var pp = this.can_go_player_up(this.player_grid_data); + if (pp !== false) { + to_row = pp; + } + this.set_placeholder(to_col, to_row); + } + + return { + col: to_col, + row: to_row + }; + }; + + + /** + * See which of the widgets in the $widgets param collection can go to + * a upper row and which not. + * + * @method widgets_contraints + * @param {jQuery} $widgets A jQuery wrapped collection of + * HTMLElements. + * @return {object} Returns a literal Object with two keys: `can_go_up` & + * `can_not_go_up`. Each contains a set of HTMLElements. + */ + fn.widgets_constraints = function($widgets) { + var $widgets_can_go_up = $([]); + var $widgets_can_not_go_up; + var wgd_can_go_up = []; + var wgd_can_not_go_up = []; + + $widgets.each($.proxy(function(i, w) { + var $w = $(w); + var wgd = $w.coords().grid; + if (this.can_go_widget_up(wgd)) { + $widgets_can_go_up = $widgets_can_go_up.add($w); + wgd_can_go_up.push(wgd); + }else{ + wgd_can_not_go_up.push(wgd); + } + }, this)); + + $widgets_can_not_go_up = $widgets.not($widgets_can_go_up); + + return { + can_go_up: this.sort_by_row_asc(wgd_can_go_up), + can_not_go_up: this.sort_by_row_desc(wgd_can_not_go_up) + }; + }; + + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) in ascending way. + * + * @method sort_by_row_asc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + fn.sort_by_row_asc = function(widgets) { + widgets = widgets.sort(function(a, b) { + if (!a.row) { + a = $(a).coords().grid; + b = $(b).coords().grid; + } + + if (a.row > b.row) { + return 1; + } + return -1; + }); + + return widgets; + }; + + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) placing first the empty cells upper left. + * + * @method sort_by_row_and_col_asc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + fn.sort_by_row_and_col_asc = function(widgets) { + widgets = widgets.sort(function(a, b) { + if (a.row > b.row || a.row === b.row && a.col > b.col) { + return 1; + } + return -1; + }); + + return widgets; + }; + + + /** + * Sorts an Array of grid coords objects by column (representing the grid + * coords of each widget) in ascending way. + * + * @method sort_by_col_asc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + fn.sort_by_col_asc = function(widgets) { + widgets = widgets.sort(function(a, b) { + if (a.col > b.col) { + return 1; + } + return -1; + }); + + return widgets; + }; + + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) in descending way. + * + * @method sort_by_row_desc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + fn.sort_by_row_desc = function(widgets) { + widgets = widgets.sort(function(a, b) { + if (a.row + a.size_y < b.row + b.size_y) { + return 1; + } + return -1; + }); + return widgets; + }; + + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) in descending way. + * + * @method manage_movements + * @param {jQuery} $widgets A jQuery collection of HTMLElements + * representing the widgets you want to move. + * @param {Number} to_col The column to which we want to move the widgets. + * @param {Number} to_row The row to which we want to move the widgets. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.manage_movements = function($widgets, to_col, to_row) { + $.each($widgets, $.proxy(function(i, w) { + var wgd = w; + var $w = wgd.el; + + var can_go_widget_up = this.can_go_widget_up(wgd); + + if (can_go_widget_up) { + //target CAN go up + //so move widget up + this.move_widget_to($w, can_go_widget_up); + this.set_placeholder(to_col, can_go_widget_up + wgd.size_y); + + } else { + //target can't go up + var can_go_player_up = this.can_go_player_up( + this.player_grid_data); + + if (!can_go_player_up) { + // target can't go up + // player cant't go up + // so we need to move widget down to a position that dont + // overlaps player + var y = (to_row + this.player_grid_data.size_y) - wgd.row; + + this.move_widget_down($w, y); + this.set_placeholder(to_col, to_row); + } + } + }, this)); + + return this; + }; + + /** + * Determines if there is a widget in the row and col given. Or if the + * HTMLElement passed as first argument is the player. + * + * @method is_player + * @param {Number|HTMLElement} col_or_el A jQuery wrapped collection of + * HTMLElements. + * @param {Number} [row] The column to which we want to move the widgets. + * @return {Boolean} Returns true or false. + */ + fn.is_player = function(col_or_el, row) { + if (row && !this.gridmap[col_or_el]) { return false; } + var $w = row ? this.gridmap[col_or_el][row] : col_or_el; + return $w && ($w.is(this.$player) || $w.is(this.$helper)); + }; + + + /** + * Determines if the widget that is being dragged is currently over the row + * and col given. + * + * @method is_player_in + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_player_in = function(col, row) { + var c = this.cells_occupied_by_player || {}; + return $.inArray(col, c.cols) >= 0 && $.inArray(row, c.rows) >= 0; + }; + + + /** + * Determines if the placeholder is currently over the row and col given. + * + * @method is_placeholder_in + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_placeholder_in = function(col, row) { + var c = this.cells_occupied_by_placeholder || {}; + return this.is_placeholder_in_col(col) && $.inArray(row, c.rows) >= 0; + }; + + + /** + * Determines if the placeholder is currently over the column given. + * + * @method is_placeholder_in_col + * @param {Number} col The column to check. + * @return {Boolean} Returns true or false. + */ + fn.is_placeholder_in_col = function(col) { + var c = this.cells_occupied_by_placeholder || []; + return $.inArray(col, c.cols) >= 0; + }; + + + /** + * Determines if the cell represented by col and row params is empty. + * + * @method is_empty + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_empty = function(col, row) { + if (typeof this.gridmap[col] !== 'undefined' && + typeof this.gridmap[col][row] !== 'undefined' && + this.gridmap[col][row] === false + ) { + return true; + } + return false; + }; + + + /** + * Determines if the cell represented by col and row params is occupied. + * + * @method is_occupied + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_occupied = function(col, row) { + if (!this.gridmap[col]) { + return false; + } + + if (this.gridmap[col][row]) { + return true; + } + return false; + }; + + + /** + * Determines if there is a widget in the cell represented by col/row params. + * + * @method is_widget + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean|HTMLElement} Returns false if there is no widget, + * else returns the jQuery HTMLElement + */ + fn.is_widget = function(col, row) { + var cell = this.gridmap[col]; + if (!cell) { + return false; + } + + cell = cell[row]; + + if (cell) { + return cell; + } + + return false; + }; + + + /** + * Determines if there is a widget in the cell represented by col/row + * params and if this is under the widget that is being dragged. + * + * @method is_widget_under_player + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_widget_under_player = function(col, row) { + if (this.is_widget(col, row)) { + return this.is_player_in(col, row); + } + return false; + }; + + + /** + * Get widgets overlapping with the player or with the object passed + * representing the grid cells. + * + * @method get_widgets_under_player + * @return {HTMLElement} Returns a jQuery collection of HTMLElements + */ + fn.get_widgets_under_player = function(cells) { + cells || (cells = this.cells_occupied_by_player || {cols: [], rows: []}); + var $widgets = $([]); + + $.each(cells.cols, $.proxy(function(i, col) { + $.each(cells.rows, $.proxy(function(i, row) { + if(this.is_widget(col, row)) { + $widgets = $widgets.add(this.gridmap[col][row]); + } + }, this)); + }, this)); + + return $widgets; + }; + + + /** + * Put placeholder at the row and column specified. + * + * @method set_placeholder + * @param {Number} col The column to which we want to move the + * placeholder. + * @param {Number} row The row to which we want to move the + * placeholder. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.set_placeholder = function(col, row) { + var phgd = $.extend({}, this.placeholder_grid_data); + var $nexts = this.widgets_below({ + col: phgd.col, + row: phgd.row, + size_y: phgd.size_y, + size_x: phgd.size_x + }); + + // Prevents widgets go out of the grid + var right_col = (col + phgd.size_x - 1); + if (right_col > this.cols) { + col = col - (right_col - col); + } + + var moved_down = this.placeholder_grid_data.row < row; + var changed_column = this.placeholder_grid_data.col !== col; + + this.placeholder_grid_data.col = col; + this.placeholder_grid_data.row = row; + + this.cells_occupied_by_placeholder = this.get_cells_occupied( + this.placeholder_grid_data); + + this.$preview_holder.attr({ + 'data-row' : row, + 'data-col' : col + }); + + if (moved_down || changed_column) { + $nexts.each($.proxy(function(i, widget) { + this.move_widget_up( + $(widget), this.placeholder_grid_data.col - col + phgd.size_y); + }, this)); + } + + + var $widgets_under_ph = this.get_widgets_under_player(this.cells_occupied_by_placeholder); + if ($widgets_under_ph.length) { + $widgets_under_ph.each($.proxy(function(i, widget) { + var $w = $(widget); + this.move_widget_down( + $w, row + phgd.size_y - $w.data('coords').grid.row); + }, this)); + } + + }; + + + /** + * Determines whether the player can move to a position above. + * + * @method can_go_player_up + * @param {Object} widget_grid_data The actual grid coords object of the + * player. + * @return {Number|Boolean} If the player can be moved to an upper row + * returns the row number, else returns false. + */ + fn.can_go_player_up = function(widget_grid_data) { + var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; + var result = true; + var upper_rows = []; + var min_row = 10000; + var $widgets_under_player = this.get_widgets_under_player(); + + /* generate an array with columns as index and array with upper rows + * empty as value */ + this.for_each_column_occupied(widget_grid_data, function(tcol) { + var grid_col = this.gridmap[tcol]; + var r = p_bottom_row + 1; + upper_rows[tcol] = []; + + while (--r > 0) { + if (this.is_empty(tcol, r) || this.is_player(tcol, r) || + this.is_widget(tcol, r) && + grid_col[r].is($widgets_under_player) + ) { + upper_rows[tcol].push(r); + min_row = r < min_row ? r : min_row; + }else{ + break; + } + } + + if (upper_rows[tcol].length === 0) { + result = false; + return true; //break + } + + upper_rows[tcol].sort(); + }); + + if (!result) { return false; } + + return this.get_valid_rows(widget_grid_data, upper_rows, min_row); + }; + + + /** + * Determines whether a widget can move to a position above. + * + * @method can_go_widget_up + * @param {Object} widget_grid_data The actual grid coords object of the + * widget we want to check. + * @return {Number|Boolean} If the widget can be moved to an upper row + * returns the row number, else returns false. + */ + fn.can_go_widget_up = function(widget_grid_data) { + var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; + var result = true; + var upper_rows = []; + var min_row = 10000; + + /* generate an array with columns as index and array with topmost rows + * empty as value */ + this.for_each_column_occupied(widget_grid_data, function(tcol) { + var grid_col = this.gridmap[tcol]; + upper_rows[tcol] = []; + + var r = p_bottom_row + 1; + // iterate over each row + while (--r > 0) { + if (this.is_widget(tcol, r) && !this.is_player_in(tcol, r)) { + if (!grid_col[r].is(widget_grid_data.el)) { + break; + } + } + + if (!this.is_player(tcol, r) && + !this.is_placeholder_in(tcol, r) && + !this.is_player_in(tcol, r)) { + upper_rows[tcol].push(r); + } + + if (r < min_row) { + min_row = r; + } + } + + if (upper_rows[tcol].length === 0) { + result = false; + return true; //break + } + + upper_rows[tcol].sort(); + }); + + if (!result) { return false; } + + return this.get_valid_rows(widget_grid_data, upper_rows, min_row); + }; + + + /** + * Search a valid row for the widget represented by `widget_grid_data' in + * the `upper_rows` array. Iteration starts from row specified in `min_row`. + * + * @method get_valid_rows + * @param {Object} widget_grid_data The actual grid coords object of the + * player. + * @param {Array} upper_rows An array with columns as index and arrays + * of valid rows as values. + * @param {Number} min_row The upper row from which the iteration will start. + * @return {Number|Boolean} Returns the upper row valid from the `upper_rows` + * for the widget in question. + */ + fn.get_valid_rows = function(widget_grid_data, upper_rows, min_row) { + var p_top_row = widget_grid_data.row; + var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; + var size_y = widget_grid_data.size_y; + var r = min_row - 1; + var valid_rows = []; + + while (++r <= p_bottom_row ) { + var common = true; + $.each(upper_rows, function(col, rows) { + if ($.isArray(rows) && $.inArray(r, rows) === -1) { + common = false; + } + }); + + if (common === true) { + valid_rows.push(r); + if (valid_rows.length === size_y) { + break; + } + } + } + + var new_row = false; + if (size_y === 1) { + if (valid_rows[0] !== p_top_row) { + new_row = valid_rows[0] || false; + } + }else{ + if (valid_rows[0] !== p_top_row) { + new_row = this.get_consecutive_numbers_index( + valid_rows, size_y); + } + } + + return new_row; + }; + + + fn.get_consecutive_numbers_index = function(arr, size_y) { + var max = arr.length; + var result = []; + var first = true; + var prev = -1; // or null? + + for (var i=0; i < max; i++) { + if (first || arr[i] === prev + 1) { + result.push(i); + if (result.length === size_y) { + break; + } + first = false; + }else{ + result = []; + first = true; + } + + prev = arr[i]; + } + + return result.length >= size_y ? arr[result[0]] : false; + }; + + + /** + * Get widgets overlapping with the player. + * + * @method get_widgets_overlapped + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.get_widgets_overlapped = function() { + var $w; + var $widgets = $([]); + var used = []; + var rows_from_bottom = this.cells_occupied_by_player.rows.slice(0); + rows_from_bottom.reverse(); + + $.each(this.cells_occupied_by_player.cols, $.proxy(function(i, col) { + $.each(rows_from_bottom, $.proxy(function(i, row) { + // if there is a widget in the player position + if (!this.gridmap[col]) { return true; } //next iteration + var $w = this.gridmap[col][row]; + if (this.is_occupied(col, row) && !this.is_player($w) && + $.inArray($w, used) === -1 + ) { + $widgets = $widgets.add($w); + used.push($w); + } + + }, this)); + }, this)); + + return $widgets; + }; + + + /** + * This callback is executed when the player begins to collide with a column. + * + * @method on_start_overlapping_column + * @param {Number} col The collided column. + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.on_start_overlapping_column = function(col) { + this.set_player(col, false); + }; + + + /** + * A callback executed when the player begins to collide with a row. + * + * @method on_start_overlapping_row + * @param {Number} row The collided row. + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.on_start_overlapping_row = function(row) { + this.set_player(false, row); + }; + + + /** + * A callback executed when the the player ends to collide with a column. + * + * @method on_stop_overlapping_column + * @param {Number} col The collided row. + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.on_stop_overlapping_column = function(col) { + this.set_player(col, false); + + var self = this; + this.for_each_widget_below(col, this.cells_occupied_by_player.rows[0], + function(tcol, trow) { + self.move_widget_up(this, self.player_grid_data.size_y); + }); + }; + + + /** + * This callback is executed when the player ends to collide with a row. + * + * @method on_stop_overlapping_row + * @param {Number} row The collided row. + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.on_stop_overlapping_row = function(row) { + this.set_player(false, row); + + var self = this; + var cols = this.cells_occupied_by_player.cols; + for (var c = 0, cl = cols.length; c < cl; c++) { + this.for_each_widget_below(cols[c], row, function(tcol, trow) { + self.move_widget_up(this, self.player_grid_data.size_y); + }); + } + }; + + + /** + * Move a widget to a specific row. The cell or cells must be empty. + * If the widget has widgets below, all of these widgets will be moved also + * if they can. + * + * @method move_widget_to + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement of the + * widget is going to be moved. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.move_widget_to = function($widget, row) { + var self = this; + var widget_grid_data = $widget.coords().grid; + var diff = row - widget_grid_data.row; + var $next_widgets = this.widgets_below($widget); + + var can_move_to_new_cell = this.can_move_to( + widget_grid_data, widget_grid_data.col, row, $widget); + + if (can_move_to_new_cell === false) { + return false; + } + + this.remove_from_gridmap(widget_grid_data); + widget_grid_data.row = row; + this.add_to_gridmap(widget_grid_data); + $widget.attr('data-row', row); + this.$changed = this.$changed.add($widget); + + + $next_widgets.each(function(i, widget) { + var $w = $(widget); + var wgd = $w.coords().grid; + var can_go_up = self.can_go_widget_up(wgd); + if (can_go_up && can_go_up !== wgd.row) { + self.move_widget_to($w, can_go_up); + } + }); + + return this; + }; + + + /** + * Move up the specified widget and all below it. + * + * @method move_widget_up + * @param {HTMLElement} $widget The widget you want to move. + * @param {Number} [y_units] The number of cells that the widget has to move. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.move_widget_up = function($widget, y_units) { + var el_grid_data = $widget.coords().grid; + var actual_row = el_grid_data.row; + var moved = []; + var can_go_up = true; + y_units || (y_units = 1); + + if (!this.can_go_up($widget)) { return false; } //break; + + this.for_each_column_occupied(el_grid_data, function(col) { + // can_go_up + if ($.inArray($widget, moved) === -1) { + var widget_grid_data = $widget.coords().grid; + var next_row = actual_row - y_units; + next_row = this.can_go_up_to_row( + widget_grid_data, col, next_row); + + if (!next_row) { + return true; + } + + var $next_widgets = this.widgets_below($widget); + + this.remove_from_gridmap(widget_grid_data); + widget_grid_data.row = next_row; + this.add_to_gridmap(widget_grid_data); + $widget.attr('data-row', widget_grid_data.row); + this.$changed = this.$changed.add($widget); + + moved.push($widget); + + $next_widgets.each($.proxy(function(i, widget) { + this.move_widget_up($(widget), y_units); + }, this)); + } + }); + + }; + + + /** + * Move down the specified widget and all below it. + * + * @method move_widget_down + * @param {jQuery} $widget The jQuery object representing the widget + * you want to move. + * @param {Number} y_units The number of cells that the widget has to move. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.move_widget_down = function($widget, y_units) { + var el_grid_data = $widget.coords().grid; + var actual_row = el_grid_data.row; + var moved = []; + var y_diff = y_units; + + if (!$widget) { return false; } + + if ($.inArray($widget, moved) === -1) { + + var widget_grid_data = $widget.coords().grid; + var next_row = actual_row + y_units; + var $next_widgets = this.widgets_below($widget); + + this.remove_from_gridmap(widget_grid_data); + + $next_widgets.each($.proxy(function(i, widget) { + var $w = $(widget); + var wd = $w.coords().grid; + var tmp_y = this.displacement_diff( + wd, widget_grid_data, y_diff); + + if (tmp_y > 0) { + this.move_widget_down($w, tmp_y); + } + }, this)); + + widget_grid_data.row = next_row; + this.update_widget_position(widget_grid_data, $widget); + $widget.attr('data-row', widget_grid_data.row); + this.$changed = this.$changed.add($widget); + + moved.push($widget); + } + }; + + + /** + * Check if the widget can move to the specified row, else returns the + * upper row possible. + * + * @method can_go_up_to_row + * @param {Number} widget_grid_data The current grid coords object of the + * widget. + * @param {Number} col The target column. + * @param {Number} row The target row. + * @return {Boolean|Number} Returns the row number if the widget can move + * to the target position, else returns false. + */ + fn.can_go_up_to_row = function(widget_grid_data, col, row) { + var ga = this.gridmap; + var result = true; + var urc = []; // upper_rows_in_columns + var actual_row = widget_grid_data.row; + var r; + + /* generate an array with columns as index and array with + * upper rows empty in the column */ + this.for_each_column_occupied(widget_grid_data, function(tcol) { + var grid_col = ga[tcol]; + urc[tcol] = []; + + r = actual_row; + while (r--) { + if (this.is_empty(tcol, r) && + !this.is_placeholder_in(tcol, r) + ) { + urc[tcol].push(r); + }else{ + break; + } + } + + if (!urc[tcol].length) { + result = false; + return true; + } + + }); + + if (!result) { return false; } + + /* get common rows starting from upper position in all the columns + * that widget occupies */ + r = row; + for (r = 1; r < actual_row; r++) { + var common = true; + + for (var uc = 0, ucl = urc.length; uc < ucl; uc++) { + if (urc[uc] && $.inArray(r, urc[uc]) === -1) { + common = false; + } + } + + if (common === true) { + result = r; + break; + } + } + + return result; + }; + + + fn.displacement_diff = function(widget_grid_data, parent_bgd, y_units) { + var actual_row = widget_grid_data.row; + var diffs = []; + var parent_max_y = parent_bgd.row + parent_bgd.size_y; + + this.for_each_column_occupied(widget_grid_data, function(col) { + var temp_y_units = 0; + + for (var r = parent_max_y; r < actual_row; r++) { + if (this.is_empty(col, r)) { + temp_y_units = temp_y_units + 1; + } + } + + diffs.push(temp_y_units); + }); + + var max_diff = Math.max.apply(Math, diffs); + y_units = (y_units - max_diff); + + return y_units > 0 ? y_units : 0; + }; + + + /** + * Get widgets below a widget. + * + * @method widgets_below + * @param {HTMLElement} $el The jQuery wrapped HTMLElement. + * @return {jQuery} A jQuery collection of HTMLElements. + */ + fn.widgets_below = function($el) { + var el_grid_data = $.isPlainObject($el) ? $el : $el.coords().grid; + var self = this; + var ga = this.gridmap; + var next_row = el_grid_data.row + el_grid_data.size_y - 1; + var $nexts = $([]); + + this.for_each_column_occupied(el_grid_data, function(col) { + self.for_each_widget_below(col, next_row, function(tcol, trow) { + if (!self.is_player(this) && $.inArray(this, $nexts) === -1) { + $nexts = $nexts.add(this); + return true; // break + } + }); + }); + + return this.sort_by_row_asc($nexts); + }; + + + /** + * Update the array of mapped positions with the new player position. + * + * @method set_cells_player_occupies + * @param {Number} col The new player col. + * @param {Number} col The new player row. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.set_cells_player_occupies = function(col, row) { + this.remove_from_gridmap(this.placeholder_grid_data); + this.placeholder_grid_data.col = col; + this.placeholder_grid_data.row = row; + this.add_to_gridmap(this.placeholder_grid_data, this.$player); + return this; + }; + + + /** + * Remove from the array of mapped positions the reference to the player. + * + * @method empty_cells_player_occupies + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.empty_cells_player_occupies = function() { + this.remove_from_gridmap(this.placeholder_grid_data); + return this; + }; + + + fn.can_go_up = function($el) { + var el_grid_data = $el.coords().grid; + var initial_row = el_grid_data.row; + var prev_row = initial_row - 1; + var ga = this.gridmap; + var upper_rows_by_column = []; + + var result = true; + if (initial_row === 1) { return false; } + + this.for_each_column_occupied(el_grid_data, function(col) { + var $w = this.is_widget(col, prev_row); + + if (this.is_occupied(col, prev_row) || + this.is_player(col, prev_row) || + this.is_placeholder_in(col, prev_row) || + this.is_player_in(col, prev_row) + ) { + result = false; + return true; //break + } + }); + + return result; + }; + + + + /** + * Check if it's possible to move a widget to a specific col/row. It takes + * into account the dimensions (`size_y` and `size_x` attrs. of the grid + * coords object) the widget occupies. + * + * @method can_move_to + * @param {Object} widget_grid_data The grid coords object that represents + * the widget. + * @param {Object} col The col to check. + * @param {Object} row The row to check. + * @param {Number} [max_row] The max row allowed. + * @return {Boolean} Returns true if all cells are empty, else return false. + */ + fn.can_move_to = function(widget_grid_data, col, row, max_row) { + var ga = this.gridmap; + var $w = widget_grid_data.el; + var future_wd = { + size_y: widget_grid_data.size_y, + size_x: widget_grid_data.size_x, + col: col, + row: row + }; + var result = true; + + //Prevents widgets go out of the grid + var right_col = col + widget_grid_data.size_x - 1; + if (right_col > this.cols) { + return false; + } + + if (max_row && max_row < row + widget_grid_data.size_y - 1) { + return false; + } + + this.for_each_cell_occupied(future_wd, function(tcol, trow) { + var $tw = this.is_widget(tcol, trow); + if ($tw && (!widget_grid_data.el || $tw.is($w))) { + result = false; + } + }); + + return result; + }; + + + /** + * Given the leftmost column returns all columns that are overlapping + * with the player. + * + * @method get_targeted_columns + * @param {Number} [from_col] The leftmost column. + * @return {Array} Returns an array with column numbers. + */ + fn.get_targeted_columns = function(from_col) { + var max = (from_col || this.player_grid_data.col) + + (this.player_grid_data.size_x - 1); + var cols = []; + for (var col = from_col; col <= max; col++) { + cols.push(col); + } + return cols; + }; + + + /** + * Given the upper row returns all rows that are overlapping with the player. + * + * @method get_targeted_rows + * @param {Number} [from_row] The upper row. + * @return {Array} Returns an array with row numbers. + */ + fn.get_targeted_rows = function(from_row) { + var max = (from_row || this.player_grid_data.row) + + (this.player_grid_data.size_y - 1); + var rows = []; + for (var row = from_row; row <= max; row++) { + rows.push(row); + } + return rows; + }; + + /** + * Get all columns and rows that a widget occupies. + * + * @method get_cells_occupied + * @param {Object} el_grid_data The grid coords object of the widget. + * @return {Object} Returns an object like `{ cols: [], rows: []}`. + */ + fn.get_cells_occupied = function(el_grid_data) { + var cells = { cols: [], rows: []}; + var i; + if (arguments[1] instanceof jQuery) { + el_grid_data = arguments[1].coords().grid; + } + + for (i = 0; i < el_grid_data.size_x; i++) { + var col = el_grid_data.col + i; + cells.cols.push(col); + } + + for (i = 0; i < el_grid_data.size_y; i++) { + var row = el_grid_data.row + i; + cells.rows.push(row); + } + + return cells; + }; + + + /** + * Iterate over the cells occupied by a widget executing a function for + * each one. + * + * @method for_each_cell_occupied + * @param {Object} el_grid_data The grid coords object that represents the + * widget. + * @param {Function} callback The function to execute on each column + * iteration. Column and row are passed as arguments. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.for_each_cell_occupied = function(grid_data, callback) { + this.for_each_column_occupied(grid_data, function(col) { + this.for_each_row_occupied(grid_data, function(row) { + callback.call(this, col, row); + }); + }); + return this; + }; + + + /** + * Iterate over the columns occupied by a widget executing a function for + * each one. + * + * @method for_each_column_occupied + * @param {Object} el_grid_data The grid coords object that represents + * the widget. + * @param {Function} callback The function to execute on each column + * iteration. The column number is passed as first argument. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.for_each_column_occupied = function(el_grid_data, callback) { + for (var i = 0; i < el_grid_data.size_x; i++) { + var col = el_grid_data.col + i; + callback.call(this, col, el_grid_data); + } + }; + + + /** + * Iterate over the rows occupied by a widget executing a function for + * each one. + * + * @method for_each_row_occupied + * @param {Object} el_grid_data The grid coords object that represents + * the widget. + * @param {Function} callback The function to execute on each column + * iteration. The row number is passed as first argument. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.for_each_row_occupied = function(el_grid_data, callback) { + for (var i = 0; i < el_grid_data.size_y; i++) { + var row = el_grid_data.row + i; + callback.call(this, row, el_grid_data); + } + }; + + + + fn._traversing_widgets = function(type, direction, col, row, callback) { + var ga = this.gridmap; + if (!ga[col]) { return; } + + var cr, max; + var action = type + '/' + direction; + if (arguments[2] instanceof jQuery) { + var el_grid_data = arguments[2].coords().grid; + col = el_grid_data.col; + row = el_grid_data.row; + callback = arguments[3]; + } + var matched = []; + var trow = row; + + + var methods = { + 'for_each/above': function() { + while (trow--) { + if (trow > 0 && this.is_widget(col, trow) && + $.inArray(ga[col][trow], matched) === -1 + ) { + cr = callback.call(ga[col][trow], col, trow); + matched.push(ga[col][trow]); + if (cr) { break; } + } + } + }, + 'for_each/below': function() { + for (trow = row + 1, max = ga[col].length; trow < max; trow++) { + if (this.is_widget(col, trow) && + $.inArray(ga[col][trow], matched) === -1 + ) { + cr = callback.call(ga[col][trow], col, trow); + matched.push(ga[col][trow]); + if (cr) { break; } + } + } + } + }; + + if (methods[action]) { + methods[action].call(this); + } + }; + + + /** + * Iterate over each widget above the column and row specified. + * + * @method for_each_widget_above + * @param {Number} col The column to start iterating. + * @param {Number} row The row to start iterating. + * @param {Function} callback The function to execute on each widget + * iteration. The value of `this` inside the function is the jQuery + * wrapped HTMLElement. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.for_each_widget_above = function(col, row, callback) { + this._traversing_widgets('for_each', 'above', col, row, callback); + return this; + }; + + + /** + * Iterate over each widget below the column and row specified. + * + * @method for_each_widget_below + * @param {Number} col The column to start iterating. + * @param {Number} row The row to start iterating. + * @param {Function} callback The function to execute on each widget + * iteration. The value of `this` inside the function is the jQuery wrapped + * HTMLElement. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.for_each_widget_below = function(col, row, callback) { + this._traversing_widgets('for_each', 'below', col, row, callback); + return this; + }; + + + /** + * Returns the highest occupied cell in the grid. + * + * @method get_highest_occupied_cell + * @return {Object} Returns an object with `col` and `row` numbers. + */ + fn.get_highest_occupied_cell = function() { + var r; + var gm = this.gridmap; + var rows = []; + var row_in_col = []; + for (var c = gm.length - 1; c >= 1; c--) { + for (r = gm[c].length - 1; r >= 1; r--) { + if (this.is_widget(c, r)) { + rows.push(r); + row_in_col[r] = c; + break; + } + } + } + + var highest_row = Math.max.apply(Math, rows); + + this.highest_occupied_cell = { + col: row_in_col[highest_row], + row: highest_row + }; + + return this.highest_occupied_cell; + }; + + + fn.get_widgets_from = function(col, row) { + var ga = this.gridmap; + var $widgets = $(); + + if (col) { + $widgets = $widgets.add( + this.$widgets.filter(function() { + var tcol = $(this).attr('data-col'); + return (tcol === col || tcol > col); + }) + ); + } + + if (row) { + $widgets = $widgets.add( + this.$widgets.filter(function() { + var trow = $(this).attr('data-row'); + return (trow === row || trow > row); + }) + ); + } + + return $widgets; + }; + + + /** + * Set the current height of the parent grid. + * + * @method set_dom_grid_height + * @return {Object} Returns the instance of the Gridster class. + */ + fn.set_dom_grid_height = function() { + var r = this.get_highest_occupied_cell().row; + this.$el.css('height', r * this.min_widget_height); + return this; + }; + + + /** + * It generates the neccessary styles to position the widgets. + * + * @method generate_stylesheet + * @param {Number} rows Number of columns. + * @param {Number} cols Number of rows. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.generate_stylesheet = function(opts) { + var styles = ''; + var max_size_x = this.options.max_size_x; + var max_rows = 0; + var max_cols = 0; + var i; + var rules; + + opts || (opts = {}); + opts.cols || (opts.cols = this.cols); + opts.rows || (opts.rows = this.rows); + opts.namespace || (opts.namespace = this.options.namespace); + opts.widget_base_dimensions || + (opts.widget_base_dimensions = this.options.widget_base_dimensions); + opts.widget_margins || + (opts.widget_margins = this.options.widget_margins); + opts.min_widget_width = (opts.widget_margins[0] * 2) + + opts.widget_base_dimensions[0]; + opts.min_widget_height = (opts.widget_margins[1] * 2) + + opts.widget_base_dimensions[1]; + + // don't duplicate stylesheets for the same configuration + var serialized_opts = $.param(opts); + if ($.inArray(serialized_opts, Gridster.generated_stylesheets) >= 0) { + return false; + } + + Gridster.generated_stylesheets.push(serialized_opts); + + /* generate CSS styles for cols */ + for (i = opts.cols; i >= 0; i--) { + styles += (opts.namespace + ' [data-col="'+ (i + 1) + '"] { left:' + + ((i * opts.widget_base_dimensions[0]) + + (i * opts.widget_margins[0]) + + ((i + 1) * opts.widget_margins[0])) + 'px;} '); + } + + /* generate CSS styles for rows */ + for (i = opts.rows; i >= 0; i--) { + styles += (opts.namespace + ' [data-row="' + (i + 1) + '"] { top:' + + ((i * opts.widget_base_dimensions[1]) + + (i * opts.widget_margins[1]) + + ((i + 1) * opts.widget_margins[1]) ) + 'px;} '); + } + + for (var y = 1; y <= opts.rows; y++) { + styles += (opts.namespace + ' [data-sizey="' + y + '"] { height:' + + (y * opts.widget_base_dimensions[1] + + (y - 1) * (opts.widget_margins[1] * 2)) + 'px;}'); + } + + for (var x = 1; x <= max_size_x; x++) { + styles += (opts.namespace + ' [data-sizex="' + x + '"] { width:' + + (x * opts.widget_base_dimensions[0] + + (x - 1) * (opts.widget_margins[0] * 2)) + 'px;}'); + } + + return this.add_style_tag(styles); + }; + + + /** + * Injects the given CSS as string to the head of the document. + * + * @method add_style_tag + * @param {String} css The styles to apply. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_style_tag = function(css) { + var d = document; + var tag = d.createElement('style'); + + d.getElementsByTagName('head')[0].appendChild(tag); + tag.setAttribute('type', 'text/css'); + + if (tag.styleSheet) { + tag.styleSheet.cssText = css; + }else{ + tag.appendChild(document.createTextNode(css)); + } + return this; + }; + + + /** + * Generates a faux grid to collide with it when a widget is dragged and + * detect row or column that we want to go. + * + * @method generate_faux_grid + * @param {Number} rows Number of columns. + * @param {Number} cols Number of rows. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.generate_faux_grid = function(rows, cols) { + this.faux_grid = []; + this.gridmap = []; + var col; + var row; + for (col = cols; col > 0; col--) { + this.gridmap[col] = []; + for (row = rows; row > 0; row--) { + this.add_faux_cell(row, col); + } + } + return this; + }; + + + /** + * Add cell to the faux grid. + * + * @method add_faux_cell + * @param {Number} row The row for the new faux cell. + * @param {Number} col The col for the new faux cell. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_faux_cell = function(row, col) { + var coords = $({ + left: this.baseX + ((col - 1) * this.min_widget_width), + top: this.baseY + (row -1) * this.min_widget_height, + width: this.min_widget_width, + height: this.min_widget_height, + col: col, + row: row, + original_col: col, + original_row: row + }).coords(); + + if (!$.isArray(this.gridmap[col])) { + this.gridmap[col] = []; + } + + this.gridmap[col][row] = false; + this.faux_grid.push(coords); + + return this; + }; + + + /** + * Add rows to the faux grid. + * + * @method add_faux_rows + * @param {Number} rows The number of rows you want to add to the faux grid. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_faux_rows = function(rows) { + var actual_rows = this.rows; + var max_rows = actual_rows + (rows || 1); + + for (var r = max_rows; r > actual_rows; r--) { + for (var c = this.cols; c >= 1; c--) { + this.add_faux_cell(r, c); + } + } + + this.rows = max_rows; + + if (this.options.autogenerate_stylesheet) { + this.generate_stylesheet(); + } + + return this; + }; + + /** + * Add cols to the faux grid. + * + * @method add_faux_cols + * @param {Number} cols The number of cols you want to add to the faux grid. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_faux_cols = function(cols) { + var actual_cols = this.cols; + var max_cols = actual_cols + (cols || 1); + + for (var c = actual_cols; c < max_cols; c++) { + for (var r = this.rows; r >= 1; r--) { + this.add_faux_cell(r, c); + } + } + + this.cols = max_cols; + + if (this.options.autogenerate_stylesheet) { + this.generate_stylesheet(); + } + + return this; + }; + + + /** + * Recalculates the offsets for the faux grid. You need to use it when + * the browser is resized. + * + * @method recalculate_faux_grid + * @return {Object} Returns the instance of the Gridster class. + */ + fn.recalculate_faux_grid = function() { + var aw = this.$wrapper.width(); + this.baseX = ($(window).width() - aw) / 2; + this.baseY = this.$wrapper.offset().top; + + $.each(this.faux_grid, $.proxy(function(i, coords) { + this.faux_grid[i] = coords.update({ + left: this.baseX + (coords.data.col -1) * this.min_widget_width, + top: this.baseY + (coords.data.row -1) * this.min_widget_height + }); + + }, this)); + + return this; + }; + + + /** + * Get all widgets in the DOM and register them. + * + * @method get_widgets_from_DOM + * @return {Object} Returns the instance of the Gridster class. + */ + fn.get_widgets_from_DOM = function() { + this.$widgets.each($.proxy(function(i, widget) { + this.register_widget($(widget)); + }, this)); + return this; + }; + + + /** + * Calculate columns and rows to be set based on the configuration + * parameters, grid dimensions, etc ... + * + * @method generate_grid_and_stylesheet + * @return {Object} Returns the instance of the Gridster class. + */ + fn.generate_grid_and_stylesheet = function() { + var aw = this.$wrapper.width(); + var ah = this.$wrapper.height(); + + var cols = Math.floor(aw / this.min_widget_width) + + this.options.extra_cols; + + var actual_cols = this.$widgets.map(function() { + return $(this).attr('data-col'); + }); + actual_cols = Array.prototype.slice.call(actual_cols, 0); + //needed to pass tests with phantomjs + actual_cols.length || (actual_cols = [0]); + + var min_cols = Math.max.apply(Math, actual_cols); + + // get all rows that could be occupied by the current widgets + var max_rows = this.options.extra_rows; + this.$widgets.each(function(i, w) { + max_rows += (+$(w).attr('data-sizey')); + }); + + this.cols = Math.max(min_cols, cols, this.options.min_cols); + this.rows = Math.max(max_rows, this.options.min_rows); + + this.baseX = ($(window).width() - aw) / 2; + this.baseY = this.$wrapper.offset().top; + + if (this.options.autogenerate_stylesheet) { + this.generate_stylesheet(); + } + + return this.generate_faux_grid(this.rows, this.cols); + }; + + /** + * Destroy this gridster by removing any sign of its presence, making it easy to avoid memory leaks + * + * @method destroy + * @return {undefined} + */ + fn.destroy = function(){ + // remove bound callback on window resize + $(window).unbind('resize', this.on_window_resize); + + if(this.drag_api){ + this.drag_api.destroy(); + } + + // lastly, remove gridster element + // this will additionally cause any data associated to this element to be removed, including this + // very gridster instance + this.$el.remove(); + }; + + + //jQuery adapter + $.fn.gridster = function(options) { + return this.each(function() { + if (!$(this).data('gridster')) { + $(this).data('gridster', new Gridster( this, options )); + } + }); + }; + + $.Gridster = fn; + +}(jQuery, window, document)); \ No newline at end of file diff --git a/assets/javascripts/gridster/jquery.leanModal.min.js b/assets/javascripts/gridster/jquery.leanModal.min.js new file mode 100644 index 0000000..a5772dd --- /dev/null +++ b/assets/javascripts/gridster/jquery.leanModal.min.js @@ -0,0 +1,5 @@ +// leanModal v1.1 by Ray Stone - http://finelysliced.com.au +// Dual licensed under the MIT and GPL + +(function($){$.fn.extend({leanModal:function(options){var defaults={top:100,overlay:0.5,closeButton:null};var overlay=$("
    ");$("body").append(overlay);options=$.extend(defaults,options);return this.each(function(){var o=options;$(this).click(function(e){var modal_id=$(this).attr("href");$("#lean_overlay").click(function(){close_modal(modal_id)});$(o.closeButton).click(function(){close_modal(modal_id)});var modal_height=$(modal_id).outerHeight();var modal_width=$(modal_id).outerWidth(); +$("#lean_overlay").css({"display":"block",opacity:0});$("#lean_overlay").fadeTo(200,o.overlay);$(modal_id).css({"display":"block","position":"fixed","opacity":0,"z-index":11000,"left":50+"%","margin-left":-(modal_width/2)+"px","top":o.top+"px"});$(modal_id).fadeTo(200,1);e.preventDefault()})});function close_modal(modal_id){$("#lean_overlay").fadeOut(200);$(modal_id).css({"display":"none"})}}})})(jQuery); diff --git a/assets/javascripts/jquery.knob.js b/assets/javascripts/jquery.knob.js new file mode 100644 index 0000000..3224638 --- /dev/null +++ b/assets/javascripts/jquery.knob.js @@ -0,0 +1,646 @@ +/*!jQuery Knob*/ +/** + * Downward compatible, touchable dial + * + * Version: 1.2.0 (15/07/2012) + * Requires: jQuery v1.7+ + * + * Copyright (c) 2012 Anthony Terrien + * Under MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * Thanks to vor, eskimoblood, spiffistan, FabrizioC + */ +$(function () { + + /** + * Kontrol library + */ + "use strict"; + + /** + * Definition of globals and core + */ + var k = {}, // kontrol + max = Math.max, + min = Math.min; + + k.c = {}; + k.c.d = $(document); + k.c.t = function (e) { + return e.originalEvent.touches.length - 1; + }; + + /** + * Kontrol Object + * + * Definition of an abstract UI control + * + * Each concrete component must call this one. + * + * k.o.call(this); + * + */ + k.o = function () { + var s = this; + + this.o = null; // array of options + this.$ = null; // jQuery wrapped element + this.i = null; // mixed HTMLInputElement or array of HTMLInputElement + this.g = null; // 2D graphics context for 'pre-rendering' + this.v = null; // value ; mixed array or integer + this.cv = null; // change value ; not commited value + this.x = 0; // canvas x position + this.y = 0; // canvas y position + this.$c = null; // jQuery canvas element + this.c = null; // rendered canvas context + this.t = 0; // touches index + this.isInit = false; + this.fgColor = null; // main color + this.pColor = null; // previous color + this.dH = null; // draw hook + this.cH = null; // change hook + this.eH = null; // cancel hook + this.rH = null; // release hook + + this.run = function () { + var cf = function (e, conf) { + var k; + for (k in conf) { + s.o[k] = conf[k]; + } + s.init(); + s._configure() + ._draw(); + }; + + if(this.$.data('kontroled')) return; + this.$.data('kontroled', true); + + this.extend(); + this.o = $.extend( + { + // Config + min : this.$.data('min') || 0, + max : this.$.data('max') || 100, + stopper : true, + readOnly : this.$.data('readonly'), + + // UI + cursor : (this.$.data('cursor') === true && 30) + || this.$.data('cursor') + || 0, + thickness : this.$.data('thickness') || 0.35, + width : this.$.data('width') || 200, + height : this.$.data('height') || 200, + displayInput : this.$.data('displayinput') == null || this.$.data('displayinput'), + displayPrevious : this.$.data('displayprevious'), + fgColor : this.$.data('fgcolor') || '#87CEEB', + inline : false, + + // Hooks + draw : null, // function () {} + change : null, // function (value) {} + cancel : null, // function () {} + release : null // function (value) {} + }, this.o + ); + + // routing value + if(this.$.is('fieldset')) { + + // fieldset = array of integer + this.v = {}; + this.i = this.$.find('input') + this.i.each(function(k) { + var $this = $(this); + s.i[k] = $this; + s.v[k] = $this.val(); + + $this.bind( + 'change' + , function () { + var val = {}; + val[k] = $this.val(); + s.val(val); + } + ); + }); + this.$.find('legend').remove(); + + } else { + // input = integer + this.i = this.$; + this.v = this.$.val(); + (this.v == '') && (this.v = this.o.min); + + this.$.bind( + 'change' + , function () { + s.val(s.$.val()); + } + ); + } + + (!this.o.displayInput) && this.$.hide(); + + this.$c = $(''); + this.c = this.$c[0].getContext("2d"); + + this.$ + .wrap($('
    ')) + .before(this.$c); + + if (this.v instanceof Object) { + this.cv = {}; + this.copy(this.v, this.cv); + } else { + this.cv = this.v; + } + + this.$ + .bind("configure", cf) + .parent() + .bind("configure", cf); + + this._listen() + ._configure() + ._xy() + .init(); + + this.isInit = true; + + this._draw(); + + return this; + }; + + this._draw = function () { + + // canvas pre-rendering + var d = true, + c = document.createElement('canvas'); + + c.width = s.o.width; + c.height = s.o.height; + s.g = c.getContext('2d'); + + s.clear(); + + s.dH + && (d = s.dH()); + + (d !== false) && s.draw(); + + s.c.drawImage(c, 0, 0); + c = null; + }; + + this._touch = function (e) { + + var touchMove = function (e) { + + var v = s.xy2val( + e.originalEvent.touches[s.t].pageX, + e.originalEvent.touches[s.t].pageY + ); + + if (v == s.cv) return; + + if ( + s.cH + && (s.cH(v) === false) + ) return; + + + s.change(v); + s._draw(); + }; + + // get touches index + this.t = k.c.t(e); + + // First touch + touchMove(e); + + // Touch events listeners + k.c.d + .bind("touchmove.k", touchMove) + .bind( + "touchend.k" + , function () { + k.c.d.unbind('touchmove.k touchend.k'); + + if ( + s.rH + && (s.rH(s.cv) === false) + ) return; + + s.val(s.cv); + } + ); + + return this; + }; + + this._mouse = function (e) { + + var mouseMove = function (e) { + var v = s.xy2val(e.pageX, e.pageY); + if (v == s.cv) return; + + if ( + s.cH + && (s.cH(v) === false) + ) return; + + s.change(v); + s._draw(); + }; + + // First click + mouseMove(e); + + // Mouse events listeners + k.c.d + .bind("mousemove.k", mouseMove) + .bind( + // Escape key cancel current change + "keyup.k" + , function (e) { + if (e.keyCode === 27) { + k.c.d.unbind("mouseup.k mousemove.k keyup.k"); + + if ( + s.eH + && (s.eH() === false) + ) return; + + s.cancel(); + } + } + ) + .bind( + "mouseup.k" + , function (e) { + k.c.d.unbind('mousemove.k mouseup.k keyup.k'); + + if ( + s.rH + && (s.rH(s.cv) === false) + ) return; + + s.val(s.cv); + } + ); + + return this; + }; + + this._xy = function () { + var o = this.$c.offset(); + this.x = o.left; + this.y = o.top; + return this; + }; + + this._listen = function () { + + if (!this.o.readOnly) { + this.$c + .bind( + "mousedown" + , function (e) { + e.preventDefault(); + s._xy()._mouse(e); + } + ) + .bind( + "touchstart" + , function (e) { + e.preventDefault(); + s._xy()._touch(e); + } + ); + this.listen(); + } else { + this.$.attr('readonly', 'readonly'); + } + + return this; + }; + + this._configure = function () { + + // Hooks + if (this.o.draw) this.dH = this.o.draw; + if (this.o.change) this.cH = this.o.change; + if (this.o.cancel) this.eH = this.o.cancel; + if (this.o.release) this.rH = this.o.release; + + if (this.o.displayPrevious) { + this.pColor = this.h2rgba(this.o.fgColor, "0.4"); + this.fgColor = this.h2rgba(this.o.fgColor, "0.6"); + } else { + this.fgColor = this.o.fgColor; + } + + return this; + }; + + this._clear = function () { + this.$c[0].width = this.$c[0].width; + }; + + // Abstract methods + this.listen = function () {}; // on start, one time + this.extend = function () {}; // each time configure triggered + this.init = function () {}; // each time configure triggered + this.change = function (v) {}; // on change + this.val = function (v) {}; // on release + this.xy2val = function (x, y) {}; // + this.draw = function () {}; // on change / on release + this.clear = function () { this._clear(); }; + + // Utils + this.h2rgba = function (h, a) { + var rgb; + h = h.substring(1,7) + rgb = [parseInt(h.substring(0,2),16) + ,parseInt(h.substring(2,4),16) + ,parseInt(h.substring(4,6),16)]; + return "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "," + a + ")"; + }; + + this.copy = function (f, t) { + for (var i in f) { t[i] = f[i]; } + }; + }; + + + /** + * k.Dial + */ + k.Dial = function () { + k.o.call(this); + + this.startAngle = null; + this.xy = null; + this.radius = null; + this.lineWidth = null; + this.cursorExt = null; + this.w2 = null; + this.PI2 = 2*Math.PI; + + this.extend = function () { + this.o = $.extend( + { + bgColor : this.$.data('bgcolor') || '#EEEEEE', + angleOffset : this.$.data('angleoffset') || 0, + angleArc : this.$.data('anglearc') || 360, + inline : true + }, this.o + ); + }; + + this.val = function (v) { + if (null != v) { + this.cv = this.o.stopper ? max(min(v, this.o.max), this.o.min) : v; + this.v = this.cv; + this.$.val(this.v); + this._draw(); + } else { + return this.v; + } + }; + + this.xy2val = function (x, y) { + var a, ret; + + a = Math.atan2( + x - (this.x + this.w2) + , - (y - this.y - this.w2) + ) - this.angleOffset; + + if(this.angleArc != this.PI2 && (a < 0) && (a > -0.5)) { + // if isset angleArc option, set to min if .5 under min + a = 0; + } else if (a < 0) { + a += this.PI2; + } + + ret = ~~ (0.5 + (a * (this.o.max - this.o.min) / this.angleArc)) + + this.o.min; + + this.o.stopper + && (ret = max(min(ret, this.o.max), this.o.min)); + + return ret; + }; + + this.listen = function () { + // bind MouseWheel + var s = this, + mw = function (e) { + e.preventDefault(); + + var ori = e.originalEvent + ,deltaX = ori.detail || ori.wheelDeltaX + ,deltaY = ori.detail || ori.wheelDeltaY + ,v = parseInt(s.$.val()) + (deltaX>0 || deltaY>0 ? 1 : deltaX<0 || deltaY<0 ? -1 : 0); + + if ( + s.cH + && (s.cH(v) === false) + ) return; + + s.val(v); + } + , kval, to, m = 1, kv = {37:-1, 38:1, 39:1, 40:-1}; + + this.$ + .bind( + "keydown" + ,function (e) { + var kc = e.keyCode; + kval = parseInt(String.fromCharCode(kc)); + + if (isNaN(kval)) { + + (kc !== 13) // enter + && (kc !== 8) // bs + && (kc !== 9) // tab + && (kc !== 189) // - + && e.preventDefault(); + + // arrows + if ($.inArray(kc,[37,38,39,40]) > -1) { + e.preventDefault(); + + var v = parseInt(s.$.val()) + kv[kc] * m; + + s.o.stopper + && (v = max(min(v, s.o.max), s.o.min)); + + s.change(v); + s._draw(); + + // long time keydown speed-up + to = window.setTimeout( + function () { m*=2; } + ,30 + ); + } + } + } + ) + .bind( + "keyup" + ,function (e) { + if (isNaN(kval)) { + if (to) { + window.clearTimeout(to); + to = null; + m = 1; + s.val(s.$.val()); + } + } else { + // kval postcond + (s.$.val() > s.o.max && s.$.val(s.o.max)) + || (s.$.val() < s.o.min && s.$.val(s.o.min)); + } + + } + ); + + this.$c.bind("mousewheel DOMMouseScroll", mw); + this.$.bind("mousewheel DOMMouseScroll", mw) + }; + + this.init = function () { + + if ( + this.v < this.o.min + || this.v > this.o.max + ) this.v = this.o.min; + + this.$.val(this.v); + this.w2 = this.o.width / 2; + this.cursorExt = this.o.cursor / 100; + this.xy = this.w2; + this.lineWidth = this.xy * this.o.thickness; + this.radius = this.xy - this.lineWidth / 2; + + this.o.angleOffset + && (this.o.angleOffset = isNaN(this.o.angleOffset) ? 0 : this.o.angleOffset); + + this.o.angleArc + && (this.o.angleArc = isNaN(this.o.angleArc) ? this.PI2 : this.o.angleArc); + + // deg to rad + this.angleOffset = this.o.angleOffset * Math.PI / 180; + this.angleArc = this.o.angleArc * Math.PI / 180; + + // compute start and end angles + this.startAngle = 1.5 * Math.PI + this.angleOffset; + this.endAngle = 1.5 * Math.PI + this.angleOffset + this.angleArc; + + var s = max( + String(Math.abs(this.o.max)).length + , String(Math.abs(this.o.min)).length + , 2 + ) + 2; + + this.o.displayInput + && this.i.css({ + 'width' : ((this.o.width / 2 + 4) >> 0) + 'px' + ,'height' : ((this.o.width / 3) >> 0) + 'px' + ,'position' : 'absolute' + ,'vertical-align' : 'middle' + ,'margin-top' : ((this.o.width / 3) >> 0) + 'px' + ,'margin-left' : '-' + ((this.o.width * 3 / 4 + 2) >> 0) + 'px' + ,'border' : 0 + ,'background' : 'none' + ,'font' : 'bold ' + ((this.o.width / s) >> 0) + 'px Arial' + ,'text-align' : 'center' + ,'color' : this.o.fgColor + ,'padding' : '0px' + ,'-webkit-appearance': 'none' + }) + || this.i.css({ + 'width' : '0px' + ,'visibility' : 'hidden' + }); + }; + + this.change = function (v) { + this.cv = v; + this.$.val(v); + }; + + this.angle = function (v) { + return (v - this.o.min) * this.angleArc / (this.o.max - this.o.min); + }; + + this.draw = function () { + + var c = this.g, // context + a = this.angle(this.cv) // Angle + , sat = this.startAngle // Start angle + , eat = sat + a // End angle + , sa, ea // Previous angles + , r = 1; + + c.lineWidth = this.lineWidth; + + this.o.cursor + && (sat = eat - this.cursorExt) + && (eat = eat + this.cursorExt); + + c.beginPath(); + c.strokeStyle = this.o.bgColor; + c.arc(this.xy, this.xy, this.radius, this.endAngle, this.startAngle, true); + c.stroke(); + + if (this.o.displayPrevious) { + ea = this.startAngle + this.angle(this.v); + sa = this.startAngle; + this.o.cursor + && (sa = ea - this.cursorExt) + && (ea = ea + this.cursorExt); + + c.beginPath(); + c.strokeStyle = this.pColor; + c.arc(this.xy, this.xy, this.radius, sa, ea, false); + c.stroke(); + r = (this.cv == this.v); + } + + c.beginPath(); + c.strokeStyle = r ? this.o.fgColor : this.fgColor ; + c.arc(this.xy, this.xy, this.radius, sat, eat, false); + c.stroke(); + }; + + this.cancel = function () { + this.val(this.v); + }; + }; + + $.fn.dial = $.fn.knob = function (o) { + return this.each( + function () { + var d = new k.Dial(); + d.o = o; + d.$ = $(this); + d.run(); + } + ).parent(); + }; + +}); \ No newline at end of file diff --git a/assets/javascripts/rickshaw-1.4.3.min.js b/assets/javascripts/rickshaw-1.4.3.min.js new file mode 100644 index 0000000..fac600f --- /dev/null +++ b/assets/javascripts/rickshaw-1.4.3.min.js @@ -0,0 +1,2 @@ +var Rickshaw={namespace:function(namespace,obj){var parts=namespace.split(".");var parent=Rickshaw;for(var i=1,length=parts.length;i=3){if(s.data[2].xthis.window.xMax)isInRange=false;return isInRange}return true};this.onUpdate=function(callback){this.updateCallbacks.push(callback)};this.registerRenderer=function(renderer){this._renderers=this._renderers||{};this._renderers[renderer.name]=renderer};this.configure=function(args){if(args.width||args.height){this.setSize(args)}Rickshaw.keys(this.defaults).forEach(function(k){this[k]=k in args?args[k]:k in this?this[k]:this.defaults[k]},this);this.setRenderer(args.renderer||this.renderer.name,args)};this.setRenderer=function(r,args){if(typeof r=="function"){this.renderer=new r({graph:self});this.registerRenderer(this.renderer)}else{if(!this._renderers[r]){throw"couldn't find renderer "+r}this.renderer=this._renderers[r]}if(typeof args=="object"){this.renderer.configure(args)}};this.setSize=function(args){args=args||{};if(typeof window!==undefined){var style=window.getComputedStyle(this.element,null);var elementWidth=parseInt(style.getPropertyValue("width"),10);var elementHeight=parseInt(style.getPropertyValue("height"),10)}this.width=args.width||elementWidth||400;this.height=args.height||elementHeight||250;this.vis&&this.vis.attr("width",this.width).attr("height",this.height)};this.initialize(args)};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(timeInterval){var addData;timeInterval=timeInterval||1;var lastRandomValue=200;var timeBase=Math.floor((new Date).getTime()/1e3);this.addData=function(data){var randomValue=Math.random()*100+15+lastRandomValue;var index=data[0].length;var counter=1;data.forEach(function(series){var randomVariance=Math.random()*20;var v=randomValue/25+counter++ +(Math.cos(index*counter*11/960)+2)*15+(Math.cos(index/7)+2)*7+(Math.cos(index/17)+2)*1;series.push({x:index*timeInterval+timeBase,y:v+randomVariance})});lastRandomValue=randomValue*.85};this.removeData=function(data){data.forEach(function(series){series.shift()});timeBase+=timeInterval}};Rickshaw.namespace("Rickshaw.Fixtures.Time");Rickshaw.Fixtures.Time=function(){var tzOffset=(new Date).getTimezoneOffset()*60;var self=this;this.months=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];this.units=[{name:"decade",seconds:86400*365.25*10,formatter:function(d){return parseInt(d.getUTCFullYear()/10,10)*10}},{name:"year",seconds:86400*365.25,formatter:function(d){return d.getUTCFullYear()}},{name:"month",seconds:86400*30.5,formatter:function(d){return self.months[d.getUTCMonth()]}},{name:"week",seconds:86400*7,formatter:function(d){return self.formatDate(d)}},{name:"day",seconds:86400,formatter:function(d){return d.getUTCDate()}},{name:"6 hour",seconds:3600*6,formatter:function(d){return self.formatTime(d)}},{name:"hour",seconds:3600,formatter:function(d){return self.formatTime(d)}},{name:"15 minute",seconds:60*15,formatter:function(d){return self.formatTime(d)}},{name:"minute",seconds:60,formatter:function(d){return d.getUTCMinutes()}},{name:"15 second",seconds:15,formatter:function(d){return d.getUTCSeconds()+"s"}},{name:"second",seconds:1,formatter:function(d){return d.getUTCSeconds()+"s"}}];this.unit=function(unitName){return this.units.filter(function(unit){return unitName==unit.name}).shift()};this.formatDate=function(d){return d3.time.format("%b %e")(d)};this.formatTime=function(d){return d.toUTCString().match(/(\d+:\d+):/)[1]};this.ceil=function(time,unit){var nearFuture;var rounded;if(unit.name=="month"){nearFuture=new Date((time+unit.seconds-1)*1e3);rounded=new Date(0);rounded.setUTCFullYear(nearFuture.getUTCFullYear());rounded.setUTCMonth(nearFuture.getUTCMonth());rounded.setUTCDate(1);rounded.setUTCHours(0);rounded.setUTCMinutes(0);rounded.setUTCSeconds(0);rounded.setUTCMilliseconds(0);return rounded.getTime()/1e3}if(unit.name=="year"){nearFuture=new Date((time+unit.seconds-1)*1e3);rounded=new Date(0);rounded.setUTCFullYear(nearFuture.getUTCFullYear());rounded.setUTCMonth(0);rounded.setUTCDate(1);rounded.setUTCHours(0);rounded.setUTCMinutes(0);rounded.setUTCSeconds(0);rounded.setUTCMilliseconds(0);return rounded.getTime()/1e3}return Math.ceil(time/unit.seconds)*unit.seconds}};Rickshaw.namespace("Rickshaw.Fixtures.Time.Local");Rickshaw.Fixtures.Time.Local=function(){var tzOffset=(new Date).getTimezoneOffset()*60;var self=this;this.months=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];this.units=[{name:"decade",seconds:86400*365.25*10,formatter:function(d){return parseInt(d.getFullYear()/10,10)*10}},{name:"year",seconds:86400*365.25,formatter:function(d){return d.getFullYear()}},{name:"month",seconds:86400*30.5,formatter:function(d){return self.months[d.getMonth()]}},{name:"week",seconds:86400*7,formatter:function(d){return self.formatDate(d)}},{name:"day",seconds:86400,formatter:function(d){return d.getDate()}},{name:"6 hour",seconds:3600*6,formatter:function(d){return self.formatTime(d)}},{name:"hour",seconds:3600,formatter:function(d){return self.formatTime(d)}},{name:"15 minute",seconds:60*15,formatter:function(d){return self.formatTime(d)}},{name:"minute",seconds:60,formatter:function(d){return d.getMinutes()}},{name:"15 second",seconds:15,formatter:function(d){return d.getSeconds()+"s"}},{name:"second",seconds:1,formatter:function(d){return d.getSeconds()+"s"}}];this.unit=function(unitName){return this.units.filter(function(unit){return unitName==unit.name}).shift()};this.formatDate=function(d){return d3.time.format("%b %e")(d)};this.formatTime=function(d){return d.toString().match(/(\d+:\d+):/)[1]};this.ceil=function(time,unit){var nearFuture;var rounded;if(unit.name=="day"){nearFuture=new Date((time+unit.seconds-1)*1e3);rounded=new Date(0);rounded.setMilliseconds(0);rounded.setSeconds(0);rounded.setMinutes(0);rounded.setHours(0);rounded.setDate(nearFuture.getDate());rounded.setMonth(nearFuture.getMonth());rounded.setFullYear(nearFuture.getFullYear());return rounded.getTime()/1e3}if(unit.name=="month"){nearFuture=new Date((time+unit.seconds-1)*1e3);rounded=new Date(0);rounded.setMilliseconds(0);rounded.setSeconds(0);rounded.setMinutes(0);rounded.setHours(0);rounded.setDate(1);rounded.setMonth(nearFuture.getMonth());rounded.setFullYear(nearFuture.getFullYear());return rounded.getTime()/1e3}if(unit.name=="year"){nearFuture=new Date((time+unit.seconds-1)*1e3);rounded=new Date(0);rounded.setFullYear(nearFuture.getFullYear());rounded.setMilliseconds(0);rounded.setSeconds(0);rounded.setMinutes(0);rounded.setHours(0);rounded.setDate(1);rounded.setMonth(0);return rounded.getTime()/1e3}return Math.ceil(time/unit.seconds)*unit.seconds}};Rickshaw.namespace("Rickshaw.Fixtures.Number");Rickshaw.Fixtures.Number.formatKMBT=function(y){var abs_y=Math.abs(y);if(abs_y>=1e12){return y/1e12+"T"}else if(abs_y>=1e9){return y/1e9+"B"}else if(abs_y>=1e6){return y/1e6+"M"}else if(abs_y>=1e3){return y/1e3+"K"}else if(abs_y<1&&y>0){return y.toFixed(2)}else if(abs_y===0){return""}else{return y}};Rickshaw.Fixtures.Number.formatBase1024KMGTP=function(y){var abs_y=Math.abs(y);if(abs_y>=0x4000000000000){return y/0x4000000000000+"P"}else if(abs_y>=1099511627776){return y/1099511627776+"T"}else if(abs_y>=1073741824){return y/1073741824+"G"}else if(abs_y>=1048576){return y/1048576+"M"}else if(abs_y>=1024){return y/1024+"K"}else if(abs_y<1&&y>0){return y.toFixed(2)}else if(abs_y===0){return""}else{return y}};Rickshaw.namespace("Rickshaw.Color.Palette");Rickshaw.Color.Palette=function(args){var color=new Rickshaw.Fixtures.Color;args=args||{};this.schemes={};this.scheme=color.schemes[args.scheme]||args.scheme||color.schemes.colorwheel;this.runningIndex=0;this.generatorIndex=0;if(args.interpolatedStopCount){var schemeCount=this.scheme.length-1;var i,j,scheme=[];for(i=0;iself.graph.x.range()[1]){if(annotation.element){annotation.line.classList.add("offscreen");annotation.element.style.display="none"}annotation.boxes.forEach(function(box){if(box.rangeElement)box.rangeElement.classList.add("offscreen")});return}if(!annotation.element){var element=annotation.element=document.createElement("div");element.classList.add("annotation");this.elements.timeline.appendChild(element);element.addEventListener("click",function(e){element.classList.toggle("active");annotation.line.classList.toggle("active");annotation.boxes.forEach(function(box){if(box.rangeElement)box.rangeElement.classList.toggle("active")})},false)}annotation.element.style.left=left+"px";annotation.element.style.display="block";annotation.boxes.forEach(function(box){var element=box.element;if(!element){element=box.element=document.createElement("div");element.classList.add("content");element.innerHTML=box.content;annotation.element.appendChild(element);annotation.line=document.createElement("div");annotation.line.classList.add("annotation_line");self.graph.element.appendChild(annotation.line);if(box.end){box.rangeElement=document.createElement("div");box.rangeElement.classList.add("annotation_range");self.graph.element.appendChild(box.rangeElement)}}if(box.end){var annotationRangeStart=left;var annotationRangeEnd=Math.min(self.graph.x(box.end),self.graph.x.range()[1]);if(annotationRangeStart>annotationRangeEnd){annotationRangeEnd=left;annotationRangeStart=Math.max(self.graph.x(box.end),self.graph.x.range()[0])}var annotationRangeWidth=annotationRangeEnd-annotationRangeStart;box.rangeElement.style.left=annotationRangeStart+"px";box.rangeElement.style.width=annotationRangeWidth+"px";box.rangeElement.classList.remove("offscreen")}annotation.line.classList.remove("offscreen");annotation.line.style.left=left+"px"})},this)};this.graph.onUpdate(function(){self.update()})};Rickshaw.namespace("Rickshaw.Graph.Axis.Time");Rickshaw.Graph.Axis.Time=function(args){var self=this;this.graph=args.graph;this.elements=[];this.ticksTreatment=args.ticksTreatment||"plain";this.fixedTimeUnit=args.timeUnit;var time=args.timeFixture||new Rickshaw.Fixtures.Time;this.appropriateTimeUnit=function(){var unit;var units=time.units;var domain=this.graph.x.domain();var rangeSeconds=domain[1]-domain[0];units.forEach(function(u){if(Math.floor(rangeSeconds/u.seconds)>=2){unit=unit||u}});return unit||time.units[time.units.length-1]};this.tickOffsets=function(){var domain=this.graph.x.domain();var unit=this.fixedTimeUnit||this.appropriateTimeUnit();var count=Math.ceil((domain[1]-domain[0])/unit.seconds);var runningTick=domain[0];var offsets=[];for(var i=0;iself.graph.x.range()[1])return;var element=document.createElement("div");element.style.left=self.graph.x(o.value)+"px";element.classList.add("x_tick");element.classList.add(self.ticksTreatment);var title=document.createElement("div");title.classList.add("title");title.innerHTML=o.unit.formatter(new Date(o.value*1e3));element.appendChild(title);self.graph.element.appendChild(element);self.elements.push(element)})};this.graph.onUpdate(function(){self.render()})};Rickshaw.namespace("Rickshaw.Graph.Axis.X");Rickshaw.Graph.Axis.X=function(args){var self=this;var berthRate=.1;this.initialize=function(args){this.graph=args.graph;this.orientation=args.orientation||"top";this.pixelsPerTick=args.pixelsPerTick||75;if(args.ticks)this.staticTicks=args.ticks;if(args.tickValues)this.tickValues=args.tickValues;this.tickSize=args.tickSize||4;this.ticksTreatment=args.ticksTreatment||"plain";if(args.element){this.element=args.element;this._discoverSize(args.element,args);this.vis=d3.select(args.element).append("svg:svg").attr("height",this.height).attr("width",this.width).attr("class","rickshaw_graph x_axis_d3");this.element=this.vis[0][0];this.element.style.position="relative";this.setSize({width:args.width,height:args.height})}else{this.vis=this.graph.vis}this.graph.onUpdate(function(){self.render()})};this.setSize=function(args){args=args||{};if(!this.element)return;this._discoverSize(this.element.parentNode,args);this.vis.attr("height",this.height).attr("width",this.width*(1+berthRate));var berth=Math.floor(this.width*berthRate/2);this.element.style.left=-1*berth+"px"};this.render=function(){if(this.graph.width!==this._renderWidth)this.setSize({auto:true});var axis=d3.svg.axis().scale(this.graph.x).orient(this.orientation);axis.tickFormat(args.tickFormat||function(x){return x});if(this.tickValues)axis.tickValues(this.tickValues);this.ticks=this.staticTicks||Math.floor(this.graph.width/this.pixelsPerTick);var berth=Math.floor(this.width*berthRate/2)||0;var transform;if(this.orientation=="top"){var yOffset=this.height||this.graph.height;transform="translate("+berth+","+yOffset+")"}else{transform="translate("+berth+", 0)"}if(this.element){this.vis.selectAll("*").remove()}this.vis.append("svg:g").attr("class",["x_ticks_d3",this.ticksTreatment].join(" ")).attr("transform",transform).call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(this.tickSize));var gridSize=(this.orientation=="bottom"?1:-1)*this.graph.height;this.graph.vis.append("svg:g").attr("class","x_grid_d3").call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(gridSize));this._renderHeight=this.graph.height};this._discoverSize=function(element,args){if(typeof window!=="undefined"){var style=window.getComputedStyle(element,null);var elementHeight=parseInt(style.getPropertyValue("height"),10);if(!args.auto){var elementWidth=parseInt(style.getPropertyValue("width"),10)}}this.width=(args.width||elementWidth||this.graph.width)*(1+berthRate);this.height=args.height||elementHeight||40};this.initialize(args)};Rickshaw.namespace("Rickshaw.Graph.Axis.Y");Rickshaw.Graph.Axis.Y=Rickshaw.Class.create({initialize:function(args){this.graph=args.graph;this.orientation=args.orientation||"right";this.pixelsPerTick=args.pixelsPerTick||75;if(args.ticks)this.staticTicks=args.ticks;if(args.tickValues)this.tickValues=args.tickValues;this.tickSize=args.tickSize||4;this.ticksTreatment=args.ticksTreatment||"plain";this.tickFormat=args.tickFormat||function(y){return y};this.berthRate=.1;if(args.element){this.element=args.element;this.vis=d3.select(args.element).append("svg:svg").attr("class","rickshaw_graph y_axis");this.element=this.vis[0][0];this.element.style.position="relative";this.setSize({width:args.width,height:args.height})}else{this.vis=this.graph.vis}var self=this;this.graph.onUpdate(function(){self.render()})},setSize:function(args){args=args||{};if(!this.element)return;if(typeof window!=="undefined"){var style=window.getComputedStyle(this.element.parentNode,null);var elementWidth=parseInt(style.getPropertyValue("width"),10);if(!args.auto){var elementHeight=parseInt(style.getPropertyValue("height"),10)}}this.width=args.width||elementWidth||this.graph.width*this.berthRate;this.height=args.height||elementHeight||this.graph.height;this.vis.attr("width",this.width).attr("height",this.height*(1+this.berthRate));var berth=this.height*this.berthRate;if(this.orientation=="left"){this.element.style.top=-1*berth+"px"}},render:function(){if(this.graph.height!==this._renderHeight)this.setSize({auto:true});this.ticks=this.staticTicks||Math.floor(this.graph.height/this.pixelsPerTick);var axis=this._drawAxis(this.graph.y);this._drawGrid(axis);this._renderHeight=this.graph.height},_drawAxis:function(scale){var axis=d3.svg.axis().scale(scale).orient(this.orientation);axis.tickFormat(this.tickFormat);if(this.tickValues)axis.tickValues(this.tickValues);if(this.orientation=="left"){var berth=this.height*this.berthRate;var transform="translate("+this.width+", "+berth+")"}if(this.element){this.vis.selectAll("*").remove()}this.vis.append("svg:g").attr("class",["y_ticks",this.ticksTreatment].join(" ")).attr("transform",transform).call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(this.tickSize));return axis},_drawGrid:function(axis){var gridSize=(this.orientation=="right"?1:-1)*this.graph.width;this.graph.vis.append("svg:g").attr("class","y_grid").call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(gridSize))}});Rickshaw.namespace("Rickshaw.Graph.Axis.Y.Scaled");Rickshaw.Graph.Axis.Y.Scaled=Rickshaw.Class.create(Rickshaw.Graph.Axis.Y,{initialize:function($super,args){if(typeof args.scale==="undefined"){throw new Error("Scaled requires scale")}this.scale=args.scale;if(typeof args.grid==="undefined"){this.grid=true}else{this.grid=args.grid}$super(args)},_drawAxis:function($super,scale){var adjustedScale=this.scale.copy().range(scale.range());return $super(adjustedScale)},_drawGrid:function($super,axis){if(this.grid){$super(axis)}}});Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Highlight");Rickshaw.Graph.Behavior.Series.Highlight=function(args){this.graph=args.graph;this.legend=args.legend;var self=this;var colorSafe={};var activeLine=null;var disabledColor=args.disabledColor||function(seriesColor){return d3.interpolateRgb(seriesColor,d3.rgb("#d8d8d8"))(.8).toString()};this.addHighlightEvents=function(l){l.element.addEventListener("mouseover",function(e){if(activeLine)return;else activeLine=l;self.legend.lines.forEach(function(line,index){if(l===line){if(index>0&&self.graph.renderer.unstack&&(line.series.renderer?line.series.renderer.unstack:true)){var seriesIndex=self.graph.series.length-index-1; +line.originalIndex=seriesIndex;var series=self.graph.series.splice(seriesIndex,1)[0];self.graph.series.push(series)}return}colorSafe[line.series.name]=colorSafe[line.series.name]||line.series.color;line.series.color=disabledColor(line.series.color)});self.graph.update()},false);l.element.addEventListener("mouseout",function(e){if(!activeLine)return;else activeLine=null;self.legend.lines.forEach(function(line){if(l===line&&line.hasOwnProperty("originalIndex")){var series=self.graph.series.pop();self.graph.series.splice(line.originalIndex,0,series);delete line.originalIndex}if(colorSafe[line.series.name]){line.series.color=colorSafe[line.series.name]}});self.graph.update()},false)};if(this.legend){this.legend.lines.forEach(function(l){self.addHighlightEvents(l)})}};Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Order");Rickshaw.Graph.Behavior.Series.Order=function(args){this.graph=args.graph;this.legend=args.legend;var self=this;if(typeof window.$=="undefined"){throw"couldn't find jQuery at window.$"}if(typeof window.$.ui=="undefined"){throw"couldn't find jQuery UI at window.$.ui"}$(function(){$(self.legend.list).sortable({containment:"parent",tolerance:"pointer",update:function(event,ui){var series=[];$(self.legend.list).find("li").each(function(index,item){if(!item.series)return;series.push(item.series)});for(var i=self.graph.series.length-1;i>=0;i--){self.graph.series[i]=series.shift()}self.graph.update()}});$(self.legend.list).disableSelection()});this.graph.onUpdate(function(){var h=window.getComputedStyle(self.legend.element).height;self.legend.element.style.height=h})};Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Toggle");Rickshaw.Graph.Behavior.Series.Toggle=function(args){this.graph=args.graph;this.legend=args.legend;var self=this;this.addAnchor=function(line){var anchor=document.createElement("a");anchor.innerHTML="✔";anchor.classList.add("action");line.element.insertBefore(anchor,line.element.firstChild);anchor.onclick=function(e){if(line.series.disabled){line.series.enable();line.element.classList.remove("disabled")}else{if(this.graph.series.filter(function(s){return!s.disabled}).length<=1)return;line.series.disable();line.element.classList.add("disabled")}}.bind(this);var label=line.element.getElementsByTagName("span")[0];label.onclick=function(e){var disableAllOtherLines=line.series.disabled;if(!disableAllOtherLines){for(var i=0;idomainX){dataIndex=Math.abs(domainX-data[i].x)yMax)yMax=y});if(series[0].xxMax)xMax=series[series.length-1].x});xMin-=(xMax-xMin)*this.padding.left;xMax+=(xMax-xMin)*this.padding.right;yMin=this.graph.min==="auto"?yMin:this.graph.min||0;yMax=this.graph.max===undefined?yMax:this.graph.max;if(this.graph.min==="auto"||yMin<0){yMin-=(yMax-yMin)*this.padding.bottom}if(this.graph.max===undefined){yMax+=(yMax-yMin)*this.padding.top}return{x:[xMin,xMax],y:[yMin,yMax]}},render:function(args){args=args||{};var graph=this.graph;var series=args.series||graph.series;var vis=args.vis||graph.vis;vis.selectAll("*").remove();var data=series.filter(function(s){return!s.disabled}).map(function(s){return s.stack});var nodes=vis.selectAll("path").data(data).enter().append("svg:path").attr("d",this.seriesPathFactory());var i=0;series.forEach(function(series){if(series.disabled)return;series.path=nodes[0][i++];this._styleSeries(series)},this)},_styleSeries:function(series){var fill=this.fill?series.color:"none";var stroke=this.stroke?series.color:"none";series.path.setAttribute("fill",fill);series.path.setAttribute("stroke",stroke);series.path.setAttribute("stroke-width",this.strokeWidth);series.path.setAttribute("class",series.className)},configure:function(args){args=args||{};Rickshaw.keys(this.defaults()).forEach(function(key){if(!args.hasOwnProperty(key)){this[key]=this[key]||this.graph[key]||this.defaults()[key];return}if(typeof this.defaults()[key]=="object"){Rickshaw.keys(this.defaults()[key]).forEach(function(k){this[key][k]=args[key][k]!==undefined?args[key][k]:this[key][k]!==undefined?this[key][k]:this.defaults()[key][k]},this)}else{this[key]=args[key]!==undefined?args[key]:this[key]!==undefined?this[key]:this.graph[key]!==undefined?this.graph[key]:this.defaults()[key]}},this)},setStrokeWidth:function(strokeWidth){if(strokeWidth!==undefined){this.strokeWidth=strokeWidth}},setTension:function(tension){if(tension!==undefined){this.tension=tension}}});Rickshaw.namespace("Rickshaw.Graph.Renderer.Line");Rickshaw.Graph.Renderer.Line=Rickshaw.Class.create(Rickshaw.Graph.Renderer,{name:"line",defaults:function($super){return Rickshaw.extend($super(),{unstack:true,fill:false,stroke:true})},seriesPathFactory:function(){var graph=this.graph;var factory=d3.svg.line().x(function(d){return graph.x(d.x)}).y(function(d){return graph.y(d.y)}).interpolate(this.graph.interpolation).tension(this.tension);factory.defined&&factory.defined(function(d){return d.y!==null});return factory}});Rickshaw.namespace("Rickshaw.Graph.Renderer.Stack");Rickshaw.Graph.Renderer.Stack=Rickshaw.Class.create(Rickshaw.Graph.Renderer,{name:"stack",defaults:function($super){return Rickshaw.extend($super(),{fill:true,stroke:false,unstack:false})},seriesPathFactory:function(){var graph=this.graph;var factory=d3.svg.area().x(function(d){return graph.x(d.x)}).y0(function(d){return graph.y(d.y0)}).y1(function(d){return graph.y(d.y+d.y0)}).interpolate(this.graph.interpolation).tension(this.tension);factory.defined&&factory.defined(function(d){return d.y!==null});return factory}});Rickshaw.namespace("Rickshaw.Graph.Renderer.Bar");Rickshaw.Graph.Renderer.Bar=Rickshaw.Class.create(Rickshaw.Graph.Renderer,{name:"bar",defaults:function($super){var defaults=Rickshaw.extend($super(),{gapSize:.05,unstack:false});delete defaults.tension;return defaults},initialize:function($super,args){args=args||{};this.gapSize=args.gapSize||this.gapSize;$super(args)},domain:function($super){var domain=$super();var frequentInterval=this._frequentInterval(this.graph.stackedData.slice(-1).shift());domain.x[1]+=Number(frequentInterval.magnitude);return domain},barWidth:function(series){var frequentInterval=this._frequentInterval(series.stack);var barWidth=this.graph.x(series.stack[0].x+frequentInterval.magnitude*(1-this.gapSize));return barWidth},render:function(args){args=args||{};var graph=this.graph;var series=args.series||graph.series;var vis=args.vis||graph.vis;vis.selectAll("*").remove();var barWidth=this.barWidth(series.active()[0]);var barXOffset=0;var activeSeriesCount=series.filter(function(s){return!s.disabled}).length;var seriesBarWidth=this.unstack?barWidth/activeSeriesCount:barWidth;var transform=function(d){var matrix=[1,0,0,d.y<0?-1:1,0,d.y<0?graph.y.magnitude(Math.abs(d.y))*2:0];return"matrix("+matrix.join(",")+")"};series.forEach(function(series){if(series.disabled)return;var barWidth=this.barWidth(series);var nodes=vis.selectAll("path").data(series.stack.filter(function(d){return d.y!==null})).enter().append("svg:rect").attr("x",function(d){return graph.x(d.x)+barXOffset}).attr("y",function(d){return graph.y(d.y0+Math.abs(d.y))*(d.y<0?-1:1)}).attr("width",seriesBarWidth).attr("height",function(d){return graph.y.magnitude(Math.abs(d.y))}).attr("transform",transform);Array.prototype.forEach.call(nodes[0],function(n){n.setAttribute("fill",series.color)});if(this.unstack)barXOffset+=seriesBarWidth},this)},_frequentInterval:function(data){var intervalCounts={};for(var i=0;i0){this[0].data.forEach(function(plot){item.data.push({x:plot.x,y:0})})}else if(item.data.length===0){item.data.push({x:this.timeBase-(this.timeInterval||0),y:0})}this.push(item);if(this.legend){this.legend.addLine(this.itemByName(item.name))}},addData:function(data,x){var index=this.getIndex();Rickshaw.keys(data).forEach(function(name){if(!this.itemByName(name)){this.addItem({name:name})}},this);this.forEach(function(item){item.data.push({x:x||(index*this.timeInterval||1)+this.timeBase,y:data[item.name]||0})},this)},getIndex:function(){return this[0]&&this[0].data&&this[0].data.length?this[0].data.length:0},itemByName:function(name){for(var i=0;i1;i--){this.currentSize+=1;this.currentIndex+=1;this.forEach(function(item){item.data.unshift({x:((i-1)*this.timeInterval||1)+this.timeBase,y:0,i:i})},this)}}},addData:function($super,data,x){$super(data,x);this.currentSize+=1;this.currentIndex+=1;if(this.maxDataPoints!==undefined){while(this.currentSize>this.maxDataPoints){this.dropData()}}},dropData:function(){this.forEach(function(item){item.data.splice(0,1)});this.currentSize-=1},getIndex:function(){return this.currentIndex}}); \ No newline at end of file diff --git a/assets/stylesheets/application.scss b/assets/stylesheets/application.scss new file mode 100644 index 0000000..20e009f --- /dev/null +++ b/assets/stylesheets/application.scss @@ -0,0 +1,257 @@ +/* + //=require_directory . + //=require_tree ../../widgets +*/ +// ---------------------------------------------------------------------------- +// Sass declarations +// ---------------------------------------------------------------------------- +$background-color: #222; +$text-color: #fff; + +$background-warning-color-1: #e82711; +$background-warning-color-2: #9b2d23; +$text-warning-color: #fff; + +$background-danger-color-1: #eeae32; +$background-danger-color-2: #ff9618; +$text-danger-color: #fff; + +@-webkit-keyframes status-warning-background { + 0% { background-color: $background-warning-color-1; } + 50% { background-color: $background-warning-color-2; } + 100% { background-color: $background-warning-color-1; } +} +@-webkit-keyframes status-danger-background { + 0% { background-color: $background-danger-color-1; } + 50% { background-color: $background-danger-color-2; } + 100% { background-color: $background-danger-color-1; } +} +@mixin animation($animation-name, $duration, $function, $animation-iteration-count:""){ + -webkit-animation: $animation-name $duration $function #{$animation-iteration-count}; + -moz-animation: $animation-name $duration $function #{$animation-iteration-count}; + -ms-animation: $animation-name $duration $function #{$animation-iteration-count}; +} + +// ---------------------------------------------------------------------------- +// Base styles +// ---------------------------------------------------------------------------- +html { + font-size: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} + +body { + margin: 0; + background-color: $background-color; + font-size: 20px; + color: $text-color; + font-family: 'Open Sans', "Helvetica Neue", Helvetica, Arial, sans-serif; +} + +b, strong { + font-weight: bold; +} + +a { + text-decoration: none; + color: inherit; +} + +img { + border: 0; + -ms-interpolation-mode: bicubic; + vertical-align: middle; +} + +img, object { + max-width: 100%; +} + +iframe { + max-width: 100%; +} + +table { + border-collapse: collapse; + border-spacing: 0; + width: 100%; +} + +td { + vertical-align: middle; +} + +ul, ol { + padding: 0; + margin: 0; +} + +h1, h2, h3, h4, h5, p { + padding: 0; + margin: 0; +} +h1 { + margin-bottom: 12px; + text-align: center; + font-size: 30px; + font-weight: 400; +} +h2 { + text-transform: uppercase; + font-size: 76px; + font-weight: 700; + color: $text-color; +} +h3 { + font-size: 25px; + font-weight: 600; + color: $text-color; +} + +// ---------------------------------------------------------------------------- +// Base widget styles +// ---------------------------------------------------------------------------- +.gridster { + margin: 0px auto; +} + +.icon-background { + width: 100%!important; + height: 100%; + position: absolute; + left: 0; + top: 0; + opacity: 0.1; + font-size: 275px; + text-align: center; + margin-top: 82px; +} + +.list-nostyle { + list-style: none; +} + +.gridster ul { + list-style: none; +} + +.gs_w { + width: 100%; + display: table; + cursor: pointer; +} + +.widget { + padding: 25px 12px; + text-align: center; + width: 100%; + display: table-cell; + vertical-align: middle; +} + +.widget.status-warning { + background-color: $background-warning-color-1; + @include animation(status-warning-background, 2s, ease, infinite); + + .icon-warning-sign { + display: inline-block; + } + + .title, .more-info { + color: $text-warning-color; + } +} + +.widget.status-danger { + color: $text-danger-color; + background-color: $background-danger-color-1; + @include animation(status-danger-background, 2s, ease, infinite); + + .icon-warning-sign { + display: inline-block; + } + + .title, .more-info { + color: $text-danger-color; + } +} + +.more-info { + font-size: 15px; + position: absolute; + bottom: 32px; + left: 0; + right: 0; +} + +.updated-at { + font-size: 15px; + position: absolute; + bottom: 12px; + left: 0; + right: 0; +} + +#save-gridster { + display: none; + position: fixed; + top: 0; + margin: 0px auto; + left: 50%; + z-index: 1000; + background: black; + width: 190px; + text-align: center; + border: 1px solid white; + border-top: 0px; + margin-left: -95px; + padding: 15px; +} + +#save-gridster:hover { + padding-top: 25px; +} + +#saving-instructions { + display: none; + padding: 10px; + width: 500px; + height: 122px; + z-index: 1000; + background: white; + top: 100px; + color: black; + font-size: 15px; + padding-bottom: 4px; + + textarea { + white-space: nowrap; + width: 494px; + height: 80px; + } +} + +#lean_overlay { + position: fixed; + z-index:100; + top: 0px; + left: 0px; + height:100%; + width:100%; + background: #000; + display: none; +} + +#container { + padding-top: 5px; +} + + +// ---------------------------------------------------------------------------- +// Clearfix +// ---------------------------------------------------------------------------- +.clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; } +.clearfix:after { clear: both; } +.clearfix { zoom: 1; } + diff --git a/assets/stylesheets/font-awesome.css b/assets/stylesheets/font-awesome.css new file mode 100644 index 0000000..69ae843 --- /dev/null +++ b/assets/stylesheets/font-awesome.css @@ -0,0 +1,1479 @@ +/*! + * Font Awesome 3.2.1 + * the iconic font designed for Bootstrap + * ------------------------------------------------------------------------------ + * The full suite of pictographic icons, examples, and documentation can be + * found at http://fontawesome.io. Stay up to date on Twitter at + * http://twitter.com/fontawesome. + * + * License + * ------------------------------------------------------------------------------ + * - The Font Awesome font is licensed under SIL OFL 1.1 - + * http://scripts.sil.org/OFL + * - Font Awesome CSS, LESS, and SASS files are licensed under MIT License - + * http://opensource.org/licenses/mit-license.html + * - Font Awesome documentation licensed under CC BY 3.0 - + * http://creativecommons.org/licenses/by/3.0/ + * - Attribution is no longer required in Font Awesome 3.0, but much appreciated: + * "Font Awesome by Dave Gandy - http://fontawesome.io" + * + * Author - Dave Gandy + * ------------------------------------------------------------------------------ + * Email: dave@fontawesome.io + * Twitter: http://twitter.com/davegandy + * Work: Lead Product Designer @ Kyruus - http://kyruus.com + */ +/* FONT PATH + * -------------------------- */ +@font-face { + font-family: 'FontAwesome'; + src: url('../assets/fontawesome-webfont.eot?v=3.2.1'); + src: url('../assets/fontawesome-webfont.eot?#iefix&v=3.2.1') format('embedded-opentype'), url('../assets/fontawesome-webfont.woff?v=3.2.1') format('woff'), url('../assets/fontawesome-webfont.ttf?v=3.2.1') format('truetype'), url('../assets/fontawesome-webfont.svg#fontawesomeregular?v=3.2.1') format('svg'); + font-weight: normal; + font-style: normal; +} +/* FONT AWESOME CORE + * -------------------------- */ +[class^="icon-"], +[class*=" icon-"] { + font-family: FontAwesome; + font-weight: normal; + font-style: normal; + text-decoration: inherit; + -webkit-font-smoothing: antialiased; + *margin-right: .3em; +} +[class^="icon-"]:before, +[class*=" icon-"]:before { + text-decoration: inherit; + display: inline-block; + speak: none; +} +/* makes the font 33% larger relative to the icon container */ +.icon-large:before { + vertical-align: -10%; + font-size: 1.3333333333333333em; +} +/* makes sure icons active on rollover in links */ +a [class^="icon-"], +a [class*=" icon-"] { + display: inline; +} +/* increased font size for icon-large */ +[class^="icon-"].icon-fixed-width, +[class*=" icon-"].icon-fixed-width { + display: inline-block; + width: 1.1428571428571428em; + text-align: right; + padding-right: 0.2857142857142857em; +} +[class^="icon-"].icon-fixed-width.icon-large, +[class*=" icon-"].icon-fixed-width.icon-large { + width: 1.4285714285714286em; +} +.icons-ul { + margin-left: 2.142857142857143em; + list-style-type: none; +} +.icons-ul > li { + position: relative; +} +.icons-ul .icon-li { + position: absolute; + left: -2.142857142857143em; + width: 2.142857142857143em; + text-align: center; + line-height: inherit; +} +[class^="icon-"].hide, +[class*=" icon-"].hide { + display: none; +} +.icon-muted { + color: #eeeeee; +} +.icon-light { + color: #ffffff; +} +.icon-dark { + color: #333333; +} +.icon-border { + border: solid 1px #eeeeee; + padding: .2em .25em .15em; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +.icon-2x { + font-size: 2em; +} +.icon-2x.icon-border { + border-width: 2px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.icon-3x { + font-size: 3em; +} +.icon-3x.icon-border { + border-width: 3px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; +} +.icon-4x { + font-size: 4em; +} +.icon-4x.icon-border { + border-width: 4px; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; +} +.icon-5x { + font-size: 5em; +} +.icon-5x.icon-border { + border-width: 5px; + -webkit-border-radius: 7px; + -moz-border-radius: 7px; + border-radius: 7px; +} +.pull-right { + float: right; +} +.pull-left { + float: left; +} +[class^="icon-"].pull-left, +[class*=" icon-"].pull-left { + margin-right: .3em; +} +[class^="icon-"].pull-right, +[class*=" icon-"].pull-right { + margin-left: .3em; +} +/* BOOTSTRAP SPECIFIC CLASSES + * -------------------------- */ +/* Bootstrap 2.0 sprites.less reset */ +[class^="icon-"], +[class*=" icon-"] { + display: inline; + width: auto; + height: auto; + line-height: normal; + vertical-align: baseline; + background-image: none; + background-position: 0% 0%; + background-repeat: repeat; + margin-top: 0; +} +/* more sprites.less reset */ +.icon-white, +.nav-pills > .active > a > [class^="icon-"], +.nav-pills > .active > a > [class*=" icon-"], +.nav-list > .active > a > [class^="icon-"], +.nav-list > .active > a > [class*=" icon-"], +.navbar-inverse .nav > .active > a > [class^="icon-"], +.navbar-inverse .nav > .active > a > [class*=" icon-"], +.dropdown-menu > li > a:hover > [class^="icon-"], +.dropdown-menu > li > a:hover > [class*=" icon-"], +.dropdown-menu > .active > a > [class^="icon-"], +.dropdown-menu > .active > a > [class*=" icon-"], +.dropdown-submenu:hover > a > [class^="icon-"], +.dropdown-submenu:hover > a > [class*=" icon-"] { + background-image: none; +} +/* keeps Bootstrap styles with and without icons the same */ +.btn [class^="icon-"].icon-large, +.nav [class^="icon-"].icon-large, +.btn [class*=" icon-"].icon-large, +.nav [class*=" icon-"].icon-large { + line-height: .9em; +} +.btn [class^="icon-"].icon-spin, +.nav [class^="icon-"].icon-spin, +.btn [class*=" icon-"].icon-spin, +.nav [class*=" icon-"].icon-spin { + display: inline-block; +} +.nav-tabs [class^="icon-"], +.nav-pills [class^="icon-"], +.nav-tabs [class*=" icon-"], +.nav-pills [class*=" icon-"], +.nav-tabs [class^="icon-"].icon-large, +.nav-pills [class^="icon-"].icon-large, +.nav-tabs [class*=" icon-"].icon-large, +.nav-pills [class*=" icon-"].icon-large { + line-height: .9em; +} +.btn [class^="icon-"].pull-left.icon-2x, +.btn [class*=" icon-"].pull-left.icon-2x, +.btn [class^="icon-"].pull-right.icon-2x, +.btn [class*=" icon-"].pull-right.icon-2x { + margin-top: .18em; +} +.btn [class^="icon-"].icon-spin.icon-large, +.btn [class*=" icon-"].icon-spin.icon-large { + line-height: .8em; +} +.btn.btn-small [class^="icon-"].pull-left.icon-2x, +.btn.btn-small [class*=" icon-"].pull-left.icon-2x, +.btn.btn-small [class^="icon-"].pull-right.icon-2x, +.btn.btn-small [class*=" icon-"].pull-right.icon-2x { + margin-top: .25em; +} +.btn.btn-large [class^="icon-"], +.btn.btn-large [class*=" icon-"] { + margin-top: 0; +} +.btn.btn-large [class^="icon-"].pull-left.icon-2x, +.btn.btn-large [class*=" icon-"].pull-left.icon-2x, +.btn.btn-large [class^="icon-"].pull-right.icon-2x, +.btn.btn-large [class*=" icon-"].pull-right.icon-2x { + margin-top: .05em; +} +.btn.btn-large [class^="icon-"].pull-left.icon-2x, +.btn.btn-large [class*=" icon-"].pull-left.icon-2x { + margin-right: .2em; +} +.btn.btn-large [class^="icon-"].pull-right.icon-2x, +.btn.btn-large [class*=" icon-"].pull-right.icon-2x { + margin-left: .2em; +} +/* Fixes alignment in nav lists */ +.nav-list [class^="icon-"], +.nav-list [class*=" icon-"] { + line-height: inherit; +} +/* EXTRAS + * -------------------------- */ +/* Stacked and layered icon */ +.icon-stack { + position: relative; + display: inline-block; + width: 2em; + height: 2em; + line-height: 2em; + vertical-align: -35%; +} +.icon-stack [class^="icon-"], +.icon-stack [class*=" icon-"] { + display: block; + text-align: center; + position: absolute; + width: 100%; + height: 100%; + font-size: 1em; + line-height: inherit; + *line-height: 2em; +} +.icon-stack .icon-stack-base { + font-size: 2em; + *line-height: 1em; +} +/* Animated rotating icon */ +.icon-spin { + display: inline-block; + -moz-animation: spin 2s infinite linear; + -o-animation: spin 2s infinite linear; + -webkit-animation: spin 2s infinite linear; + animation: spin 2s infinite linear; +} +/* Prevent stack and spinners from being taken inline when inside a link */ +a .icon-stack, +a .icon-spin { + display: inline-block; + text-decoration: none; +} +@-moz-keyframes spin { + 0% { + -moz-transform: rotate(0deg); + } + 100% { + -moz-transform: rotate(359deg); + } +} +@-webkit-keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + } +} +@-o-keyframes spin { + 0% { + -o-transform: rotate(0deg); + } + 100% { + -o-transform: rotate(359deg); + } +} +@-ms-keyframes spin { + 0% { + -ms-transform: rotate(0deg); + } + 100% { + -ms-transform: rotate(359deg); + } +} +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(359deg); + } +} +/* Icon rotations and mirroring */ +.icon-rotate-90:before { + -webkit-transform: rotate(90deg); + -moz-transform: rotate(90deg); + -ms-transform: rotate(90deg); + -o-transform: rotate(90deg); + transform: rotate(90deg); + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); +} +.icon-rotate-180:before { + -webkit-transform: rotate(180deg); + -moz-transform: rotate(180deg); + -ms-transform: rotate(180deg); + -o-transform: rotate(180deg); + transform: rotate(180deg); + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); +} +.icon-rotate-270:before { + -webkit-transform: rotate(270deg); + -moz-transform: rotate(270deg); + -ms-transform: rotate(270deg); + -o-transform: rotate(270deg); + transform: rotate(270deg); + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); +} +.icon-flip-horizontal:before { + -webkit-transform: scale(-1, 1); + -moz-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + -o-transform: scale(-1, 1); + transform: scale(-1, 1); +} +.icon-flip-vertical:before { + -webkit-transform: scale(1, -1); + -moz-transform: scale(1, -1); + -ms-transform: scale(1, -1); + -o-transform: scale(1, -1); + transform: scale(1, -1); +} +/* ensure rotation occurs inside anchor tags */ +a .icon-rotate-90:before, +a .icon-rotate-180:before, +a .icon-rotate-270:before, +a .icon-flip-horizontal:before, +a .icon-flip-vertical:before { + display: inline-block; +} +/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen + readers do not read off random characters that represent icons */ +.icon-glass:before { + content: "\f000"; +} +.icon-music:before { + content: "\f001"; +} +.icon-search:before { + content: "\f002"; +} +.icon-envelope-alt:before { + content: "\f003"; +} +.icon-heart:before { + content: "\f004"; +} +.icon-star:before { + content: "\f005"; +} +.icon-star-empty:before { + content: "\f006"; +} +.icon-user:before { + content: "\f007"; +} +.icon-film:before { + content: "\f008"; +} +.icon-th-large:before { + content: "\f009"; +} +.icon-th:before { + content: "\f00a"; +} +.icon-th-list:before { + content: "\f00b"; +} +.icon-ok:before { + content: "\f00c"; +} +.icon-remove:before { + content: "\f00d"; +} +.icon-zoom-in:before { + content: "\f00e"; +} +.icon-zoom-out:before { + content: "\f010"; +} +.icon-power-off:before, +.icon-off:before { + content: "\f011"; +} +.icon-signal:before { + content: "\f012"; +} +.icon-gear:before, +.icon-cog:before { + content: "\f013"; +} +.icon-trash:before { + content: "\f014"; +} +.icon-home:before { + content: "\f015"; +} +.icon-file-alt:before { + content: "\f016"; +} +.icon-time:before { + content: "\f017"; +} +.icon-road:before { + content: "\f018"; +} +.icon-download-alt:before { + content: "\f019"; +} +.icon-download:before { + content: "\f01a"; +} +.icon-upload:before { + content: "\f01b"; +} +.icon-inbox:before { + content: "\f01c"; +} +.icon-play-circle:before { + content: "\f01d"; +} +.icon-rotate-right:before, +.icon-repeat:before { + content: "\f01e"; +} +.icon-refresh:before { + content: "\f021"; +} +.icon-list-alt:before { + content: "\f022"; +} +.icon-lock:before { + content: "\f023"; +} +.icon-flag:before { + content: "\f024"; +} +.icon-headphones:before { + content: "\f025"; +} +.icon-volume-off:before { + content: "\f026"; +} +.icon-volume-down:before { + content: "\f027"; +} +.icon-volume-up:before { + content: "\f028"; +} +.icon-qrcode:before { + content: "\f029"; +} +.icon-barcode:before { + content: "\f02a"; +} +.icon-tag:before { + content: "\f02b"; +} +.icon-tags:before { + content: "\f02c"; +} +.icon-book:before { + content: "\f02d"; +} +.icon-bookmark:before { + content: "\f02e"; +} +.icon-print:before { + content: "\f02f"; +} +.icon-camera:before { + content: "\f030"; +} +.icon-font:before { + content: "\f031"; +} +.icon-bold:before { + content: "\f032"; +} +.icon-italic:before { + content: "\f033"; +} +.icon-text-height:before { + content: "\f034"; +} +.icon-text-width:before { + content: "\f035"; +} +.icon-align-left:before { + content: "\f036"; +} +.icon-align-center:before { + content: "\f037"; +} +.icon-align-right:before { + content: "\f038"; +} +.icon-align-justify:before { + content: "\f039"; +} +.icon-list:before { + content: "\f03a"; +} +.icon-indent-left:before { + content: "\f03b"; +} +.icon-indent-right:before { + content: "\f03c"; +} +.icon-facetime-video:before { + content: "\f03d"; +} +.icon-picture:before { + content: "\f03e"; +} +.icon-pencil:before { + content: "\f040"; +} +.icon-map-marker:before { + content: "\f041"; +} +.icon-adjust:before { + content: "\f042"; +} +.icon-tint:before { + content: "\f043"; +} +.icon-edit:before { + content: "\f044"; +} +.icon-share:before { + content: "\f045"; +} +.icon-check:before { + content: "\f046"; +} +.icon-move:before { + content: "\f047"; +} +.icon-step-backward:before { + content: "\f048"; +} +.icon-fast-backward:before { + content: "\f049"; +} +.icon-backward:before { + content: "\f04a"; +} +.icon-play:before { + content: "\f04b"; +} +.icon-pause:before { + content: "\f04c"; +} +.icon-stop:before { + content: "\f04d"; +} +.icon-forward:before { + content: "\f04e"; +} +.icon-fast-forward:before { + content: "\f050"; +} +.icon-step-forward:before { + content: "\f051"; +} +.icon-eject:before { + content: "\f052"; +} +.icon-chevron-left:before { + content: "\f053"; +} +.icon-chevron-right:before { + content: "\f054"; +} +.icon-plus-sign:before { + content: "\f055"; +} +.icon-minus-sign:before { + content: "\f056"; +} +.icon-remove-sign:before { + content: "\f057"; +} +.icon-ok-sign:before { + content: "\f058"; +} +.icon-question-sign:before { + content: "\f059"; +} +.icon-info-sign:before { + content: "\f05a"; +} +.icon-screenshot:before { + content: "\f05b"; +} +.icon-remove-circle:before { + content: "\f05c"; +} +.icon-ok-circle:before { + content: "\f05d"; +} +.icon-ban-circle:before { + content: "\f05e"; +} +.icon-arrow-left:before { + content: "\f060"; +} +.icon-arrow-right:before { + content: "\f061"; +} +.icon-arrow-up:before { + content: "\f062"; +} +.icon-arrow-down:before { + content: "\f063"; +} +.icon-mail-forward:before, +.icon-share-alt:before { + content: "\f064"; +} +.icon-resize-full:before { + content: "\f065"; +} +.icon-resize-small:before { + content: "\f066"; +} +.icon-plus:before { + content: "\f067"; +} +.icon-minus:before { + content: "\f068"; +} +.icon-asterisk:before { + content: "\f069"; +} +.icon-exclamation-sign:before { + content: "\f06a"; +} +.icon-gift:before { + content: "\f06b"; +} +.icon-leaf:before { + content: "\f06c"; +} +.icon-fire:before { + content: "\f06d"; +} +.icon-eye-open:before { + content: "\f06e"; +} +.icon-eye-close:before { + content: "\f070"; +} +.icon-warning-sign:before { + content: "\f071"; +} +.icon-plane:before { + content: "\f072"; +} +.icon-calendar:before { + content: "\f073"; +} +.icon-random:before { + content: "\f074"; +} +.icon-comment:before { + content: "\f075"; +} +.icon-magnet:before { + content: "\f076"; +} +.icon-chevron-up:before { + content: "\f077"; +} +.icon-chevron-down:before { + content: "\f078"; +} +.icon-retweet:before { + content: "\f079"; +} +.icon-shopping-cart:before { + content: "\f07a"; +} +.icon-folder-close:before { + content: "\f07b"; +} +.icon-folder-open:before { + content: "\f07c"; +} +.icon-resize-vertical:before { + content: "\f07d"; +} +.icon-resize-horizontal:before { + content: "\f07e"; +} +.icon-bar-chart:before { + content: "\f080"; +} +.icon-twitter-sign:before { + content: "\f081"; +} +.icon-facebook-sign:before { + content: "\f082"; +} +.icon-camera-retro:before { + content: "\f083"; +} +.icon-key:before { + content: "\f084"; +} +.icon-gears:before, +.icon-cogs:before { + content: "\f085"; +} +.icon-comments:before { + content: "\f086"; +} +.icon-thumbs-up-alt:before { + content: "\f087"; +} +.icon-thumbs-down-alt:before { + content: "\f088"; +} +.icon-star-half:before { + content: "\f089"; +} +.icon-heart-empty:before { + content: "\f08a"; +} +.icon-signout:before { + content: "\f08b"; +} +.icon-linkedin-sign:before { + content: "\f08c"; +} +.icon-pushpin:before { + content: "\f08d"; +} +.icon-external-link:before { + content: "\f08e"; +} +.icon-signin:before { + content: "\f090"; +} +.icon-trophy:before { + content: "\f091"; +} +.icon-github-sign:before { + content: "\f092"; +} +.icon-upload-alt:before { + content: "\f093"; +} +.icon-lemon:before { + content: "\f094"; +} +.icon-phone:before { + content: "\f095"; +} +.icon-unchecked:before, +.icon-check-empty:before { + content: "\f096"; +} +.icon-bookmark-empty:before { + content: "\f097"; +} +.icon-phone-sign:before { + content: "\f098"; +} +.icon-twitter:before { + content: "\f099"; +} +.icon-facebook:before { + content: "\f09a"; +} +.icon-github:before { + content: "\f09b"; +} +.icon-unlock:before { + content: "\f09c"; +} +.icon-credit-card:before { + content: "\f09d"; +} +.icon-rss:before { + content: "\f09e"; +} +.icon-hdd:before { + content: "\f0a0"; +} +.icon-bullhorn:before { + content: "\f0a1"; +} +.icon-bell:before { + content: "\f0a2"; +} +.icon-certificate:before { + content: "\f0a3"; +} +.icon-hand-right:before { + content: "\f0a4"; +} +.icon-hand-left:before { + content: "\f0a5"; +} +.icon-hand-up:before { + content: "\f0a6"; +} +.icon-hand-down:before { + content: "\f0a7"; +} +.icon-circle-arrow-left:before { + content: "\f0a8"; +} +.icon-circle-arrow-right:before { + content: "\f0a9"; +} +.icon-circle-arrow-up:before { + content: "\f0aa"; +} +.icon-circle-arrow-down:before { + content: "\f0ab"; +} +.icon-globe:before { + content: "\f0ac"; +} +.icon-wrench:before { + content: "\f0ad"; +} +.icon-tasks:before { + content: "\f0ae"; +} +.icon-filter:before { + content: "\f0b0"; +} +.icon-briefcase:before { + content: "\f0b1"; +} +.icon-fullscreen:before { + content: "\f0b2"; +} +.icon-group:before { + content: "\f0c0"; +} +.icon-link:before { + content: "\f0c1"; +} +.icon-cloud:before { + content: "\f0c2"; +} +.icon-beaker:before { + content: "\f0c3"; +} +.icon-cut:before { + content: "\f0c4"; +} +.icon-copy:before { + content: "\f0c5"; +} +.icon-paperclip:before, +.icon-paper-clip:before { + content: "\f0c6"; +} +.icon-save:before { + content: "\f0c7"; +} +.icon-sign-blank:before { + content: "\f0c8"; +} +.icon-reorder:before { + content: "\f0c9"; +} +.icon-list-ul:before { + content: "\f0ca"; +} +.icon-list-ol:before { + content: "\f0cb"; +} +.icon-strikethrough:before { + content: "\f0cc"; +} +.icon-underline:before { + content: "\f0cd"; +} +.icon-table:before { + content: "\f0ce"; +} +.icon-magic:before { + content: "\f0d0"; +} +.icon-truck:before { + content: "\f0d1"; +} +.icon-pinterest:before { + content: "\f0d2"; +} +.icon-pinterest-sign:before { + content: "\f0d3"; +} +.icon-google-plus-sign:before { + content: "\f0d4"; +} +.icon-google-plus:before { + content: "\f0d5"; +} +.icon-money:before { + content: "\f0d6"; +} +.icon-caret-down:before { + content: "\f0d7"; +} +.icon-caret-up:before { + content: "\f0d8"; +} +.icon-caret-left:before { + content: "\f0d9"; +} +.icon-caret-right:before { + content: "\f0da"; +} +.icon-columns:before { + content: "\f0db"; +} +.icon-sort:before { + content: "\f0dc"; +} +.icon-sort-down:before { + content: "\f0dd"; +} +.icon-sort-up:before { + content: "\f0de"; +} +.icon-envelope:before { + content: "\f0e0"; +} +.icon-linkedin:before { + content: "\f0e1"; +} +.icon-rotate-left:before, +.icon-undo:before { + content: "\f0e2"; +} +.icon-legal:before { + content: "\f0e3"; +} +.icon-dashboard:before { + content: "\f0e4"; +} +.icon-comment-alt:before { + content: "\f0e5"; +} +.icon-comments-alt:before { + content: "\f0e6"; +} +.icon-bolt:before { + content: "\f0e7"; +} +.icon-sitemap:before { + content: "\f0e8"; +} +.icon-umbrella:before { + content: "\f0e9"; +} +.icon-paste:before { + content: "\f0ea"; +} +.icon-lightbulb:before { + content: "\f0eb"; +} +.icon-exchange:before { + content: "\f0ec"; +} +.icon-cloud-download:before { + content: "\f0ed"; +} +.icon-cloud-upload:before { + content: "\f0ee"; +} +.icon-user-md:before { + content: "\f0f0"; +} +.icon-stethoscope:before { + content: "\f0f1"; +} +.icon-suitcase:before { + content: "\f0f2"; +} +.icon-bell-alt:before { + content: "\f0f3"; +} +.icon-coffee:before { + content: "\f0f4"; +} +.icon-food:before { + content: "\f0f5"; +} +.icon-file-text-alt:before { + content: "\f0f6"; +} +.icon-building:before { + content: "\f0f7"; +} +.icon-hospital:before { + content: "\f0f8"; +} +.icon-ambulance:before { + content: "\f0f9"; +} +.icon-medkit:before { + content: "\f0fa"; +} +.icon-fighter-jet:before { + content: "\f0fb"; +} +.icon-beer:before { + content: "\f0fc"; +} +.icon-h-sign:before { + content: "\f0fd"; +} +.icon-plus-sign-alt:before { + content: "\f0fe"; +} +.icon-double-angle-left:before { + content: "\f100"; +} +.icon-double-angle-right:before { + content: "\f101"; +} +.icon-double-angle-up:before { + content: "\f102"; +} +.icon-double-angle-down:before { + content: "\f103"; +} +.icon-angle-left:before { + content: "\f104"; +} +.icon-angle-right:before { + content: "\f105"; +} +.icon-angle-up:before { + content: "\f106"; +} +.icon-angle-down:before { + content: "\f107"; +} +.icon-desktop:before { + content: "\f108"; +} +.icon-laptop:before { + content: "\f109"; +} +.icon-tablet:before { + content: "\f10a"; +} +.icon-mobile-phone:before { + content: "\f10b"; +} +.icon-circle-blank:before { + content: "\f10c"; +} +.icon-quote-left:before { + content: "\f10d"; +} +.icon-quote-right:before { + content: "\f10e"; +} +.icon-spinner:before { + content: "\f110"; +} +.icon-circle:before { + content: "\f111"; +} +.icon-mail-reply:before, +.icon-reply:before { + content: "\f112"; +} +.icon-github-alt:before { + content: "\f113"; +} +.icon-folder-close-alt:before { + content: "\f114"; +} +.icon-folder-open-alt:before { + content: "\f115"; +} +.icon-expand-alt:before { + content: "\f116"; +} +.icon-collapse-alt:before { + content: "\f117"; +} +.icon-smile:before { + content: "\f118"; +} +.icon-frown:before { + content: "\f119"; +} +.icon-meh:before { + content: "\f11a"; +} +.icon-gamepad:before { + content: "\f11b"; +} +.icon-keyboard:before { + content: "\f11c"; +} +.icon-flag-alt:before { + content: "\f11d"; +} +.icon-flag-checkered:before { + content: "\f11e"; +} +.icon-terminal:before { + content: "\f120"; +} +.icon-code:before { + content: "\f121"; +} +.icon-reply-all:before { + content: "\f122"; +} +.icon-mail-reply-all:before { + content: "\f122"; +} +.icon-star-half-full:before, +.icon-star-half-empty:before { + content: "\f123"; +} +.icon-location-arrow:before { + content: "\f124"; +} +.icon-crop:before { + content: "\f125"; +} +.icon-code-fork:before { + content: "\f126"; +} +.icon-unlink:before { + content: "\f127"; +} +.icon-question:before { + content: "\f128"; +} +.icon-info:before { + content: "\f129"; +} +.icon-exclamation:before { + content: "\f12a"; +} +.icon-superscript:before { + content: "\f12b"; +} +.icon-subscript:before { + content: "\f12c"; +} +.icon-eraser:before { + content: "\f12d"; +} +.icon-puzzle-piece:before { + content: "\f12e"; +} +.icon-microphone:before { + content: "\f130"; +} +.icon-microphone-off:before { + content: "\f131"; +} +.icon-shield:before { + content: "\f132"; +} +.icon-calendar-empty:before { + content: "\f133"; +} +.icon-fire-extinguisher:before { + content: "\f134"; +} +.icon-rocket:before { + content: "\f135"; +} +.icon-maxcdn:before { + content: "\f136"; +} +.icon-chevron-sign-left:before { + content: "\f137"; +} +.icon-chevron-sign-right:before { + content: "\f138"; +} +.icon-chevron-sign-up:before { + content: "\f139"; +} +.icon-chevron-sign-down:before { + content: "\f13a"; +} +.icon-html5:before { + content: "\f13b"; +} +.icon-css3:before { + content: "\f13c"; +} +.icon-anchor:before { + content: "\f13d"; +} +.icon-unlock-alt:before { + content: "\f13e"; +} +.icon-bullseye:before { + content: "\f140"; +} +.icon-ellipsis-horizontal:before { + content: "\f141"; +} +.icon-ellipsis-vertical:before { + content: "\f142"; +} +.icon-rss-sign:before { + content: "\f143"; +} +.icon-play-sign:before { + content: "\f144"; +} +.icon-ticket:before { + content: "\f145"; +} +.icon-minus-sign-alt:before { + content: "\f146"; +} +.icon-check-minus:before { + content: "\f147"; +} +.icon-level-up:before { + content: "\f148"; +} +.icon-level-down:before { + content: "\f149"; +} +.icon-check-sign:before { + content: "\f14a"; +} +.icon-edit-sign:before { + content: "\f14b"; +} +.icon-external-link-sign:before { + content: "\f14c"; +} +.icon-share-sign:before { + content: "\f14d"; +} +.icon-compass:before { + content: "\f14e"; +} +.icon-collapse:before { + content: "\f150"; +} +.icon-collapse-top:before { + content: "\f151"; +} +.icon-expand:before { + content: "\f152"; +} +.icon-euro:before, +.icon-eur:before { + content: "\f153"; +} +.icon-gbp:before { + content: "\f154"; +} +.icon-dollar:before, +.icon-usd:before { + content: "\f155"; +} +.icon-rupee:before, +.icon-inr:before { + content: "\f156"; +} +.icon-yen:before, +.icon-jpy:before { + content: "\f157"; +} +.icon-renminbi:before, +.icon-cny:before { + content: "\f158"; +} +.icon-won:before, +.icon-krw:before { + content: "\f159"; +} +.icon-bitcoin:before, +.icon-btc:before { + content: "\f15a"; +} +.icon-file:before { + content: "\f15b"; +} +.icon-file-text:before { + content: "\f15c"; +} +.icon-sort-by-alphabet:before { + content: "\f15d"; +} +.icon-sort-by-alphabet-alt:before { + content: "\f15e"; +} +.icon-sort-by-attributes:before { + content: "\f160"; +} +.icon-sort-by-attributes-alt:before { + content: "\f161"; +} +.icon-sort-by-order:before { + content: "\f162"; +} +.icon-sort-by-order-alt:before { + content: "\f163"; +} +.icon-thumbs-up:before { + content: "\f164"; +} +.icon-thumbs-down:before { + content: "\f165"; +} +.icon-youtube-sign:before { + content: "\f166"; +} +.icon-youtube:before { + content: "\f167"; +} +.icon-xing:before { + content: "\f168"; +} +.icon-xing-sign:before { + content: "\f169"; +} +.icon-youtube-play:before { + content: "\f16a"; +} +.icon-dropbox:before { + content: "\f16b"; +} +.icon-stackexchange:before { + content: "\f16c"; +} +.icon-instagram:before { + content: "\f16d"; +} +.icon-flickr:before { + content: "\f16e"; +} +.icon-adn:before { + content: "\f170"; +} +.icon-bitbucket:before { + content: "\f171"; +} +.icon-bitbucket-sign:before { + content: "\f172"; +} +.icon-tumblr:before { + content: "\f173"; +} +.icon-tumblr-sign:before { + content: "\f174"; +} +.icon-long-arrow-down:before { + content: "\f175"; +} +.icon-long-arrow-up:before { + content: "\f176"; +} +.icon-long-arrow-left:before { + content: "\f177"; +} +.icon-long-arrow-right:before { + content: "\f178"; +} +.icon-apple:before { + content: "\f179"; +} +.icon-windows:before { + content: "\f17a"; +} +.icon-android:before { + content: "\f17b"; +} +.icon-linux:before { + content: "\f17c"; +} +.icon-dribbble:before { + content: "\f17d"; +} +.icon-skype:before { + content: "\f17e"; +} +.icon-foursquare:before { + content: "\f180"; +} +.icon-trello:before { + content: "\f181"; +} +.icon-female:before { + content: "\f182"; +} +.icon-male:before { + content: "\f183"; +} +.icon-gittip:before { + content: "\f184"; +} +.icon-sun:before { + content: "\f185"; +} +.icon-moon:before { + content: "\f186"; +} +.icon-archive:before { + content: "\f187"; +} +.icon-bug:before { + content: "\f188"; +} +.icon-vk:before { + content: "\f189"; +} +.icon-weibo:before { + content: "\f18a"; +} +.icon-renren:before { + content: "\f18b"; +} diff --git a/assets/stylesheets/jquery.gridster.css b/assets/stylesheets/jquery.gridster.css new file mode 100644 index 0000000..d512484 --- /dev/null +++ b/assets/stylesheets/jquery.gridster.css @@ -0,0 +1,57 @@ +/*! gridster.js - v0.1.0 - 2012-08-14 +* http://gridster.net/ +* Copyright (c) 2012 ducksboard; Licensed MIT */ + +.gridster { + position:relative; +} + +.gridster > * { + margin: 0 auto; + -webkit-transition: height .4s; + -moz-transition: height .4s; + -o-transition: height .4s; + -ms-transition: height .4s; + transition: height .4s; +} + +.gridster .gs_w{ + z-index: 2; + position: absolute; +} + +.ready .gs_w:not(.preview-holder) { + -webkit-transition: opacity .3s, left .3s, top .3s; + -moz-transition: opacity .3s, left .3s, top .3s; + -o-transition: opacity .3s, left .3s, top .3s; + transition: opacity .3s, left .3s, top .3s; +} + +.gridster .preview-holder { + z-index: 1; + position: absolute; + background-color: #fff; + border-color: #fff; + opacity: 0.3; +} + +.gridster .player-revert { + z-index: 10!important; + -webkit-transition: left .3s, top .3s!important; + -moz-transition: left .3s, top .3s!important; + -o-transition: left .3s, top .3s!important; + transition: left .3s, top .3s!important; +} + +.gridster .dragging { + z-index: 10!important; + -webkit-transition: all 0s !important; + -moz-transition: all 0s !important; + -o-transition: all 0s !important; + transition: all 0s !important; +} + +/* Uncomment this if you set helper : "clone" in draggable options */ +/*.gridster .player { + opacity:0; +}*/ \ No newline at end of file diff --git a/config.ru b/config.ru new file mode 100644 index 0000000..67af9aa --- /dev/null +++ b/config.ru @@ -0,0 +1,23 @@ +require 'dashing' + +configure do + if ENV.has_key?('PIX_AUTH_TOKEN') + set :auth_token, ENV['PIX_AUTH_TOKEN'] + else + set :auth_token, 'ie2Aex6ooLi4usi0ahngaht' + end + set :default_dashboard, 'default' + + helpers do + def protected! + # Put any authentication code you want in here. + # This method is run before accessing any resource. + end + end +end + +map Sinatra::Application.assets_prefix do + run Sinatra::Application.sprockets +end + +run Sinatra::Application diff --git a/credentials_example b/credentials_example new file mode 100644 index 0000000..7982c8f --- /dev/null +++ b/credentials_example @@ -0,0 +1,5 @@ +export "GITHUB_AUTH_TOKEN=" +export "TWITTER_CONSUMER_KEY=" +export "TWITTER_CONSUMER_SECRET=" +export "TWITTER_ACCESS_TOKEN=" +export "TWITTER_ACCESS_TOKEN_SECRET=" diff --git a/dashboards/dashboard.erb b/dashboards/dashboard.erb new file mode 100644 index 0000000..ba450cf --- /dev/null +++ b/dashboards/dashboard.erb @@ -0,0 +1,13 @@ +
  • +
    +
  • + +
  • +
    +
  • +
  • +
    +
  • +
  • +
    +
  • diff --git a/dashboards/default.erb b/dashboards/default.erb new file mode 100644 index 0000000..d5441b8 --- /dev/null +++ b/dashboards/default.erb @@ -0,0 +1,15 @@ + + +<% content_for :title do %>LEAP Dashboard for Laptops<% end %> +
    +
      + <%= erb :'dashboard' %> +
    +
    diff --git a/dashboards/layout.erb b/dashboards/layout.erb new file mode 100644 index 0000000..5c8dfb8 --- /dev/null +++ b/dashboards/layout.erb @@ -0,0 +1,51 @@ + + + + + + + + + <%= yield_content(:title) %> + + + + + + + + + + + + +
    + <%= yield %> +
    + + <% if development? %> +
    +

    Paste the following at the top of <%= params[:dashboard] %>.erb

    + +
    + Save this layout + <% end %> + + diff --git a/jobs/jenkins_build_status.rb b/jobs/jenkins_build_status.rb new file mode 100644 index 0000000..267b8da --- /dev/null +++ b/jobs/jenkins_build_status.rb @@ -0,0 +1,73 @@ +require 'net/http' +require 'json' + +JENKINS_URI = "https://jenkins.leap.se/" + +JENKINS_AUTH = { + 'name' => 'nobody', + 'password' => 'nopw' +} + +SCHEDULER.every '10s' do + + json = getFromJenkins(JENKINS_URI + 'api/json?pretty=true') + + failedJobs = Array.new + puts failedJobs + succeededJobs = Array.new + array = json['jobs'] + array.each { + |job| + + next if job['color'] == 'disabled' + next if job['color'] == 'notbuilt' + next if job['color'] == 'blue' + next if job['color'] == 'blue_anime' + + jobStatus = ''; + if job['color'] == 'yellow' || job['color'] == 'yellow_anime' + jobStatus = getFromJenkins(job['url'] + 'lastUnstableBuild/api/json') + elsif job['color'] == 'aborted' || job['color'] == 'aborted_anime' + jobStatus = getFromJenkins(job['url'] + 'lastUnsuccessfulBuild/api/json') + else + jobStatus = getFromJenkins(job['url'] + 'lastFailedBuild/api/json') + end + + culprits = jobStatus['culprits'] + + culpritName = getNameFromCulprits(culprits) + if culpritName != '' + culpritName = culpritName.partition('<').first + end + + failedJobs.push({ label: job['name'], value: culpritName}) + } + + failed = failedJobs.size > 0 + + send_event('jenkinsBuildStatus', { failedJobs: failedJobs, succeededJobs: succeededJobs, failed: failed }) +end + +def getFromJenkins(path) + + uri = URI.parse(path) + + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true + request = Net::HTTP::Get.new(uri.request_uri) + ##if JENKINS_AUTH['name'] + # request.basic_auth(JENKINS_AUTH['name'], JENKINS_AUTH['password']) + #end + response = http.request(request) + + json = JSON.parse(response.body) + return json +end + +def getNameFromCulprits(culprits) + culprits.each { + |culprit| + return culprit['fullName'] + } + return '' +end diff --git a/jobs/nagios.rb b/jobs/nagios.rb new file mode 100644 index 0000000..4186d22 --- /dev/null +++ b/jobs/nagios.rb @@ -0,0 +1,90 @@ +SCHEDULER.every '10s' do + require 'bundler/setup' + require 'nagiosharder' + require 'pp' + + environments = { + cdev: { + domain: 'cdev.bitmask.i', + query_url: 'https://unstable.bitmask.net/cgi-bin/nagios3/', + home_url: 'https://unstable.bitmask.net/nagios3/', + username: 'nagiosadmin', + password: ENV['UNSTABLE_PASS'] + }, + dev: { + domain: 'dev.bitmask.i', + query_url: 'https://unstable.bitmask.net/cgi-bin/nagios3/', + home_url: 'https://unstable.bitmask.net/nagios3/', + username: 'nagiosadmin', + password: ENV['UNSTABLE_PASS'] + }, + unstable: { + domain: 'unstable.bitmask.i', + query_url: 'https://unstable.bitmask.net/cgi-bin/nagios3/', + home_url: 'https://unstable.bitmask.net/nagios3/', + username: 'nagiosadmin', + password: ENV['UNSTABLE_PASS'] + }, + } + + environments.each do |key, env| + nag = NagiosHarder::Site.new(env[:query_url], env[:username], env[:password],'3','iso8601') + unacked = nag.service_status( + :host_status_types => [:all], + :service_status_types => [:warning, :critical, :unknown], + :service_props => [:no_scheduled_downtime, :state_unacknowledged] + ) + + critical_count = 0 + critical_services = Array.new + warning_count = 0 + warning_services = Array.new + unknown_count = 0 + unknown_services = Array.new + + unacked.each do |alert| + next if ! alert["host"].include? env[:domain] + next if ! tried_at_maximum(alert["attempts"]) + + if alert["status"].eql? "CRITICAL" + critical_count += 1 + critical_services << alert["service"] + elsif alert["status"].eql? "WARNING" + warning_count += 1 + warning_services << alert["service"] + elsif alert["status"].eql? "UNKNOWN" + unknown_count += 1 + unknown_services << alert["service"] + end + end + + if ['cdev.bitmask.i', 'dev.bitmask.i', 'unstable.bitmask.i'].include? env[:domain] + status = critical_count + warning_count + unknown_count > 0 ? "gray" : "green" + else + status = critical_count > 0 ? "red" : (warning_count + unknown_count > 0 ? "yellow" : "green") + end + + # nagiosharder may not alert us to a problem querying nagios. + # If no problems found check that we fetch service status and + # expect to find more than 0 entries. + if critical_count == 0 and warning_count == 0 and unknown_count == 0 + if nag.service_status.length == 0 + status = "error" + end + end + + puts key.to_s + ": " + critical_count.to_s + puts critical_services.join(", ") + puts + + send_event('nagios-' + key.to_s, { + criticals: critical_count, critical_services: critical_services, + warnings: warning_count, warning_services: warning_services, + unknown: unknown_count, unknown_services: unknown_services, + status: status, nagios_url: env[:home_url]}) + end +end + +def tried_at_maximum(attempts) + return attempts ? attempts.split("/").uniq.size == 1 : false +end diff --git a/lib/ccmenu.rb b/lib/ccmenu.rb new file mode 100644 index 0000000..90257f5 --- /dev/null +++ b/lib/ccmenu.rb @@ -0,0 +1,57 @@ +require 'crack' +require 'open-uri' +require 'json' + +class CCMenu + + def initialize(url) + @data = Crack::XML.parse(open(url))['Projects']['Project'] + @data=@data.select{ |i| i['name'][/^[^\:\:]*\:\:[^\:\:]*$/]} + puts @data + end + + def json + @data.to_json + end + + def status + overall = 'Success' + @data.each do |step| + if step['lastBuildStatus'] != 'Success' + overall = step['lastBuildStatus'] + end + end + return overall + end + def failed + list = Array.new + @data.each do |step| + puts step + list << step['name'] if step['lastBuildStatus'] == 'Failure' + end + return list + end + + def num_total + @data.length + end + + def num_failed + self.failed.length + end + + def color + case status + when 'Failure' then 'red' + when 'Exception' then 'orange' + when 'Unknown' then 'orange' + else 'green' + end + end + + + def data + @data + end + +end diff --git a/lib/github.rb b/lib/github.rb new file mode 100644 index 0000000..77d765e --- /dev/null +++ b/lib/github.rb @@ -0,0 +1,123 @@ +require 'date' +require 'open-uri' +require 'json' + +class Github + + def initialize(orgname) + url='https://api.github.com/orgs/'+orgname + @headers={'User-Agent' => 'LEAP Dashboard'} + if ENV.has_key?('GITHUB_AUTH_TOKEN') + @headers['Authorization'] = "token "+ENV['GITHUB_AUTH_TOKEN'] + end + @org=JSON.parse(open(url,@headers).read) + @repos=JSON.parse(open(@org['repos_url'],@headers).read) + end + + def repocount + @repos.count + end + + def prcount + count=0 + @repos.each do |r| + count+=JSON.parse(open(r['url']+'/pulls',@headers).read).count + end + return count + end + + def recent_open_issues + count=0 + @repos.each do |r| + url=r['url']+'/issues?state=open&since='+(Time.new().to_datetime << 1).to_time.strftime("%Y-%m-%dT%H:%M:%SZ") + count+=JSON.parse(open(url,@headers).read).count + end + return count + end + def recent_issues + count=0 + @repos.each do |r| + url=r['url']+'/issues?state=all&since='+(Time.new().to_datetime << 1).to_time.strftime("%Y-%m-%dT%H:%M:%SZ") + count+=JSON.parse(open(url,@headers).read).count + end + return count + end + def open_pull_requests + count=0 + @repos.each do |r| + url=r['url']+'/pulls?state=open' + count+=JSON.parse(open(url,@headers).read).count + end + return count + end + def pull_requests + count=0 + @repos.each do |r| + url=r['url']+'/pulls?state=all' + count+=JSON.parse(open(url,@headers).read).count + end + return count + end + def forks + count=0 + @repos.each do |r| + url=r['url']+'/forks' + count+=JSON.parse(open(url,@headers).read).count + end + return count + end + def issues_in_development + count=0 + @repos.each do |r| + url=r['url']+'/issues?labels=2%20-%20Development' + count+=JSON.parse(open(url,@headers).read).count + end + return count + end + def prlist + prlist=[] + @repos.each do |r| + JSON.parse(open(r['url']+'/pulls',@headers).read).each do |pr| + prlist<<{'label'=>pr['title'], 'pr_url'=>pr['html_url']} + end + end + return prlist + end + + def stargazers + @repos.inject(0) {|sum,hash| sum + hash['stargazers_count']} + end + def epic_issues + count=0 + @repos.each do |r| + url=r['url']+'/issues?labels=UA%20multiuser' + count+=JSON.parse(open(url,@headers).read).count + end + return count + end + def all_epic_issues + count=0 + @repos.each do |r| + url=r['url']+'/issues?labels=UA%20multiuser&state=all' + count+=JSON.parse(open(url,@headers).read).count + end + return count + end + + def action_items + issues=[] + @repos.each do |r| + url=r['url']+'/issues?labels=Action+item' + JSON.parse(open(url,@headers).read).each do |issue| + title=issue['title'] + if issue.has_key?('assignee') and not issue['assignee'].nil? + assignee=issue['assignee']['login'] + else + assignee='None' + end + issues<<{'label' => title, 'value' => assignee} + end + end + issues + end +end diff --git a/lib/weekly_goals.rb b/lib/weekly_goals.rb new file mode 100644 index 0000000..1a263bc --- /dev/null +++ b/lib/weekly_goals.rb @@ -0,0 +1,22 @@ +require 'time' + +class WeeklyGoals + def initialize + @goals = [ + "Remove docker: Finish the load test report comparing the new and the old archicteture", + "Modularize stylesheet: ", + "Fix bug: Error running functionals on vagrant", + "Fix bug: Fix Feedback Form", + "Fix bug: Minify JS"] + end + + def rotating_goal + min = Time.new.min + idx = min % @goals.length + @goals[idx] + end + + def all_goals + @goals + end +end diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..1a8f334 --- /dev/null +++ b/public/404.html @@ -0,0 +1,26 @@ + + + + This Dashboard doesn't exist. + + + + + +
    +

    Drats! That Dashboard doesn't exist.

    +

    You may have mistyped the address or the page may have moved.

    +
    + + \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..29a408f Binary files /dev/null and b/public/favicon.ico differ diff --git a/widgets/ccmenu/ccmenu.coffee b/widgets/ccmenu/ccmenu.coffee new file mode 100644 index 0000000..7c22cd6 --- /dev/null +++ b/widgets/ccmenu/ccmenu.coffee @@ -0,0 +1,9 @@ +class Dashing.Ccmenu extends Dashing.Widget + + onData: (data) -> + if data.color + # clear existing "status-*" classes + $(@get('node')).attr 'class', (i,c) -> + c.replace /\bstatus-\S+/g, '' + # add new class + $(@get('node')).addClass "status-#{data.color}" diff --git a/widgets/ccmenu/ccmenu.html b/widgets/ccmenu/ccmenu.html new file mode 100644 index 0000000..0508028 --- /dev/null +++ b/widgets/ccmenu/ccmenu.html @@ -0,0 +1,7 @@ + +
      +
    • +

      +
    • +
    +

    diff --git a/widgets/ccmenu/ccmenu.scss b/widgets/ccmenu/ccmenu.scss new file mode 100644 index 0000000..fe3d3a3 --- /dev/null +++ b/widgets/ccmenu/ccmenu.scss @@ -0,0 +1,44 @@ +// ---------------------------------------------------------------------------- +// Sass declarations +// ---------------------------------------------------------------------------- +$title-color: rgba(255, 255, 255, 0.7); +$green-color: #50BA5B; +$red-color: #D93C38; +$orange-color: #F6A41C; + +// ---------------------------------------------------------------------------- +// Widget-ccmenu styles +// ---------------------------------------------------------------------------- +.widget-ccmenu { + vertical-align: top; + + .title { + color: $title-color; + } + + .title-link { + font-size: 30px; + + &:hover { + color: rgba(255, 255, 255, 0.7); + } + } + + li { + margin-bottom: 5px; + } + .updated-at { + color: rgba(0, 0, 0, 0.3); + } +} + +.status-green { + background-color: $green-color; +} +.status-red { + background-color: $red-color; +} + +.status-orange { + background-color: $orange-color; +} diff --git a/widgets/clock/clock.coffee b/widgets/clock/clock.coffee new file mode 100644 index 0000000..cd6b116 --- /dev/null +++ b/widgets/clock/clock.coffee @@ -0,0 +1,18 @@ +class Dashing.Clock extends Dashing.Widget + + ready: -> + setInterval(@startTime, 500) + + startTime: => + today = new Date() + + h = today.getHours() + m = today.getMinutes() + s = today.getSeconds() + m = @formatTime(m) + s = @formatTime(s) + @set('time', h + ":" + m + ":" + s) + @set('date', today.toDateString()) + + formatTime: (i) -> + if i < 10 then "0" + i else i \ No newline at end of file diff --git a/widgets/clock/clock.html b/widgets/clock/clock.html new file mode 100644 index 0000000..06759d4 --- /dev/null +++ b/widgets/clock/clock.html @@ -0,0 +1,2 @@ +

    +

    \ No newline at end of file diff --git a/widgets/clock/clock.scss b/widgets/clock/clock.scss new file mode 100644 index 0000000..19e91bf --- /dev/null +++ b/widgets/clock/clock.scss @@ -0,0 +1,13 @@ +// ---------------------------------------------------------------------------- +// Sass declarations +// ---------------------------------------------------------------------------- +$background-color: #dc5945; + +// ---------------------------------------------------------------------------- +// Widget-clock styles +// ---------------------------------------------------------------------------- +.widget-clock { + + background-color: $background-color; + +} diff --git a/widgets/comments/comments.coffee b/widgets/comments/comments.coffee new file mode 100644 index 0000000..ff659ec --- /dev/null +++ b/widgets/comments/comments.coffee @@ -0,0 +1,24 @@ +class Dashing.Comments extends Dashing.Widget + + @accessor 'quote', -> + "“#{@get('current_comment')?.body}”" + + ready: -> + @currentIndex = 0 + @commentElem = $(@node).find('.comment-container') + @nextComment() + @startCarousel() + + onData: (data) -> + @currentIndex = 0 + + startCarousel: -> + setInterval(@nextComment, 8000) + + nextComment: => + comments = @get('comments') + if comments + @commentElem.fadeOut => + @currentIndex = (@currentIndex + 1) % comments.length + @set 'current_comment', comments[@currentIndex] + @commentElem.fadeIn() diff --git a/widgets/comments/comments.html b/widgets/comments/comments.html new file mode 100644 index 0000000..7c580be --- /dev/null +++ b/widgets/comments/comments.html @@ -0,0 +1,7 @@ +

    +
    +

    +

    +
    + +

    diff --git a/widgets/comments/comments.scss b/widgets/comments/comments.scss new file mode 100644 index 0000000..2ace30e --- /dev/null +++ b/widgets/comments/comments.scss @@ -0,0 +1,33 @@ +// ---------------------------------------------------------------------------- +// Sass declarations +// ---------------------------------------------------------------------------- +$background-color: #eb9c3c; + +$title-color: rgba(255, 255, 255, 0.7); +$moreinfo-color: rgba(255, 255, 255, 0.7); + +// ---------------------------------------------------------------------------- +// Widget-comment styles +// ---------------------------------------------------------------------------- +.widget-comments { + + background-color: $background-color; + + .title { + color: $title-color; + margin-bottom: 15px; + } + + .name { + padding-left: 5px; + } + + .comment-container { + display: none; + } + + .more-info { + color: $moreinfo-color; + } + +} diff --git a/widgets/github_pr/github_pr.coffee b/widgets/github_pr/github_pr.coffee new file mode 100644 index 0000000..43f4fc4 --- /dev/null +++ b/widgets/github_pr/github_pr.coffee @@ -0,0 +1,6 @@ +class Dashing.Github_pr extends Dashing.Widget + ready: -> + if @get('unordered') + $(@node).find('ol').remove() + else + $(@node).find('ul').remove() diff --git a/widgets/github_pr/github_pr.html b/widgets/github_pr/github_pr.html new file mode 100644 index 0000000..5d4d8d8 --- /dev/null +++ b/widgets/github_pr/github_pr.html @@ -0,0 +1,10 @@ +

    + +
      +
    • + +
    • +
    + +

    +

    diff --git a/widgets/github_pr/github_pr.scss b/widgets/github_pr/github_pr.scss new file mode 100644 index 0000000..e4ad010 --- /dev/null +++ b/widgets/github_pr/github_pr.scss @@ -0,0 +1,57 @@ +// ---------------------------------------------------------------------------- +// Sass declarations +// ---------------------------------------------------------------------------- +$background-color: #178CA6; +$value-color: #fff; + +$title-color: #fff; +$label-color: rgba(255, 255, 255, 0.7); +$moreinfo-color: rgba(255, 255, 255, 0.7); + +// ---------------------------------------------------------------------------- +// Widget-github_pr styles +// ---------------------------------------------------------------------------- +.widget-github-pr { + + background-color: $background-color; + vertical-align: top; + + .title { + color: $title-color; + } + + ol, ul { + margin: 0 15px; + text-align: left; + color: $label-color; + } + + ol { + list-style-position: inside; + } + + li { + margin-bottom: 5px; + } + + .github_pr-nostyle { + list-style: none; + } + + .label-link { + color: $value-color; + + &:hover { + color: rgba(255, 255, 255, 0.7); + } + } + + .updated-at { + color: rgba(0, 0, 0, 0.3); + } + + .more-info { + color: $moreinfo-color; + } + +} diff --git a/widgets/graph/graph.coffee b/widgets/graph/graph.coffee new file mode 100644 index 0000000..a54de08 --- /dev/null +++ b/widgets/graph/graph.coffee @@ -0,0 +1,36 @@ +class Dashing.Graph extends Dashing.Widget + + @accessor 'current', -> + return @get('displayedValue') if @get('displayedValue') + points = @get('points') + if points + points[points.length - 1].y + + ready: -> + container = $(@node).parent() + # Gross hacks. Let's fix this. + width = (Dashing.widget_base_dimensions[0] * container.data("sizex")) + Dashing.widget_margins[0] * 2 * (container.data("sizex") - 1) + height = (Dashing.widget_base_dimensions[1] * container.data("sizey")) + @graph = new Rickshaw.Graph( + element: @node + width: width + height: height + renderer: @get("graphtype") + series: [ + { + color: "#fff", + data: [{x:0, y:0}] + } + ] + ) + + @graph.series[0].data = @get('points') if @get('points') + + x_axis = new Rickshaw.Graph.Axis.X(graph: @graph) + y_axis = new Rickshaw.Graph.Axis.Y(graph: @graph, tickFormat: Rickshaw.Fixtures.Number.formatKMBT) + @graph.render() + + onData: (data) -> + if @graph + @graph.series[0].data = data.points + @graph.render() diff --git a/widgets/graph/graph.html b/widgets/graph/graph.html new file mode 100644 index 0000000..456dd0f --- /dev/null +++ b/widgets/graph/graph.html @@ -0,0 +1,5 @@ +

    + +

    + +

    diff --git a/widgets/graph/graph.scss b/widgets/graph/graph.scss new file mode 100644 index 0000000..d1d7534 --- /dev/null +++ b/widgets/graph/graph.scss @@ -0,0 +1,65 @@ +// ---------------------------------------------------------------------------- +// Sass declarations +// ---------------------------------------------------------------------------- +$background-color: #178CA6; + +$title-color: rgba(255, 255, 255, 0.7); +$moreinfo-color: rgba(255, 255, 255, 0.3); +$tick-color: rgba(0, 0, 0, 0.4); + + +// ---------------------------------------------------------------------------- +// Widget-graph styles +// ---------------------------------------------------------------------------- +.widget-graph { + + background-color: $background-color; + position: relative; + + + svg { + position: absolute; + opacity: 0.4; + fill-opacity: 0.4; + left: 0px; + top: 0px; + } + + .title, .value { + position: relative; + z-index: 99; + } + + .title { + color: $title-color; + } + + .more-info { + color: $moreinfo-color; + font-weight: 600; + font-size: 20px; + margin-top: 0; + } + + .x_tick { + position: absolute; + bottom: 0; + .title { + font-size: 20px; + color: $tick-color; + opacity: 0.5; + padding-bottom: 3px; + } + } + + .y_ticks { + font-size: 20px; + fill: $tick-color; + fill-opacity: 1; + } + + .domain { + display: none; + } + +} diff --git a/widgets/iframe/iframe.coffee b/widgets/iframe/iframe.coffee new file mode 100644 index 0000000..e674e6b --- /dev/null +++ b/widgets/iframe/iframe.coffee @@ -0,0 +1,7 @@ +class Dashing.Iframe extends Dashing.Widget + +ready: -> + $(@node).find(".iframe").attr('src', @get('src')) + +onData: (data) -> + $(@node).find(".iframe").attr('src', data.src) diff --git a/widgets/iframe/iframe.html b/widgets/iframe/iframe.html new file mode 100644 index 0000000..c626d10 --- /dev/null +++ b/widgets/iframe/iframe.html @@ -0,0 +1 @@ + diff --git a/widgets/iframe/iframe.scss b/widgets/iframe/iframe.scss new file mode 100644 index 0000000..6827a1b --- /dev/null +++ b/widgets/iframe/iframe.scss @@ -0,0 +1,8 @@ +.widget-iframe { + padding: 3px 0px 0px 0px !important; + + iframe { + width: 100%; + height: 100%; + } +} diff --git a/widgets/image/image.coffee b/widgets/image/image.coffee new file mode 100644 index 0000000..c3892c0 --- /dev/null +++ b/widgets/image/image.coffee @@ -0,0 +1,9 @@ +class Dashing.Image extends Dashing.Widget + + ready: -> + # This is fired when the widget is done being rendered + + onData: (data) -> + # Handle incoming data + # You can access the html node of this widget with `@node` + # Example: $(@node).fadeOut().fadeIn() will make the node flash each time data comes in. diff --git a/widgets/image/image.html b/widgets/image/image.html new file mode 100644 index 0000000..41a88eb --- /dev/null +++ b/widgets/image/image.html @@ -0,0 +1 @@ + diff --git a/widgets/image/image.scss b/widgets/image/image.scss new file mode 100644 index 0000000..0b1a316 --- /dev/null +++ b/widgets/image/image.scss @@ -0,0 +1,13 @@ +// ---------------------------------------------------------------------------- +// Sass declarations +// ---------------------------------------------------------------------------- +$background-color: #4b4b4b; + +// ---------------------------------------------------------------------------- +// Widget-image styles +// ---------------------------------------------------------------------------- +.widget-image { + + background-color: $background-color; + +} diff --git a/widgets/jenkins_build_status/jenkins_build_status.coffee b/widgets/jenkins_build_status/jenkins_build_status.coffee new file mode 100644 index 0000000..d7d8630 --- /dev/null +++ b/widgets/jenkins_build_status/jenkins_build_status.coffee @@ -0,0 +1,28 @@ +class Dashing.JenkinsBuildStatus extends Dashing.Widget + + lastPlayed: 0 + timeBetweenSounds: 300000 + + onData: (data) -> + if data.failed + $(@node).find('div.build-failed').show() + $(@node).find('div.build-succeeded').hide() + $(@node).css("background-color", "red") + + if 'speechSynthesis' of window + @playSoundForUser data.failedJobs[0].value if Date.now() - @lastPlayed > @timeBetweenSounds + else + $(@node).find('div.build-failed').hide() + $(@node).find('div.build-succeeded').show() + $(@node).css("background-color", "#12b0c5") + + playSoundForUser: (user) -> + @lastPlayed = Date.now() + texts = ["#{user} has broken the build.", "The build is broken by #{user}", "#{user} is great, but lacks some programming skills", "Oops, I did it again."] + textNr = Math.floor((Math.random() * texts.length)); + @playSound texts[textNr] + + playSound: (text) -> + msg = new SpeechSynthesisUtterance(text) + msg.voice = speechSynthesis.getVoices()[0] + speechSynthesis.speak msg \ No newline at end of file diff --git a/widgets/jenkins_build_status/jenkins_build_status.html b/widgets/jenkins_build_status/jenkins_build_status.html new file mode 100644 index 0000000..472bc73 --- /dev/null +++ b/widgets/jenkins_build_status/jenkins_build_status.html @@ -0,0 +1,16 @@ +
    +

    FAILED

    +
      +
    • +
      +
      +
    • +
    +
    + +
    +

    All builds are successful

    + +
    + +

    diff --git a/widgets/jenkins_build_status/jenkins_build_status.scss b/widgets/jenkins_build_status/jenkins_build_status.scss new file mode 100644 index 0000000..5e1eff1 --- /dev/null +++ b/widgets/jenkins_build_status/jenkins_build_status.scss @@ -0,0 +1,56 @@ +// ---------------------------------------------------------------------------- +// Sass declarations +// ---------------------------------------------------------------------------- +$background-color: #ec663c; +$title-color: rgba(255, 255, 255, 0.7); +$label-color: rgba(255, 255, 255, 0.7); +$value-color: #fff; + +// ---------------------------------------------------------------------------- +// Widget-text styles +// ---------------------------------------------------------------------------- +.widget-jenkins-build-status { + + background-color: $background-color; + + .title { + color: $title-color; + } + .updated-at { + color: rgba(255, 255, 255, 0.7); + } + + ol, ul { + margin: 0 15px; + text-align: left; + color: $label-color; + } + + li { + margin-bottom: 5px; + font-size: 40px; + } + + .label { + color: $label-color; + } + + .value { + margin-left: 12px; + font-weight: 600; + color: $value-color; + } + + .updated-at { + color: rgba(0, 0, 0, 0.3); + } + + .build-failed { + display: none; + } + + .fa { + font-size: 10em; + color: $label-color; + } +} diff --git a/widgets/list/list.coffee b/widgets/list/list.coffee new file mode 100644 index 0000000..0028073 --- /dev/null +++ b/widgets/list/list.coffee @@ -0,0 +1,6 @@ +class Dashing.List extends Dashing.Widget + ready: -> + if @get('unordered') + $(@node).find('ol').remove() + else + $(@node).find('ul').remove() diff --git a/widgets/list/list.html b/widgets/list/list.html new file mode 100644 index 0000000..07e0471 --- /dev/null +++ b/widgets/list/list.html @@ -0,0 +1,18 @@ +

    + +
      +
    1. + + +
    2. +
    + +
      +
    • + + +
    • +
    + +

    +

    diff --git a/widgets/list/list.scss b/widgets/list/list.scss new file mode 100644 index 0000000..1d2bdb0 --- /dev/null +++ b/widgets/list/list.scss @@ -0,0 +1,60 @@ +// ---------------------------------------------------------------------------- +// Sass declarations +// ---------------------------------------------------------------------------- +$background-color: #178CA6; +$value-color: #fff; + +$title-color: rgba(255, 255, 255, 0.7); +$label-color: rgba(255, 255, 255, 0.7); +$moreinfo-color: rgba(255, 255, 255, 0.7); + +// ---------------------------------------------------------------------------- +// Widget-list styles +// ---------------------------------------------------------------------------- +.widget-list { + + background-color: $background-color; + vertical-align: top; + + .title { + color: $title-color; + } + + ol, ul { + margin: 0 15px; + text-align: left; + color: $label-color; + } + + ol { + list-style-position: inside; + } + + li { + margin-bottom: 5px; + } + + .list-nostyle { + list-style: none; + } + + .label { + color: $label-color; + } + + .value { + float: right; + margin-left: 12px; + font-weight: 600; + color: $value-color; + } + + .updated-at { + color: rgba(0, 0, 0, 0.3); + } + + .more-info { + color: $moreinfo-color; + } + +} diff --git a/widgets/meter/meter.coffee b/widgets/meter/meter.coffee new file mode 100644 index 0000000..b7b3aa8 --- /dev/null +++ b/widgets/meter/meter.coffee @@ -0,0 +1,16 @@ +class Dashing.Meter extends Dashing.Widget + + @accessor 'value', Dashing.AnimatedValue + + constructor: -> + super + @observe 'max', (max) -> + $(@node).find(".meter").trigger('configure', {'max': max}) + @observe 'min', (min) -> + $(@node).find(".meter").trigger('configure', {'min': min}) + + ready: -> + meter = $(@node).find(".meter") + meter.attr("data-bgcolor", meter.css("background-color")) + meter.attr("data-fgcolor", meter.css("color")) + meter.knob() diff --git a/widgets/meter/meter.html b/widgets/meter/meter.html new file mode 100644 index 0000000..16f1f06 --- /dev/null +++ b/widgets/meter/meter.html @@ -0,0 +1,7 @@ +

    + + + +

    + +

    diff --git a/widgets/meter/meter.scss b/widgets/meter/meter.scss new file mode 100644 index 0000000..da9ff0b --- /dev/null +++ b/widgets/meter/meter.scss @@ -0,0 +1,35 @@ +// ---------------------------------------------------------------------------- +// Sass declarations +// ---------------------------------------------------------------------------- +$background-color: #9c4274; + +$title-color: rgba(255, 255, 255, 0.7); +$moreinfo-color: rgba(255, 255, 255, 0.3); + +$meter-background: darken($background-color, 15%); + +// ---------------------------------------------------------------------------- +// Widget-meter styles +// ---------------------------------------------------------------------------- +.widget-meter { + + background-color: $background-color; + + input.meter { + background-color: $meter-background; + color: #fff; + } + + .title { + color: $title-color; + } + + .more-info { + color: $moreinfo-color; + } + + .updated-at { + color: rgba(0, 0, 0, 0.3); + } + +} diff --git a/widgets/nagios/nagios.coffee b/widgets/nagios/nagios.coffee new file mode 100644 index 0000000..72f47e5 --- /dev/null +++ b/widgets/nagios/nagios.coffee @@ -0,0 +1,9 @@ +class Dashing.Nagios extends Dashing.Widget + + ready: -> + # This is fired when the widget is done being rendered + + onData: (data) -> + # Handle incoming data + # You can access the html node of this widget with `@node` + # Example: $(@node).fadeOut().fadeIn() will make the node flash each time data comes in. \ No newline at end of file diff --git a/widgets/nagios/nagios.html b/widgets/nagios/nagios.html new file mode 100644 index 0000000..cbc4e43 --- /dev/null +++ b/widgets/nagios/nagios.html @@ -0,0 +1,42 @@ +
      +
    • + + + +
      +

      Error querying Nagios

      +
      +
      +
      +

      +

      critical

      +

      criticals

      +
      +
        +
      • +

        +
      • +
      +
      +

      +

      warning

      +

      warnings

      +
      +
        +
      • +

        +
      • +
      +
      +

      +

      unkown

      +
      +
        +
      • +

        +
      • +
      +

      +
      +
    • +
    diff --git a/widgets/nagios/nagios.scss b/widgets/nagios/nagios.scss new file mode 100644 index 0000000..26e57cf --- /dev/null +++ b/widgets/nagios/nagios.scss @@ -0,0 +1,102 @@ +$background: #444; +$text: #fff; +$success: #50BA5B; +$warning: #F6A41C; +$failure: #D93C38; +$neutral: #888; + +$error: #000; + +.widget-nagios { + li { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + padding-top: 25px; + + .alert { + position: relative; + padding: 2px 0 0 0; + } + + .title-link { + font-size: 30px; + + &:hover { + color: rgba(255, 255, 255, 0.7); + } + } + + h3 { + font-size: 60px; + font-weight: bold; + } + + h4 { + font-size: 20px; + font-weight: bold; + text-transform: uppercase; + display: inline; + } + + p { + &.widget-content { + text-align: left; + margin-left: 20px; + font-size: 16px; + } + } + + &.green { + background-color: $success; + + p { + &.updated-at { + color: lighten($success, 20%); + } + } + } + + &.yellow { + background-color: $warning; + + p { + &.updated-at { + color: lighten($warning, 20%); + } + } + } + + &.red { + background-color: $failure; + + p { + &.updated-at { + color: lighten($failure, 20%); + } + } + } + + &.gray { + background-color: $neutral; + + p { + &.updated-at { + color: lighten($neutral, 20%); + } + } + } + + &.error { + background-color: $error; + + p { + &.updated-at { + color: lighten($error, 20%); + } + } + } + } +} diff --git a/widgets/number/number.coffee b/widgets/number/number.coffee new file mode 100644 index 0000000..645ee7f --- /dev/null +++ b/widgets/number/number.coffee @@ -0,0 +1,24 @@ +class Dashing.Number extends Dashing.Widget + @accessor 'current', Dashing.AnimatedValue + + @accessor 'difference', -> + if @get('last') + last = parseInt(@get('last')) + current = parseInt(@get('current')) + if last != 0 + diff = Math.abs(Math.round((current - last) / last * 100)) + "#{diff}%" + else + "" + + @accessor 'arrow', -> + if @get('last') + if parseInt(@get('current')) > parseInt(@get('last')) then 'icon-arrow-up' else 'icon-arrow-down' + + onData: (data) -> + if data.status + # clear existing "status-*" classes + $(@get('node')).attr 'class', (i,c) -> + c.replace /\bstatus-\S+/g, '' + # add new class + $(@get('node')).addClass "status-#{data.status}" diff --git a/widgets/number/number.html b/widgets/number/number.html new file mode 100644 index 0000000..c82e5f4 --- /dev/null +++ b/widgets/number/number.html @@ -0,0 +1,11 @@ +

    + +

    + +

    + +

    + +

    + +

    diff --git a/widgets/number/number.scss b/widgets/number/number.scss new file mode 100644 index 0000000..9e36c5a --- /dev/null +++ b/widgets/number/number.scss @@ -0,0 +1,39 @@ +// ---------------------------------------------------------------------------- +// Sass declarations +// ---------------------------------------------------------------------------- +$background-color: #178CA6; +$value-color: #fff; + +$title-color: rgba(255, 255, 255, 0.7); +$moreinfo-color: rgba(255, 255, 255, 0.7); + +// ---------------------------------------------------------------------------- +// Widget-number styles +// ---------------------------------------------------------------------------- +.widget-number { + + background-color: $background-color; + + .title { + color: $title-color; + } + + .value { + color: $value-color; + } + + .change-rate { + font-weight: 500; + font-size: 30px; + color: $value-color; + } + + .more-info { + color: $moreinfo-color; + } + + .updated-at { + color: rgba(0, 0, 0, 0.3); + } + +} diff --git a/widgets/text/text.coffee b/widgets/text/text.coffee new file mode 100644 index 0000000..1741d8b --- /dev/null +++ b/widgets/text/text.coffee @@ -0,0 +1 @@ +class Dashing.Text extends Dashing.Widget diff --git a/widgets/text/text.html b/widgets/text/text.html new file mode 100644 index 0000000..decd109 --- /dev/null +++ b/widgets/text/text.html @@ -0,0 +1,7 @@ +

    + +

    + +

    + +

    diff --git a/widgets/text/text.scss b/widgets/text/text.scss new file mode 100644 index 0000000..45d790e --- /dev/null +++ b/widgets/text/text.scss @@ -0,0 +1,32 @@ +// ---------------------------------------------------------------------------- +// Sass declarations +// ---------------------------------------------------------------------------- +$background-color: #ec663c; + +$title-color: rgba(255, 255, 255, 0.7); +$moreinfo-color: rgba(255, 255, 255, 0.7); + +// ---------------------------------------------------------------------------- +// Widget-text styles +// ---------------------------------------------------------------------------- +.widget-text { + + background-color: $background-color; + + .title { + color: $title-color; + } + + .more-info { + color: $moreinfo-color; + } + + .updated-at { + color: rgba(255, 255, 255, 0.7); + } + + + &.large h3 { + font-size: 65px; + } +} -- cgit v1.2.3 From f6fda92d26b070690899b250224e7cc9a2530b51 Mon Sep 17 00:00:00 2001 From: varac Date: Wed, 13 Jul 2016 20:46:51 +0200 Subject: added Gemfile.lock --- Gemfile.lock | 64 ++++++++++++++++++++++++++++-------------------------------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 769e8a4..d3a9e78 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,43 +1,43 @@ GEM remote: https://rubygems.org/ specs: - activesupport (5.0.0) - concurrent-ruby (~> 1.0, >= 1.0.2) + activesupport (4.2.4) i18n (~> 0.7) + json (~> 1.7, >= 1.7.7) minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) addressable (2.4.0) - backports (3.6.8) + backports (3.6.4) buftok (0.2.0) coffee-script (2.2.0) coffee-script-source execjs - coffee-script-source (1.10.0) - concurrent-ruby (1.0.2) - crack (0.4.3) + coffee-script-source (1.9.1.1) + crack (0.4.2) safe_yaml (~> 1.0.0) daemons (1.2.3) - dashing (1.3.7) + dashing (1.3.4) coffee-script (~> 2.2.0) execjs (~> 2.0.2) - rack (~> 1.5.4) + rack (~> 1.5.2) rufus-scheduler (~> 2.0.24) sass (~> 3.2.12) sinatra (~> 1.4.4) sinatra-contrib (~> 1.4.2) sprockets (~> 2.10.1) thin (~> 1.6.1) - thor (> 0.18.1) - domain_name (0.5.20160615) + thor (~> 0.18.1) + domain_name (0.5.25) unf (>= 0.0.5, < 1.0.0) equalizer (0.0.10) - eventmachine (1.2.0.1) + eventmachine (1.0.7) execjs (2.0.2) faraday (0.9.2) multipart-post (>= 1.2, < 3) hashie (1.2.0) hike (1.2.3) - http (1.0.4) + http (1.0.2) addressable (~> 2.3) http-cookie (~> 1.0) http-form_data (~> 1.0.1) @@ -53,12 +53,10 @@ GEM json (1.8.3) memoizable (0.4.2) thread_safe (~> 0.3, >= 0.3.1) - mime-types (3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2016.0521) - mini_portile2 (2.1.0) - minitest (5.9.0) - multi_json (1.12.1) + mime-types (2.6.2) + mini_portile (0.6.2) + minitest (5.8.2) + multi_json (1.11.2) multi_xml (0.5.5) multipart-post (2.0.0) nagiosharder (0.5.0) @@ -71,46 +69,44 @@ GEM terminal-table naught (1.1.0) netrc (0.11.0) - nokogiri (1.6.8) - mini_portile2 (~> 2.1.0) - pkg-config (~> 1.1.7) - pkg-config (1.1.7) + nokogiri (1.6.6.2) + mini_portile (~> 0.6.0) rack (1.5.5) rack-protection (1.5.3) rack rack-test (0.6.3) rack (>= 1.0) - rest-client (2.0.0) + rest-client (1.8.0) http-cookie (>= 1.0.2, < 2.0) - mime-types (>= 1.16, < 4.0) - netrc (~> 0.8) + mime-types (>= 1.16, < 3.0) + netrc (~> 0.7) rufus-scheduler (2.0.24) tzinfo (>= 0.3.22) safe_yaml (1.0.4) sass (3.2.19) simple_oauth (0.3.1) - sinatra (1.4.7) - rack (~> 1.5) + sinatra (1.4.6) + rack (~> 1.4) rack-protection (~> 1.4) tilt (>= 1.3, < 3) - sinatra-contrib (1.4.7) + sinatra-contrib (1.4.6) backports (>= 2.0) multi_json rack-protection rack-test sinatra (~> 1.4.0) tilt (>= 1.3, < 3) - sprockets (2.10.2) + sprockets (2.10.1) hike (~> 1.2) multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) - terminal-table (1.6.0) - thin (1.6.4) + terminal-table (1.5.2) + thin (1.6.3) daemons (~> 1.0, >= 1.0.9) - eventmachine (~> 1.0, >= 1.0.4) + eventmachine (~> 1.0) rack (~> 1.0) - thor (0.19.1) + thor (0.18.1) thread_safe (0.3.5) tilt (1.4.1) twitter (5.16.0) @@ -128,7 +124,7 @@ GEM thread_safe (~> 0.1) unf (0.1.4) unf_ext - unf_ext (0.0.7.2) + unf_ext (0.0.7.1) PLATFORMS ruby -- cgit v1.2.3 From 9133ad57a565f4722bb7bca73ac7ccc8574e2c43 Mon Sep 17 00:00:00 2001 From: varac Date: Wed, 13 Jul 2016 20:48:14 +0200 Subject: smaller fontsize for jenkins failed builds --- widgets/jenkins_build_status/jenkins_build_status.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widgets/jenkins_build_status/jenkins_build_status.scss b/widgets/jenkins_build_status/jenkins_build_status.scss index 5e1eff1..2e475b0 100644 --- a/widgets/jenkins_build_status/jenkins_build_status.scss +++ b/widgets/jenkins_build_status/jenkins_build_status.scss @@ -28,7 +28,7 @@ $value-color: #fff; li { margin-bottom: 5px; - font-size: 40px; + font-size: 20px; } .label { -- cgit v1.2.3 From 1654a8789e8a05ad1b8c27ee64679a9f0d42fd38 Mon Sep 17 00:00:00 2001 From: varac Date: Wed, 13 Jul 2016 21:03:00 +0200 Subject: added demo.bm and mail.bm --- dashboards/dashboard.erb | 14 +++++++++++--- jobs/nagios.rb | 14 ++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/dashboards/dashboard.erb b/dashboards/dashboard.erb index ba450cf..7d187ea 100644 --- a/dashboards/dashboard.erb +++ b/dashboards/dashboard.erb @@ -2,12 +2,20 @@
    +
  • +
    +
  • +
  • +
    +
  • + +
  • -
    +
  • -
    +
  • -
    +
  • diff --git a/jobs/nagios.rb b/jobs/nagios.rb index 4186d22..1f76237 100644 --- a/jobs/nagios.rb +++ b/jobs/nagios.rb @@ -11,6 +11,13 @@ SCHEDULER.every '10s' do username: 'nagiosadmin', password: ENV['UNSTABLE_PASS'] }, + demo: { + domain: 'demo.bitmask.i', + query_url: 'https://unstable.bitmask.net/cgi-bin/nagios3/', + home_url: 'https://unstable.bitmask.net/nagios3/', + username: 'nagiosadmin', + password: ENV['UNSTABLE_PASS'] + }, dev: { domain: 'dev.bitmask.i', query_url: 'https://unstable.bitmask.net/cgi-bin/nagios3/', @@ -18,6 +25,13 @@ SCHEDULER.every '10s' do username: 'nagiosadmin', password: ENV['UNSTABLE_PASS'] }, + mail: { + domain: 'mail.bitmask.i', + query_url: 'https://unstable.bitmask.net/cgi-bin/nagios3/', + home_url: 'https://unstable.bitmask.net/nagios3/', + username: 'nagiosadmin', + password: ENV['UNSTABLE_PASS'] + }, unstable: { domain: 'unstable.bitmask.i', query_url: 'https://unstable.bitmask.net/cgi-bin/nagios3/', -- cgit v1.2.3 From 02711e3bdd90ee8ef15b773af4a66a68688147f5 Mon Sep 17 00:00:00 2001 From: varac Date: Mon, 15 Aug 2016 14:04:53 +0200 Subject: Updated bundled gems --- Gemfile.lock | 69 +++++++++++++++++++++++++++++++----------------------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index d3a9e78..a530e09 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,43 +1,43 @@ GEM remote: https://rubygems.org/ specs: - activesupport (4.2.4) + activesupport (5.0.0.1) + concurrent-ruby (~> 1.0, >= 1.0.2) i18n (~> 0.7) - json (~> 1.7, >= 1.7.7) minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) addressable (2.4.0) - backports (3.6.4) + backports (3.6.8) buftok (0.2.0) coffee-script (2.2.0) coffee-script-source execjs - coffee-script-source (1.9.1.1) - crack (0.4.2) + coffee-script-source (1.10.0) + concurrent-ruby (1.0.2) + crack (0.4.3) safe_yaml (~> 1.0.0) - daemons (1.2.3) - dashing (1.3.4) + daemons (1.2.4) + dashing (1.3.7) coffee-script (~> 2.2.0) execjs (~> 2.0.2) - rack (~> 1.5.2) + rack (~> 1.5.4) rufus-scheduler (~> 2.0.24) sass (~> 3.2.12) sinatra (~> 1.4.4) sinatra-contrib (~> 1.4.2) sprockets (~> 2.10.1) thin (~> 1.6.1) - thor (~> 0.18.1) - domain_name (0.5.25) + thor (> 0.18.1) + domain_name (0.5.20160615) unf (>= 0.0.5, < 1.0.0) equalizer (0.0.10) - eventmachine (1.0.7) + eventmachine (1.2.0.1) execjs (2.0.2) faraday (0.9.2) multipart-post (>= 1.2, < 3) hashie (1.2.0) hike (1.2.3) - http (1.0.2) + http (1.0.4) addressable (~> 2.3) http-cookie (~> 1.0) http-form_data (~> 1.0.1) @@ -46,17 +46,18 @@ GEM domain_name (~> 0.5) http-form_data (1.0.1) http_parser.rb (0.6.0) - httparty (0.13.7) - json (~> 1.8) + httparty (0.14.0) multi_xml (>= 0.5.2) i18n (0.7.0) json (1.8.3) memoizable (0.4.2) thread_safe (~> 0.3, >= 0.3.1) - mime-types (2.6.2) - mini_portile (0.6.2) - minitest (5.8.2) - multi_json (1.11.2) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) + mini_portile2 (2.1.0) + minitest (5.9.0) + multi_json (1.12.1) multi_xml (0.5.5) multipart-post (2.0.0) nagiosharder (0.5.0) @@ -69,44 +70,46 @@ GEM terminal-table naught (1.1.0) netrc (0.11.0) - nokogiri (1.6.6.2) - mini_portile (~> 0.6.0) + nokogiri (1.6.8) + mini_portile2 (~> 2.1.0) + pkg-config (~> 1.1.7) + pkg-config (1.1.7) rack (1.5.5) rack-protection (1.5.3) rack rack-test (0.6.3) rack (>= 1.0) - rest-client (1.8.0) + rest-client (2.0.0) http-cookie (>= 1.0.2, < 2.0) - mime-types (>= 1.16, < 3.0) - netrc (~> 0.7) + mime-types (>= 1.16, < 4.0) + netrc (~> 0.8) rufus-scheduler (2.0.24) tzinfo (>= 0.3.22) safe_yaml (1.0.4) sass (3.2.19) simple_oauth (0.3.1) - sinatra (1.4.6) - rack (~> 1.4) + sinatra (1.4.7) + rack (~> 1.5) rack-protection (~> 1.4) tilt (>= 1.3, < 3) - sinatra-contrib (1.4.6) + sinatra-contrib (1.4.7) backports (>= 2.0) multi_json rack-protection rack-test sinatra (~> 1.4.0) tilt (>= 1.3, < 3) - sprockets (2.10.1) + sprockets (2.10.2) hike (~> 1.2) multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) - terminal-table (1.5.2) - thin (1.6.3) + terminal-table (1.6.0) + thin (1.6.4) daemons (~> 1.0, >= 1.0.9) - eventmachine (~> 1.0) + eventmachine (~> 1.0, >= 1.0.4) rack (~> 1.0) - thor (0.18.1) + thor (0.19.1) thread_safe (0.3.5) tilt (1.4.1) twitter (5.16.0) @@ -124,7 +127,7 @@ GEM thread_safe (~> 0.1) unf (0.1.4) unf_ext - unf_ext (0.0.7.1) + unf_ext (0.0.7.2) PLATFORMS ruby -- cgit v1.2.3 From ea95a54c8dde97821d32304ea419cb331f7a342a Mon Sep 17 00:00:00 2001 From: varac Date: Mon, 15 Aug 2016 14:16:12 +0200 Subject: updated bundled Gems --- Gemfile.lock | 83 ++++++++++++++++++++++++++------------------------------ smashing.gemspec | 5 ++++ 2 files changed, 43 insertions(+), 45 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index a530e09..fecabb2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,20 @@ +PATH + remote: . + specs: + dashing (1.3.6) + coffee-script (~> 2.2.0) + crack + execjs (~> 2.0.2) + nagiosharder + rack (~> 1.5.4) + rufus-scheduler (~> 2.0.24) + sass (~> 3.2.12) + sinatra (~> 1.4.4) + sinatra-contrib (~> 1.4.2) + sprockets (~> 2.10.1) + thin (~> 1.6.1) + thor (> 0.18.1) + GEM remote: https://rubygems.org/ specs: @@ -6,9 +23,7 @@ GEM i18n (~> 0.7) minitest (~> 5.1) tzinfo (~> 1.1) - addressable (2.4.0) backports (3.6.8) - buftok (0.2.0) coffee-script (2.2.0) coffee-script-source execjs @@ -17,49 +32,31 @@ GEM crack (0.4.3) safe_yaml (~> 1.0.0) daemons (1.2.4) - dashing (1.3.7) - coffee-script (~> 2.2.0) - execjs (~> 2.0.2) - rack (~> 1.5.4) - rufus-scheduler (~> 2.0.24) - sass (~> 3.2.12) - sinatra (~> 1.4.4) - sinatra-contrib (~> 1.4.2) - sprockets (~> 2.10.1) - thin (~> 1.6.1) - thor (> 0.18.1) + docile (1.1.5) domain_name (0.5.20160615) unf (>= 0.0.5, < 1.0.0) - equalizer (0.0.10) eventmachine (1.2.0.1) execjs (2.0.2) - faraday (0.9.2) - multipart-post (>= 1.2, < 3) + fakeweb (1.3.0) + haml (4.0.7) + tilt hashie (1.2.0) hike (1.2.3) - http (1.0.4) - addressable (~> 2.3) - http-cookie (~> 1.0) - http-form_data (~> 1.0.1) - http_parser.rb (~> 0.6.0) http-cookie (1.0.2) domain_name (~> 0.5) - http-form_data (1.0.1) - http_parser.rb (0.6.0) httparty (0.14.0) multi_xml (>= 0.5.2) i18n (0.7.0) - json (1.8.3) - memoizable (0.4.2) - thread_safe (~> 0.3, >= 0.3.1) + metaclass (0.0.4) mime-types (3.1) mime-types-data (~> 3.2015) mime-types-data (3.2016.0521) mini_portile2 (2.1.0) - minitest (5.9.0) + minitest (5.2.3) + mocha (0.14.0) + metaclass (~> 0.0.1) multi_json (1.12.1) multi_xml (0.5.5) - multipart-post (2.0.0) nagiosharder (0.5.0) activesupport hashie (~> 1.2.0) @@ -68,7 +65,6 @@ GEM nokogiri rest-client terminal-table - naught (1.1.0) netrc (0.11.0) nokogiri (1.6.8) mini_portile2 (~> 2.1.0) @@ -79,6 +75,7 @@ GEM rack rack-test (0.6.3) rack (>= 1.0) + rake (10.1.1) rest-client (2.0.0) http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) @@ -87,7 +84,11 @@ GEM tzinfo (>= 0.3.22) safe_yaml (1.0.4) sass (3.2.19) - simple_oauth (0.3.1) + simplecov (0.8.2) + docile (~> 1.1.0) + multi_json + simplecov-html (~> 0.8.0) + simplecov-html (0.8.0) sinatra (1.4.7) rack (~> 1.5) rack-protection (~> 1.4) @@ -112,17 +113,6 @@ GEM thor (0.19.1) thread_safe (0.3.5) tilt (1.4.1) - twitter (5.16.0) - addressable (~> 2.3) - buftok (~> 0.2.0) - equalizer (= 0.0.10) - faraday (~> 0.9.0) - http (~> 1.0) - http_parser.rb (~> 0.6.0) - json (~> 1.8) - memoizable (~> 0.4.0) - naught (~> 1.0) - simple_oauth (~> 0.3.0) tzinfo (1.2.2) thread_safe (~> 0.1) unf (0.1.4) @@ -133,10 +123,13 @@ PLATFORMS ruby DEPENDENCIES - crack - dashing - nagiosharder - twitter + dashing! + fakeweb (~> 1.3.0) + haml (~> 4.0.4) + minitest (~> 5.2.0) + mocha (~> 0.14.0) + rake (~> 10.1.0) + simplecov (~> 0.8.2) BUNDLED WITH 1.11.2 diff --git a/smashing.gemspec b/smashing.gemspec index 4f1fbd6..dba7c38 100644 --- a/smashing.gemspec +++ b/smashing.gemspec @@ -26,6 +26,11 @@ Gem::Specification.new do |s| s.add_dependency('sprockets', '~> 2.10.1') s.add_dependency('rack', '~> 1.5.4') + # additional deps + s.add_dependency('crack') + s.add_dependency('nagiosharder') + + s.add_development_dependency('rake', '~> 10.1.0') s.add_development_dependency('haml', '~> 4.0.4') s.add_development_dependency('minitest', '~> 5.2.0') -- cgit v1.2.3 From 23e9b0e31dce7c290bba8d43322e214e7c2584fe Mon Sep 17 00:00:00 2001 From: varac Date: Sat, 27 Aug 2016 21:15:34 +0200 Subject: Gitlab merge reqs --- Gemfile.lock | 8 ++- dashboards/dashboard.erb | 16 ++++-- dashboards/default.erb | 2 +- jobs/gitlab_merge_requests.rb | 36 ++++++++++++ smashing.gemspec | 1 + .../gitlab_merge_requests.coffee | 9 +++ .../gitlab_merge_requests.html | 18 ++++++ .../gitlab_merge_requests.scss | 67 ++++++++++++++++++++++ 8 files changed, 149 insertions(+), 8 deletions(-) create mode 100644 jobs/gitlab_merge_requests.rb create mode 100644 widgets/gitlab_merge_requests/gitlab_merge_requests.coffee create mode 100644 widgets/gitlab_merge_requests/gitlab_merge_requests.html create mode 100644 widgets/gitlab_merge_requests/gitlab_merge_requests.scss diff --git a/Gemfile.lock b/Gemfile.lock index fecabb2..fc509cd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,6 +5,7 @@ PATH coffee-script (~> 2.2.0) crack execjs (~> 2.0.2) + gitlab nagiosharder rack (~> 1.5.4) rufus-scheduler (~> 2.0.24) @@ -38,15 +39,20 @@ GEM eventmachine (1.2.0.1) execjs (2.0.2) fakeweb (1.3.0) + gitlab (3.7.0) + httparty (~> 0.13.0) + terminal-table haml (4.0.7) tilt hashie (1.2.0) hike (1.2.3) http-cookie (1.0.2) domain_name (~> 0.5) - httparty (0.14.0) + httparty (0.13.7) + json (~> 1.8) multi_xml (>= 0.5.2) i18n (0.7.0) + json (1.8.3) metaclass (0.0.4) mime-types (3.1) mime-types-data (~> 3.2015) diff --git a/dashboards/dashboard.erb b/dashboards/dashboard.erb index 7d187ea..35ae341 100644 --- a/dashboards/dashboard.erb +++ b/dashboards/dashboard.erb @@ -1,21 +1,25 @@ -
  • +
  • -
  • +
  • -
  • +
  • -
  • +
  • -
  • +
  • -
  • +
  • + +
  • +
    +
  • diff --git a/dashboards/default.erb b/dashboards/default.erb index d5441b8..86110c0 100644 --- a/dashboards/default.erb +++ b/dashboards/default.erb @@ -3,7 +3,7 @@ $(function() { // These settings override the defaults set in application.coffee. You can do this on a per dashboard basis. Dashing.widget_margins = [2, 2] Dashing.widget_base_dimensions = [235, 295] - Dashing.numColumns = 6 + Dashing.numColumns = 8 }); diff --git a/jobs/gitlab_merge_requests.rb b/jobs/gitlab_merge_requests.rb new file mode 100644 index 0000000..f39caf9 --- /dev/null +++ b/jobs/gitlab_merge_requests.rb @@ -0,0 +1,36 @@ +require 'gitlab' +require 'date' + +# TODO: Move config to yaml +my_group_path = 'leap' +Gitlab.configure do |config| + # API endpoint URL, default + config.endpoint = ENV['GITLAB_ENDPOINT'] + + # User's private token or OAuth2 access token + config.private_token = ENV['GITLAB_TOKEN'] +end + +pr_widget_data_id = 'gitlab-merge-requests' +SCHEDULER.every '10m', :first_in => 0 do |job| + my_group = Gitlab.groups(:search => my_group_path).find do |group| + group.path == my_group_path + end + projects = Gitlab.group(my_group.id).projects.map do |proj| + { :id => proj['id'], :name => proj['name'] } + end + + open_merge_requests = projects.inject([]) { |merges, proj| + Gitlab.merge_requests(proj[:id], :state => 'opened').each do |request| + puts proj[:name] + ': ' + request.title + merges.push({ + title: request.title, + repo: proj[:name], + updated_at: DateTime.parse(request.updated_at).strftime("%b %-d %Y, %l:%m %p"), + creator: "@" + request.author.username + }) + end + merges + } + send_event(pr_widget_data_id, { header: "Open Merge Requests", merges: open_merge_requests }) +end diff --git a/smashing.gemspec b/smashing.gemspec index dba7c38..d662f8c 100644 --- a/smashing.gemspec +++ b/smashing.gemspec @@ -29,6 +29,7 @@ Gem::Specification.new do |s| # additional deps s.add_dependency('crack') s.add_dependency('nagiosharder') + s.add_dependency('gitlab') s.add_development_dependency('rake', '~> 10.1.0') diff --git a/widgets/gitlab_merge_requests/gitlab_merge_requests.coffee b/widgets/gitlab_merge_requests/gitlab_merge_requests.coffee new file mode 100644 index 0000000..e68c3f6 --- /dev/null +++ b/widgets/gitlab_merge_requests/gitlab_merge_requests.coffee @@ -0,0 +1,9 @@ +class Dashing.GitlabMergeRequests extends Dashing.Widget + + ready: -> + # This is fired when the widget is done being rendered + + onData: (data) -> + # Handle incoming data + # You can access the html node of this widget with `@node` + # Example: $(@node).fadeOut().fadeIn() will make the node flash each time data comes in. \ No newline at end of file diff --git a/widgets/gitlab_merge_requests/gitlab_merge_requests.html b/widgets/gitlab_merge_requests/gitlab_merge_requests.html new file mode 100644 index 0000000..396da97 --- /dev/null +++ b/widgets/gitlab_merge_requests/gitlab_merge_requests.html @@ -0,0 +1,18 @@ +

    GitLab

    +

    + +
      +
    • +
      +
      +
      +
       · 
      +
      +
       · 
      +
      +
      +
      +
    • +
    + +

    diff --git a/widgets/gitlab_merge_requests/gitlab_merge_requests.scss b/widgets/gitlab_merge_requests/gitlab_merge_requests.scss new file mode 100644 index 0000000..d3c51f5 --- /dev/null +++ b/widgets/gitlab_merge_requests/gitlab_merge_requests.scss @@ -0,0 +1,67 @@ +// ---------------------------------------------------------------------------- +// Sass declarations +// ---------------------------------------------------------------------------- +$background-color: teal; + +$heading-color: rgba(255, 255, 255, 0.7); +$merge-title-color: rgba(255, 255, 255, 1.0); +$merge-info-color: rgba(255, 255, 255, 1.0); + +// ---------------------------------------------------------------------------- +// Widget-gitlab-merge-requests styles +// ---------------------------------------------------------------------------- +.widget-gitlab-merge-requests { + + background-color: $background-color; + vertical-align: top !important; + + .header { + margin-bottom: 0; + font-size: xx-large; + color: $heading-color; + } + + .sub-header { + color: $heading-color; + font-size: large; + margin-bottom: 1vh; + } + + .merge { + margin-bottom: 2vh; + } + + .merge-title { + font-size: medium; + font-weight: bold; + color: $merge-title-color; + } + + .merge-info { + font-size: small; + font-style: italic; + color: $merge-info-color; + } + + .merge-info div { + float:left; + } + + .list { + list-style: none; + } + + .updated-at { + color: rgba(0, 0, 0, 0.3); + } + + ol, ul { + margin: 0 15px; + text-align: left; + } + + ol { + list-style-position: inside; + } + +} -- cgit v1.2.3 From dabb0d5f4900599209748cd207ffe0ae5434d177 Mon Sep 17 00:00:00 2001 From: varac Date: Thu, 29 Sep 2016 10:03:58 +0200 Subject: [wip] gitlab-builds --- dashboards/dashboard.erb | 19 +++--- jobs/gitlab_builds.rb | 66 +++++++++++++++++++++ jobs/gitlab_merge_requests.rb | 36 ------------ jobs/nagios.rb | 7 +-- .../gitlab_build_status/gitlab_build_status.coffee | 11 ++++ .../gitlab_build_status/gitlab_build_status.html | 15 +++++ .../gitlab_build_status/gitlab_build_status.scss | 56 ++++++++++++++++++ .../gitlab_merge_requests.coffee | 9 --- .../gitlab_merge_requests.html | 18 ------ .../gitlab_merge_requests.scss | 67 ---------------------- 10 files changed, 161 insertions(+), 143 deletions(-) create mode 100644 jobs/gitlab_builds.rb delete mode 100644 jobs/gitlab_merge_requests.rb create mode 100644 widgets/gitlab_build_status/gitlab_build_status.coffee create mode 100644 widgets/gitlab_build_status/gitlab_build_status.html create mode 100644 widgets/gitlab_build_status/gitlab_build_status.scss delete mode 100644 widgets/gitlab_merge_requests/gitlab_merge_requests.coffee delete mode 100644 widgets/gitlab_merge_requests/gitlab_merge_requests.html delete mode 100644 widgets/gitlab_merge_requests/gitlab_merge_requests.scss diff --git a/dashboards/dashboard.erb b/dashboards/dashboard.erb index 35ae341..babc0f2 100644 --- a/dashboards/dashboard.erb +++ b/dashboards/dashboard.erb @@ -1,25 +1,26 @@ -
  • +
  • +
    +
  • + +
  • -
  • +
  • -
  • +
  • -
  • +
  • -
  • +
  • -
  • +
  • -
  • -
    -
  • diff --git a/jobs/gitlab_builds.rb b/jobs/gitlab_builds.rb new file mode 100644 index 0000000..0071835 --- /dev/null +++ b/jobs/gitlab_builds.rb @@ -0,0 +1,66 @@ +require 'gitlab' +require 'date' +require 'pp' + +endpoint = 'https://0xacab.org/api/v3' +#group_path = 'leap' +#group_id = 52 +token = '8vw3vWRrzFw8B6XH9e6d' +broken_builds = [] + +Gitlab.configure do |config| + # API endpoint URL, default + config.endpoint = endpoint + + # User's private token or OAuth2 access token + config.private_token = token +end + +# find group id by name +#my_group = Gitlab.groups(:search => group_path).find do |group| + #group.path == group_path +#end +#my_group_id = my_group.id + + +SCHEDULER.every '60s', :first_in => 0 do + + projects = [ + # {:id=>327, :name=>"zeromq3", :path=>"leap/zeromq3"}, + # {:id=>97, :name=>"soledad", :path=>"leap/soledad"}, + # {:id=>129, :name=>"platform", :path=>"leap/platfrom"}, + {:id=>241, :name=>"leap_cli", :path=>"leap/leap_cli"} + ] + + # get a list of all projects + #projects = Gitlab.group(group_id).projects.map do |proj| + #pp proj + #{ :id => proj['id'], :name => proj['name'], :path => proj['path_with_namespace'] } + #end + + + projects.each do |proj| + + begin + pipeline = Gitlab.pipelines(proj[:id], { per_page: 1 }) + #pp pipeline[0] + proj[:status] = pipeline[0].status + proj[:pipeline_id] = pipeline[0].id + proj[:ref] = pipeline[0].ref + proj[:date] = pipeline[0].finished_at + rescue + proj[:status] = 'No builds configured' + end + + unless proj[:status] =~ /^success|running|No builds configured$/ + broken_builds << proj + end + puts proj + end + + failed = broken_builds.size > 0 + #puts failed + + send_event('gitlab-builds', { failed: failed, header: "Gitlab builds", broken_builds: broken_builds }) +end + diff --git a/jobs/gitlab_merge_requests.rb b/jobs/gitlab_merge_requests.rb deleted file mode 100644 index f39caf9..0000000 --- a/jobs/gitlab_merge_requests.rb +++ /dev/null @@ -1,36 +0,0 @@ -require 'gitlab' -require 'date' - -# TODO: Move config to yaml -my_group_path = 'leap' -Gitlab.configure do |config| - # API endpoint URL, default - config.endpoint = ENV['GITLAB_ENDPOINT'] - - # User's private token or OAuth2 access token - config.private_token = ENV['GITLAB_TOKEN'] -end - -pr_widget_data_id = 'gitlab-merge-requests' -SCHEDULER.every '10m', :first_in => 0 do |job| - my_group = Gitlab.groups(:search => my_group_path).find do |group| - group.path == my_group_path - end - projects = Gitlab.group(my_group.id).projects.map do |proj| - { :id => proj['id'], :name => proj['name'] } - end - - open_merge_requests = projects.inject([]) { |merges, proj| - Gitlab.merge_requests(proj[:id], :state => 'opened').each do |request| - puts proj[:name] + ': ' + request.title - merges.push({ - title: request.title, - repo: proj[:name], - updated_at: DateTime.parse(request.updated_at).strftime("%b %-d %Y, %l:%m %p"), - creator: "@" + request.author.username - }) - end - merges - } - send_event(pr_widget_data_id, { header: "Open Merge Requests", merges: open_merge_requests }) -end diff --git a/jobs/nagios.rb b/jobs/nagios.rb index 1f76237..bda1b59 100644 --- a/jobs/nagios.rb +++ b/jobs/nagios.rb @@ -1,7 +1,6 @@ SCHEDULER.every '10s' do require 'bundler/setup' require 'nagiosharder' - require 'pp' environments = { cdev: { @@ -87,9 +86,9 @@ SCHEDULER.every '10s' do end end - puts key.to_s + ": " + critical_count.to_s - puts critical_services.join(", ") - puts + #puts key.to_s + ": " + critical_count.to_s + #puts critical_services.join(", ") + #puts send_event('nagios-' + key.to_s, { criticals: critical_count, critical_services: critical_services, diff --git a/widgets/gitlab_build_status/gitlab_build_status.coffee b/widgets/gitlab_build_status/gitlab_build_status.coffee new file mode 100644 index 0000000..e501aed --- /dev/null +++ b/widgets/gitlab_build_status/gitlab_build_status.coffee @@ -0,0 +1,11 @@ +class Dashing.GitlabBuildStatus extends Dashing.Widget + + onData: (data) -> + if data.failed + $(@node).find('div.gitlab-build-failed').show() + $(@node).find('div.gitlab-build-succeeded').hide() + $(@node).css("background-color", "red") + else + $(@node).find('div.gitlab-build-failed').hide() + $(@node).find('div.gitlab-build-succeeded').show() + $(@node).css("background-color", "#50BA5B") diff --git a/widgets/gitlab_build_status/gitlab_build_status.html b/widgets/gitlab_build_status/gitlab_build_status.html new file mode 100644 index 0000000..b71d8a6 --- /dev/null +++ b/widgets/gitlab_build_status/gitlab_build_status.html @@ -0,0 +1,15 @@ +
    +

    FAILED

    +
      +
    • +
      +
      +
    • +
    +
    + +
    +

    All Gitlab builds are successful

    +
    + +

    diff --git a/widgets/gitlab_build_status/gitlab_build_status.scss b/widgets/gitlab_build_status/gitlab_build_status.scss new file mode 100644 index 0000000..46e867d --- /dev/null +++ b/widgets/gitlab_build_status/gitlab_build_status.scss @@ -0,0 +1,56 @@ +// ---------------------------------------------------------------------------- +// Sass declarations +// ---------------------------------------------------------------------------- +$background-color: #ec663c; +$title-color: rgba(255, 255, 255, 0.7); +$label-color: rgba(255, 255, 255, 0.7); +$value-color: #fff; + +// ---------------------------------------------------------------------------- +// Widget-text styles +// ---------------------------------------------------------------------------- +.widget-gitlab-build-status { + + background-color: $background-color; + + .title { + color: $title-color; + } + .updated-at { + color: rgba(255, 255, 255, 0.7); + } + + ol, ul { + margin: 0 15px; + text-align: left; + color: $label-color; + } + + li { + margin-bottom: 5px; + font-size: 20px; + } + + .label { + color: $label-color; + } + + .value { + margin-left: 12px; + font-weight: 600; + color: $value-color; + } + + .updated-at { + color: rgba(0, 0, 0, 0.3); + } + + .build-failed { + display: none; + } + + .fa { + font-size: 10em; + color: $label-color; + } +} diff --git a/widgets/gitlab_merge_requests/gitlab_merge_requests.coffee b/widgets/gitlab_merge_requests/gitlab_merge_requests.coffee deleted file mode 100644 index e68c3f6..0000000 --- a/widgets/gitlab_merge_requests/gitlab_merge_requests.coffee +++ /dev/null @@ -1,9 +0,0 @@ -class Dashing.GitlabMergeRequests extends Dashing.Widget - - ready: -> - # This is fired when the widget is done being rendered - - onData: (data) -> - # Handle incoming data - # You can access the html node of this widget with `@node` - # Example: $(@node).fadeOut().fadeIn() will make the node flash each time data comes in. \ No newline at end of file diff --git a/widgets/gitlab_merge_requests/gitlab_merge_requests.html b/widgets/gitlab_merge_requests/gitlab_merge_requests.html deleted file mode 100644 index 396da97..0000000 --- a/widgets/gitlab_merge_requests/gitlab_merge_requests.html +++ /dev/null @@ -1,18 +0,0 @@ -

    GitLab

    -

    - -
      -
    • -
      -
      -
      -
       · 
      -
      -
       · 
      -
      -
      -
      -
    • -
    - -

    diff --git a/widgets/gitlab_merge_requests/gitlab_merge_requests.scss b/widgets/gitlab_merge_requests/gitlab_merge_requests.scss deleted file mode 100644 index d3c51f5..0000000 --- a/widgets/gitlab_merge_requests/gitlab_merge_requests.scss +++ /dev/null @@ -1,67 +0,0 @@ -// ---------------------------------------------------------------------------- -// Sass declarations -// ---------------------------------------------------------------------------- -$background-color: teal; - -$heading-color: rgba(255, 255, 255, 0.7); -$merge-title-color: rgba(255, 255, 255, 1.0); -$merge-info-color: rgba(255, 255, 255, 1.0); - -// ---------------------------------------------------------------------------- -// Widget-gitlab-merge-requests styles -// ---------------------------------------------------------------------------- -.widget-gitlab-merge-requests { - - background-color: $background-color; - vertical-align: top !important; - - .header { - margin-bottom: 0; - font-size: xx-large; - color: $heading-color; - } - - .sub-header { - color: $heading-color; - font-size: large; - margin-bottom: 1vh; - } - - .merge { - margin-bottom: 2vh; - } - - .merge-title { - font-size: medium; - font-weight: bold; - color: $merge-title-color; - } - - .merge-info { - font-size: small; - font-style: italic; - color: $merge-info-color; - } - - .merge-info div { - float:left; - } - - .list { - list-style: none; - } - - .updated-at { - color: rgba(0, 0, 0, 0.3); - } - - ol, ul { - margin: 0 15px; - text-align: left; - } - - ol { - list-style-position: inside; - } - -} -- cgit v1.2.3 From c31e90830674070d16848e380a6e703fa1ae3edd Mon Sep 17 00:00:00 2001 From: varac Date: Fri, 30 Sep 2016 23:49:28 +0200 Subject: Use varacs gitlab gem fork for pipeline support --- Gemfile | 1 + Gemfile.lock | 25 ++++++++++++++++--------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Gemfile b/Gemfile index 01dc836..4406e52 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,5 @@ source 'https://rubygems.org' +gem 'gitlab', :git => 'https://github.com/varac/gitlab.git', :branch => 'pipeline_support' gemspec diff --git a/Gemfile.lock b/Gemfile.lock index fc509cd..4aaeb58 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,12 @@ +GIT + remote: https://github.com/varac/gitlab.git + revision: a2d105128aab0226082bbdabf7da0e24892e5b79 + branch: pipeline_support + specs: + gitlab (3.7.0) + httparty + terminal-table + PATH remote: . specs: @@ -34,25 +43,20 @@ GEM safe_yaml (~> 1.0.0) daemons (1.2.4) docile (1.1.5) - domain_name (0.5.20160615) + domain_name (0.5.20160826) unf (>= 0.0.5, < 1.0.0) eventmachine (1.2.0.1) execjs (2.0.2) fakeweb (1.3.0) - gitlab (3.7.0) - httparty (~> 0.13.0) - terminal-table haml (4.0.7) tilt hashie (1.2.0) hike (1.2.3) - http-cookie (1.0.2) + http-cookie (1.0.3) domain_name (~> 0.5) - httparty (0.13.7) - json (~> 1.8) + httparty (0.14.0) multi_xml (>= 0.5.2) i18n (0.7.0) - json (1.8.3) metaclass (0.0.4) mime-types (3.1) mime-types-data (~> 3.2015) @@ -111,7 +115,8 @@ GEM multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) - terminal-table (1.6.0) + terminal-table (1.7.3) + unicode-display_width (~> 1.1.1) thin (1.6.4) daemons (~> 1.0, >= 1.0.9) eventmachine (~> 1.0, >= 1.0.4) @@ -124,6 +129,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.2) + unicode-display_width (1.1.1) PLATFORMS ruby @@ -131,6 +137,7 @@ PLATFORMS DEPENDENCIES dashing! fakeweb (~> 1.3.0) + gitlab! haml (~> 4.0.4) minitest (~> 5.2.0) mocha (~> 0.14.0) -- cgit v1.2.3 From 4d6005b1e9c1aeee678415ff39da6f0364ae1fcf Mon Sep 17 00:00:00 2001 From: varac Date: Sat, 1 Oct 2016 00:02:01 +0200 Subject: Gitlab Build Status works now --- jobs/gitlab_build_status.rb | 44 +++++++++++++++ jobs/gitlab_builds.rb | 66 ---------------------- .../gitlab_build_status/gitlab_build_status.html | 7 ++- 3 files changed, 48 insertions(+), 69 deletions(-) create mode 100644 jobs/gitlab_build_status.rb delete mode 100644 jobs/gitlab_builds.rb diff --git a/jobs/gitlab_build_status.rb b/jobs/gitlab_build_status.rb new file mode 100644 index 0000000..addfaed --- /dev/null +++ b/jobs/gitlab_build_status.rb @@ -0,0 +1,44 @@ +require 'gitlab' +require 'date' +require 'pp' + +Gitlab.configure do |config| + # API endpoint URL, default + config.endpoint = ENV['GITLAB_ENDPOINT'] + + # User's private token or OAuth2 access token + config.private_token = ENV['GITLAB_TOKEN'] +end + +SCHEDULER.every '300s', :first_in => 0 do + broken_builds = [] + + # get a list of all projects + projects = Gitlab.group(ENV['GITLAB_GROUP_ID']).projects.map do |proj| + { :id => proj['id'], :name => proj['name'], :path => proj['path_with_namespace'] } + end + + projects.each do |proj| + + begin + pipeline = Gitlab.pipelines(proj[:id], { per_page: 1 }) + #pp pipeline[0] + proj[:status] = pipeline[0].status + proj[:pipeline_id] = pipeline[0].id + proj[:ref] = pipeline[0].ref + date = DateTime.parse(pipeline[0].updated_at).strftime("%F %T") + proj[:date] = date + rescue + proj[:status] = 'No builds configured' + end + + unless proj[:status] =~ /^success|running|No builds configured$/ + broken_builds << proj + end + puts proj + end + + failed = broken_builds.size > 0 + + send_event('gitlab-builds', { failed: failed, header: "Gitlab builds", broken_builds: broken_builds }) +end diff --git a/jobs/gitlab_builds.rb b/jobs/gitlab_builds.rb deleted file mode 100644 index 0071835..0000000 --- a/jobs/gitlab_builds.rb +++ /dev/null @@ -1,66 +0,0 @@ -require 'gitlab' -require 'date' -require 'pp' - -endpoint = 'https://0xacab.org/api/v3' -#group_path = 'leap' -#group_id = 52 -token = '8vw3vWRrzFw8B6XH9e6d' -broken_builds = [] - -Gitlab.configure do |config| - # API endpoint URL, default - config.endpoint = endpoint - - # User's private token or OAuth2 access token - config.private_token = token -end - -# find group id by name -#my_group = Gitlab.groups(:search => group_path).find do |group| - #group.path == group_path -#end -#my_group_id = my_group.id - - -SCHEDULER.every '60s', :first_in => 0 do - - projects = [ - # {:id=>327, :name=>"zeromq3", :path=>"leap/zeromq3"}, - # {:id=>97, :name=>"soledad", :path=>"leap/soledad"}, - # {:id=>129, :name=>"platform", :path=>"leap/platfrom"}, - {:id=>241, :name=>"leap_cli", :path=>"leap/leap_cli"} - ] - - # get a list of all projects - #projects = Gitlab.group(group_id).projects.map do |proj| - #pp proj - #{ :id => proj['id'], :name => proj['name'], :path => proj['path_with_namespace'] } - #end - - - projects.each do |proj| - - begin - pipeline = Gitlab.pipelines(proj[:id], { per_page: 1 }) - #pp pipeline[0] - proj[:status] = pipeline[0].status - proj[:pipeline_id] = pipeline[0].id - proj[:ref] = pipeline[0].ref - proj[:date] = pipeline[0].finished_at - rescue - proj[:status] = 'No builds configured' - end - - unless proj[:status] =~ /^success|running|No builds configured$/ - broken_builds << proj - end - puts proj - end - - failed = broken_builds.size > 0 - #puts failed - - send_event('gitlab-builds', { failed: failed, header: "Gitlab builds", broken_builds: broken_builds }) -end - diff --git a/widgets/gitlab_build_status/gitlab_build_status.html b/widgets/gitlab_build_status/gitlab_build_status.html index b71d8a6..00d799b 100644 --- a/widgets/gitlab_build_status/gitlab_build_status.html +++ b/widgets/gitlab_build_status/gitlab_build_status.html @@ -1,9 +1,10 @@ -
    +

    FAILED

    • -
      -
      + +
      -
      +
      ()
    -- cgit v1.2.3 From 782793d75212d9efc1f2d3d03e4c889c924142e8 Mon Sep 17 00:00:00 2001 From: varac Date: Sat, 1 Oct 2016 01:02:23 +0200 Subject: activesupport >= 5.0.0 requires ruby 2.2 which break jessie support --- Gemfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Gemfile b/Gemfile index 4406e52..439b890 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,7 @@ source 'https://rubygems.org' gem 'gitlab', :git => 'https://github.com/varac/gitlab.git', :branch => 'pipeline_support' +# activesupport >= 5.0.0 requires ruby 2.2 which break jessie support +gem 'activesupport', '< 5.0.0' gemspec -- cgit v1.2.3 From d38ffd2864575cecb193c9933e0c6a40cbbaf6c3 Mon Sep 17 00:00:00 2001 From: varac Date: Sat, 1 Oct 2016 01:06:45 +0200 Subject: update Gemfile.lock --- Gemfile.lock | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 4aaeb58..3615536 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -28,17 +28,17 @@ PATH GEM remote: https://rubygems.org/ specs: - activesupport (5.0.0.1) - concurrent-ruby (~> 1.0, >= 1.0.2) + activesupport (4.2.7.1) i18n (~> 0.7) + json (~> 1.7, >= 1.7.7) minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) backports (3.6.8) coffee-script (2.2.0) coffee-script-source execjs coffee-script-source (1.10.0) - concurrent-ruby (1.0.2) crack (0.4.3) safe_yaml (~> 1.0.0) daemons (1.2.4) @@ -57,6 +57,7 @@ GEM httparty (0.14.0) multi_xml (>= 0.5.2) i18n (0.7.0) + json (1.8.3) metaclass (0.0.4) mime-types (3.1) mime-types-data (~> 3.2015) @@ -135,6 +136,7 @@ PLATFORMS ruby DEPENDENCIES + activesupport (< 5.0.0) dashing! fakeweb (~> 1.3.0) gitlab! -- cgit v1.2.3 From 009594945171ce5aab15d341f36fad80dce36377 Mon Sep 17 00:00:00 2001 From: varac Date: Sat, 1 Oct 2016 01:21:42 +0200 Subject: pin two other gems that fail to install otherwise --- .gitignore | 2 ++ Gemfile | 6 ++++++ Gemfile.lock | 12 ++++++------ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 65268e8..bc62fd0 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ history.yml .*.swp assets/images/piwik.png credentials +.bundle/ +vendor/ diff --git a/Gemfile b/Gemfile index 439b890..08e8f3f 100644 --- a/Gemfile +++ b/Gemfile @@ -2,6 +2,12 @@ source 'https://rubygems.org' gem 'gitlab', :git => 'https://github.com/varac/gitlab.git', :branch => 'pipeline_support' # activesupport >= 5.0.0 requires ruby 2.2 which break jessie support gem 'activesupport', '< 5.0.0' +# both following gems will break on jessie with +# Gem::InstallError: rest-client requires Ruby version >= 2.0.0. +# even when ruby 2.1 ist installed. So we pin earlier versions: +gem 'mime-types', '2.6.2' +gem 'rest-client', '< 2.0.0' + gemspec diff --git a/Gemfile.lock b/Gemfile.lock index 3615536..f541e3d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -59,9 +59,7 @@ GEM i18n (0.7.0) json (1.8.3) metaclass (0.0.4) - mime-types (3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2016.0521) + mime-types (2.6.2) mini_portile2 (2.1.0) minitest (5.2.3) mocha (0.14.0) @@ -87,10 +85,10 @@ GEM rack-test (0.6.3) rack (>= 1.0) rake (10.1.1) - rest-client (2.0.0) + rest-client (1.8.0) http-cookie (>= 1.0.2, < 2.0) - mime-types (>= 1.16, < 4.0) - netrc (~> 0.8) + mime-types (>= 1.16, < 3.0) + netrc (~> 0.7) rufus-scheduler (2.0.24) tzinfo (>= 0.3.22) safe_yaml (1.0.4) @@ -141,9 +139,11 @@ DEPENDENCIES fakeweb (~> 1.3.0) gitlab! haml (~> 4.0.4) + mime-types (= 2.6.2) minitest (~> 5.2.0) mocha (~> 0.14.0) rake (~> 10.1.0) + rest-client (< 2.0.0) simplecov (~> 0.8.2) BUNDLED WITH -- cgit v1.2.3 From e7d8080b14a32788f120c1abde69ed0420d760b0 Mon Sep 17 00:00:00 2001 From: Varac Date: Mon, 17 Jul 2017 18:36:56 +0200 Subject: Add Nagios --- .gitignore | 1 + README.md | 2 +- dashboards/dashboard.erb | 3 ++ jobs/gitlab_build_status.rb | 5 +- jobs/nagios.rb | 123 +++++++++++++++++++++++++++----------------- 5 files changed, 84 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index bc62fd0..7ad5658 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ assets/images/piwik.png credentials .bundle/ vendor/ +nagiosharder.yml diff --git a/README.md b/README.md index 8717cfa..faa31e9 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ gem install bundler Install app dependencies using bundler: ``` -bundle install --path=vendor/bundle +bundle install --path=vendor/bundle --deployment ``` If `bundle install` fails along the way, you're probably missing Xcode command line utilities: `xcode-select --install` diff --git a/dashboards/dashboard.erb b/dashboards/dashboard.erb index babc0f2..1c97924 100644 --- a/dashboards/dashboard.erb +++ b/dashboards/dashboard.erb @@ -12,6 +12,9 @@
  • +
  • +
    +
  • diff --git a/jobs/gitlab_build_status.rb b/jobs/gitlab_build_status.rb index addfaed..be52f98 100644 --- a/jobs/gitlab_build_status.rb +++ b/jobs/gitlab_build_status.rb @@ -1,6 +1,5 @@ require 'gitlab' require 'date' -require 'pp' Gitlab.configure do |config| # API endpoint URL, default @@ -10,7 +9,7 @@ Gitlab.configure do |config| config.private_token = ENV['GITLAB_TOKEN'] end -SCHEDULER.every '300s', :first_in => 0 do +SCHEDULER.every '3000s', :first_in => 0 do broken_builds = [] # get a list of all projects @@ -35,7 +34,7 @@ SCHEDULER.every '300s', :first_in => 0 do unless proj[:status] =~ /^success|running|No builds configured$/ broken_builds << proj end - puts proj + #puts proj end failed = broken_builds.size > 0 diff --git a/jobs/nagios.rb b/jobs/nagios.rb index bda1b59..56c6f88 100644 --- a/jobs/nagios.rb +++ b/jobs/nagios.rb @@ -8,88 +8,119 @@ SCHEDULER.every '10s' do query_url: 'https://unstable.bitmask.net/cgi-bin/nagios3/', home_url: 'https://unstable.bitmask.net/nagios3/', username: 'nagiosadmin', - password: ENV['UNSTABLE_PASS'] + password: ENV['NAGIOS_UNSTABLE_PASS'] }, demo: { domain: 'demo.bitmask.i', query_url: 'https://unstable.bitmask.net/cgi-bin/nagios3/', home_url: 'https://unstable.bitmask.net/nagios3/', username: 'nagiosadmin', - password: ENV['UNSTABLE_PASS'] + password: ENV['NAGIOS_UNSTABLE_PASS'] }, dev: { domain: 'dev.bitmask.i', query_url: 'https://unstable.bitmask.net/cgi-bin/nagios3/', home_url: 'https://unstable.bitmask.net/nagios3/', username: 'nagiosadmin', - password: ENV['UNSTABLE_PASS'] + password: ENV['NAGIOS_UNSTABLE_PASS'] }, mail: { domain: 'mail.bitmask.i', query_url: 'https://unstable.bitmask.net/cgi-bin/nagios3/', home_url: 'https://unstable.bitmask.net/nagios3/', username: 'nagiosadmin', - password: ENV['UNSTABLE_PASS'] + password: ENV['NAGIOS_UNSTABLE_PASS'] }, unstable: { domain: 'unstable.bitmask.i', query_url: 'https://unstable.bitmask.net/cgi-bin/nagios3/', home_url: 'https://unstable.bitmask.net/nagios3/', username: 'nagiosadmin', - password: ENV['UNSTABLE_PASS'] + password: ENV['NAGIOS_UNSTABLE_PASS'] + }, + leap: { + domain: 'leap.se', + query_url: 'https://hare.leap.se/cgi-bin/nagios3/', + home_url: 'https://hare.leap.se/nagios3/', + username: 'nagiosadmin', + password: ENV['NAGIOS_LEAP_PASS'] }, } - environments.each do |key, env| - nag = NagiosHarder::Site.new(env[:query_url], env[:username], env[:password],'3','iso8601') - unacked = nag.service_status( - :host_status_types => [:all], - :service_status_types => [:warning, :critical, :unknown], - :service_props => [:no_scheduled_downtime, :state_unacknowledged] - ) - critical_count = 0 - critical_services = Array.new - warning_count = 0 - warning_services = Array.new - unknown_count = 0 - unknown_services = Array.new - unacked.each do |alert| - next if ! alert["host"].include? env[:domain] - next if ! tried_at_maximum(alert["attempts"]) + environments = { + leap: { + domain: 'leap.se', + query_url: 'https://hare.leap.se/cgi-bin/nagios3/', + home_url: 'https://hare.leap.se/nagios3/', + username: 'nagiosadmin', + password: ENV['NAGIOS_LEAP_PASS'] + } + } + - if alert["status"].eql? "CRITICAL" - critical_count += 1 - critical_services << alert["service"] - elsif alert["status"].eql? "WARNING" - warning_count += 1 - warning_services << alert["service"] - elsif alert["status"].eql? "UNKNOWN" - unknown_count += 1 - unknown_services << alert["service"] + environments.each do |key, env| + begin + nag = NagiosHarder::Site.new(env[:query_url], env[:username], env[:password],'3','iso8601') + #puts nag + unacked = nag.service_status( + :host_status_types => [:all], + :service_status_types => [:warning, :critical, :unknown], + :service_props => [:no_scheduled_downtime, :state_unacknowledged] + ) + #puts unacked + critical_count = 0 + critical_services = Array.new + warning_count = 0 + warning_services = Array.new + unknown_count = 0 + unknown_services = Array.new + + unacked.each do |alert| + puts alert + next if ! alert["host"].include? env[:domain] + next if ! tried_at_maximum(alert["attempts"]) + puts '=============' + if alert["status"].eql? "CRITICAL" + critical_count += 1 + critical_services << alert["service"] + elsif alert["status"].eql? "WARNING" + warning_count += 1 + warning_services << alert["service"] + elsif alert["status"].eql? "UNKNOWN" + unknown_count += 1 + unknown_services << alert["service"] + end end - end - if ['cdev.bitmask.i', 'dev.bitmask.i', 'unstable.bitmask.i'].include? env[:domain] - status = critical_count + warning_count + unknown_count > 0 ? "gray" : "green" - else - status = critical_count > 0 ? "red" : (warning_count + unknown_count > 0 ? "yellow" : "green") - end + if ['cdev.bitmask.i', 'dev.bitmask.i', 'unstable.bitmask.i'].include? env[:domain] + status = critical_count + warning_count + unknown_count > 0 ? "gray" : "green" + else + status = critical_count > 0 ? "red" : (warning_count + unknown_count > 0 ? "yellow" : "green") + end - # nagiosharder may not alert us to a problem querying nagios. - # If no problems found check that we fetch service status and - # expect to find more than 0 entries. - if critical_count == 0 and warning_count == 0 and unknown_count == 0 - if nag.service_status.length == 0 - status = "error" + # nagiosharder may not alert us to a problem querying nagios. + # If no problems found check that we fetch service status and + # expect to find more than 0 entries. + if critical_count == 0 and warning_count == 0 and unknown_count == 0 + if nag.service_status.length == 0 + status = "error" + end end - end - #puts key.to_s + ": " + critical_count.to_s - #puts critical_services.join(", ") - #puts + puts key.to_s + ": " + critical_count.to_s + puts critical_services.join(", ") + puts + + rescue => error + error.backtrace + puts 'Could not query nagios at '+env[:query_url] + status = "red" + critical_count = 1 + critical_services = ['Could not query nagios'] + end send_event('nagios-' + key.to_s, { criticals: critical_count, critical_services: critical_services, warnings: warning_count, warning_services: warning_services, -- cgit v1.2.3 From 4db50705a9bf22701ae43011d4b9485dadcaad8b Mon Sep 17 00:00:00 2001 From: Azul Date: Mon, 17 Jul 2017 19:05:54 +0200 Subject: minor: run for all environments rolling back a debug restriction to leap environment --- jobs/nagios.rb | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/jobs/nagios.rb b/jobs/nagios.rb index 56c6f88..687ac32 100644 --- a/jobs/nagios.rb +++ b/jobs/nagios.rb @@ -49,17 +49,6 @@ SCHEDULER.every '10s' do - environments = { - leap: { - domain: 'leap.se', - query_url: 'https://hare.leap.se/cgi-bin/nagios3/', - home_url: 'https://hare.leap.se/nagios3/', - username: 'nagiosadmin', - password: ENV['NAGIOS_LEAP_PASS'] - } - } - - environments.each do |key, env| begin nag = NagiosHarder::Site.new(env[:query_url], env[:username], env[:password],'3','iso8601') -- cgit v1.2.3 From d8e6b9f73339b5e73f77a418c1ba1a3206393e73 Mon Sep 17 00:00:00 2001 From: Azul Date: Mon, 17 Jul 2017 19:06:53 +0200 Subject: cleanup: jenkins - we don't use anymore --- dashboards/dashboard.erb | 16 ++--- jobs/jenkins_build_status.rb | 73 ---------------------- .../jenkins_build_status.coffee | 28 --------- .../jenkins_build_status/jenkins_build_status.html | 16 ----- .../jenkins_build_status/jenkins_build_status.scss | 56 ----------------- 5 files changed, 6 insertions(+), 183 deletions(-) delete mode 100644 jobs/jenkins_build_status.rb delete mode 100644 widgets/jenkins_build_status/jenkins_build_status.coffee delete mode 100644 widgets/jenkins_build_status/jenkins_build_status.html delete mode 100644 widgets/jenkins_build_status/jenkins_build_status.scss diff --git a/dashboards/dashboard.erb b/dashboards/dashboard.erb index 1c97924..5c86d7f 100644 --- a/dashboards/dashboard.erb +++ b/dashboards/dashboard.erb @@ -2,28 +2,24 @@
  • -
  • -
    -
  • - -
  • +
  • -
  • +
  • -
  • +
  • -
  • +
  • -
  • +
  • -
  • +
  • diff --git a/jobs/jenkins_build_status.rb b/jobs/jenkins_build_status.rb deleted file mode 100644 index 267b8da..0000000 --- a/jobs/jenkins_build_status.rb +++ /dev/null @@ -1,73 +0,0 @@ -require 'net/http' -require 'json' - -JENKINS_URI = "https://jenkins.leap.se/" - -JENKINS_AUTH = { - 'name' => 'nobody', - 'password' => 'nopw' -} - -SCHEDULER.every '10s' do - - json = getFromJenkins(JENKINS_URI + 'api/json?pretty=true') - - failedJobs = Array.new - puts failedJobs - succeededJobs = Array.new - array = json['jobs'] - array.each { - |job| - - next if job['color'] == 'disabled' - next if job['color'] == 'notbuilt' - next if job['color'] == 'blue' - next if job['color'] == 'blue_anime' - - jobStatus = ''; - if job['color'] == 'yellow' || job['color'] == 'yellow_anime' - jobStatus = getFromJenkins(job['url'] + 'lastUnstableBuild/api/json') - elsif job['color'] == 'aborted' || job['color'] == 'aborted_anime' - jobStatus = getFromJenkins(job['url'] + 'lastUnsuccessfulBuild/api/json') - else - jobStatus = getFromJenkins(job['url'] + 'lastFailedBuild/api/json') - end - - culprits = jobStatus['culprits'] - - culpritName = getNameFromCulprits(culprits) - if culpritName != '' - culpritName = culpritName.partition('<').first - end - - failedJobs.push({ label: job['name'], value: culpritName}) - } - - failed = failedJobs.size > 0 - - send_event('jenkinsBuildStatus', { failedJobs: failedJobs, succeededJobs: succeededJobs, failed: failed }) -end - -def getFromJenkins(path) - - uri = URI.parse(path) - - http = Net::HTTP.new(uri.host, uri.port) - http.use_ssl = true - request = Net::HTTP::Get.new(uri.request_uri) - ##if JENKINS_AUTH['name'] - # request.basic_auth(JENKINS_AUTH['name'], JENKINS_AUTH['password']) - #end - response = http.request(request) - - json = JSON.parse(response.body) - return json -end - -def getNameFromCulprits(culprits) - culprits.each { - |culprit| - return culprit['fullName'] - } - return '' -end diff --git a/widgets/jenkins_build_status/jenkins_build_status.coffee b/widgets/jenkins_build_status/jenkins_build_status.coffee deleted file mode 100644 index d7d8630..0000000 --- a/widgets/jenkins_build_status/jenkins_build_status.coffee +++ /dev/null @@ -1,28 +0,0 @@ -class Dashing.JenkinsBuildStatus extends Dashing.Widget - - lastPlayed: 0 - timeBetweenSounds: 300000 - - onData: (data) -> - if data.failed - $(@node).find('div.build-failed').show() - $(@node).find('div.build-succeeded').hide() - $(@node).css("background-color", "red") - - if 'speechSynthesis' of window - @playSoundForUser data.failedJobs[0].value if Date.now() - @lastPlayed > @timeBetweenSounds - else - $(@node).find('div.build-failed').hide() - $(@node).find('div.build-succeeded').show() - $(@node).css("background-color", "#12b0c5") - - playSoundForUser: (user) -> - @lastPlayed = Date.now() - texts = ["#{user} has broken the build.", "The build is broken by #{user}", "#{user} is great, but lacks some programming skills", "Oops, I did it again."] - textNr = Math.floor((Math.random() * texts.length)); - @playSound texts[textNr] - - playSound: (text) -> - msg = new SpeechSynthesisUtterance(text) - msg.voice = speechSynthesis.getVoices()[0] - speechSynthesis.speak msg \ No newline at end of file diff --git a/widgets/jenkins_build_status/jenkins_build_status.html b/widgets/jenkins_build_status/jenkins_build_status.html deleted file mode 100644 index 472bc73..0000000 --- a/widgets/jenkins_build_status/jenkins_build_status.html +++ /dev/null @@ -1,16 +0,0 @@ -
    -

    FAILED

    -
      -
    • -
      -
      -
    • -
    -
    - -
    -

    All builds are successful

    - -
    - -

    diff --git a/widgets/jenkins_build_status/jenkins_build_status.scss b/widgets/jenkins_build_status/jenkins_build_status.scss deleted file mode 100644 index 2e475b0..0000000 --- a/widgets/jenkins_build_status/jenkins_build_status.scss +++ /dev/null @@ -1,56 +0,0 @@ -// ---------------------------------------------------------------------------- -// Sass declarations -// ---------------------------------------------------------------------------- -$background-color: #ec663c; -$title-color: rgba(255, 255, 255, 0.7); -$label-color: rgba(255, 255, 255, 0.7); -$value-color: #fff; - -// ---------------------------------------------------------------------------- -// Widget-text styles -// ---------------------------------------------------------------------------- -.widget-jenkins-build-status { - - background-color: $background-color; - - .title { - color: $title-color; - } - .updated-at { - color: rgba(255, 255, 255, 0.7); - } - - ol, ul { - margin: 0 15px; - text-align: left; - color: $label-color; - } - - li { - margin-bottom: 5px; - font-size: 20px; - } - - .label { - color: $label-color; - } - - .value { - margin-left: 12px; - font-weight: 600; - color: $value-color; - } - - .updated-at { - color: rgba(0, 0, 0, 0.3); - } - - .build-failed { - display: none; - } - - .fa { - font-size: 10em; - color: $label-color; - } -} -- cgit v1.2.3 From 90be5e3859fbdd02202720716894b6b5261ab0e3 Mon Sep 17 00:00:00 2001 From: Azul Date: Mon, 17 Jul 2017 20:51:46 +0200 Subject: feat: only alert for failing master builds --- Gemfile | 3 +-- Gemfile.lock | 16 +++++----------- jobs/gitlab_build_status.rb | 18 ++++++++++-------- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/Gemfile b/Gemfile index 08e8f3f..a1601d1 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,5 @@ source 'https://rubygems.org' -gem 'gitlab', :git => 'https://github.com/varac/gitlab.git', :branch => 'pipeline_support' +gem 'gitlab' # activesupport >= 5.0.0 requires ruby 2.2 which break jessie support gem 'activesupport', '< 5.0.0' # both following gems will break on jessie with @@ -8,7 +8,6 @@ gem 'activesupport', '< 5.0.0' gem 'mime-types', '2.6.2' gem 'rest-client', '< 2.0.0' - gemspec gem "nagiosharder" diff --git a/Gemfile.lock b/Gemfile.lock index f541e3d..9629ed0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,12 +1,3 @@ -GIT - remote: https://github.com/varac/gitlab.git - revision: a2d105128aab0226082bbdabf7da0e24892e5b79 - branch: pipeline_support - specs: - gitlab (3.7.0) - httparty - terminal-table - PATH remote: . specs: @@ -48,6 +39,9 @@ GEM eventmachine (1.2.0.1) execjs (2.0.2) fakeweb (1.3.0) + gitlab (4.2.0) + httparty + terminal-table haml (4.0.7) tilt hashie (1.2.0) @@ -137,7 +131,7 @@ DEPENDENCIES activesupport (< 5.0.0) dashing! fakeweb (~> 1.3.0) - gitlab! + gitlab haml (~> 4.0.4) mime-types (= 2.6.2) minitest (~> 5.2.0) @@ -147,4 +141,4 @@ DEPENDENCIES simplecov (~> 0.8.2) BUNDLED WITH - 1.11.2 + 1.14.6 diff --git a/jobs/gitlab_build_status.rb b/jobs/gitlab_build_status.rb index be52f98..317118d 100644 --- a/jobs/gitlab_build_status.rb +++ b/jobs/gitlab_build_status.rb @@ -14,18 +14,20 @@ SCHEDULER.every '3000s', :first_in => 0 do # get a list of all projects projects = Gitlab.group(ENV['GITLAB_GROUP_ID']).projects.map do |proj| - { :id => proj['id'], :name => proj['name'], :path => proj['path_with_namespace'] } + { :id => proj['id'], :name => proj['name'], :path => proj['path_with_namespace'], + :default_branch => proj['default_branch']} end projects.each do |proj| - begin - pipeline = Gitlab.pipelines(proj[:id], { per_page: 1 }) + pipelines = Gitlab.pipelines(proj[:id], { per_page: 10 }) #pp pipeline[0] - proj[:status] = pipeline[0].status - proj[:pipeline_id] = pipeline[0].id - proj[:ref] = pipeline[0].ref - date = DateTime.parse(pipeline[0].updated_at).strftime("%F %T") + pipeline = pipelines.find{|p| p.ref == proj[:default_branch]} + next if pipeline.nil? + proj[:status] = pipeline.status + proj[:pipeline_id] = pipeline.id + proj[:ref] = pipeline.ref + date = DateTime.parse(pipeline.updated_at).strftime("%F %T") proj[:date] = date rescue proj[:status] = 'No builds configured' @@ -34,7 +36,7 @@ SCHEDULER.every '3000s', :first_in => 0 do unless proj[:status] =~ /^success|running|No builds configured$/ broken_builds << proj end - #puts proj + puts proj end failed = broken_builds.size > 0 -- cgit v1.2.3 From 067b5a37801e8a5b6822060ff10200e5aae428eb Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 18 Jul 2017 09:18:08 +0200 Subject: nagios: better error reporting, less noise --- jobs/nagios.rb | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/jobs/nagios.rb b/jobs/nagios.rb index 687ac32..34a5487 100644 --- a/jobs/nagios.rb +++ b/jobs/nagios.rb @@ -67,10 +67,8 @@ SCHEDULER.every '10s' do unknown_services = Array.new unacked.each do |alert| - puts alert next if ! alert["host"].include? env[:domain] next if ! tried_at_maximum(alert["attempts"]) - puts '=============' if alert["status"].eql? "CRITICAL" critical_count += 1 critical_services << alert["service"] @@ -98,16 +96,14 @@ SCHEDULER.every '10s' do end end - puts key.to_s + ": " + critical_count.to_s - puts critical_services.join(", ") - puts + puts "#{key}: #{critical_count} (#{critical_services.join(', ')})" rescue => error - error.backtrace puts 'Could not query nagios at '+env[:query_url] + puts error status = "red" critical_count = 1 - critical_services = ['Could not query nagios'] + critical_services = ["Could not query nagios #{error}"] end send_event('nagios-' + key.to_s, { -- cgit v1.2.3 From 60cecf2f1815dfa51ea64091cc3afcda47438623 Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 18 Jul 2017 09:21:59 +0200 Subject: nagios: only check every minute --- jobs/nagios.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jobs/nagios.rb b/jobs/nagios.rb index 34a5487..1ea1444 100644 --- a/jobs/nagios.rb +++ b/jobs/nagios.rb @@ -1,4 +1,4 @@ -SCHEDULER.every '10s' do +SCHEDULER.every '60s' do require 'bundler/setup' require 'nagiosharder' -- cgit v1.2.3 From b207ea5f24cb52d5818d04d2b0c872cc8598628e Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 18 Jul 2017 12:57:44 +0200 Subject: refactor: GitlabStats to collect stats from gitlab Want to add stats about merge requests in the future while still fetching each project once. --- jobs/gitlab_build_status.rb | 34 ++++------------------------------ lib/gitlab_stats.rb | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 30 deletions(-) create mode 100644 lib/gitlab_stats.rb diff --git a/jobs/gitlab_build_status.rb b/jobs/gitlab_build_status.rb index 317118d..7febcae 100644 --- a/jobs/gitlab_build_status.rb +++ b/jobs/gitlab_build_status.rb @@ -1,39 +1,13 @@ -require 'gitlab' -require 'date' +require 'gitlab_stats' -Gitlab.configure do |config| - # API endpoint URL, default - config.endpoint = ENV['GITLAB_ENDPOINT'] - - # User's private token or OAuth2 access token - config.private_token = ENV['GITLAB_TOKEN'] -end SCHEDULER.every '3000s', :first_in => 0 do broken_builds = [] - # get a list of all projects - projects = Gitlab.group(ENV['GITLAB_GROUP_ID']).projects.map do |proj| - { :id => proj['id'], :name => proj['name'], :path => proj['path_with_namespace'], - :default_branch => proj['default_branch']} - end - - projects.each do |proj| - begin - pipelines = Gitlab.pipelines(proj[:id], { per_page: 10 }) - #pp pipeline[0] - pipeline = pipelines.find{|p| p.ref == proj[:default_branch]} - next if pipeline.nil? - proj[:status] = pipeline.status - proj[:pipeline_id] = pipeline.id - proj[:ref] = pipeline.ref - date = DateTime.parse(pipeline.updated_at).strftime("%F %T") - proj[:date] = date - rescue - proj[:status] = 'No builds configured' - end + GitlabStats.projects.each do |proj| + GitlabStats.add_pipeline_stats_to proj - unless proj[:status] =~ /^success|running|No builds configured$/ + unless proj[:status] =~ /^success|running|No builds configured/ broken_builds << proj end puts proj diff --git a/lib/gitlab_stats.rb b/lib/gitlab_stats.rb new file mode 100644 index 0000000..5b41263 --- /dev/null +++ b/lib/gitlab_stats.rb @@ -0,0 +1,42 @@ +require 'gitlab' +require 'date' + +Gitlab.configure do |config| + # API endpoint URL, default + config.endpoint = ENV['GITLAB_ENDPOINT'] + + # User's private token or OAuth2 access token + config.private_token = ENV['GITLAB_TOKEN'] +end + +module GitlabStats + # get a list of all projects + def self.projects + Gitlab.group(ENV['GITLAB_GROUP_ID']).projects.map do |proj| + { :id => proj['id'], :name => proj['name'], :path => proj['path_with_namespace'], + :default_branch => proj['default_branch']} + end + end + + def self.add_pipeline_stats_to(proj) + pipelines = Gitlab.pipelines(proj[:id], { per_page: 10 }) + #pp pipeline[0] + pipeline = pipelines.find{|p| p.ref == proj[:default_branch]} + if pipeline.nil? + if pipelines.any? + proj[:status] = 'No builds run for default branch lately' + else + proj[:status] = 'No builds configured' + end + return + end + proj[:status] = pipeline.status + proj[:pipeline_id] = pipeline.id + proj[:ref] = pipeline.ref + date = DateTime.parse(pipeline.updated_at).strftime("%F %T") + proj[:date] = date + rescue e + proj[:status] = "Error: #{e}" + end + +end -- cgit v1.2.3 From bf3d53d415bdc8daef7313c8db0916056abff5f4 Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 18 Jul 2017 13:55:06 +0200 Subject: minor: increase frequency of gitlab runs also print the duration of going through all projects. --- jobs/gitlab_build_status.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jobs/gitlab_build_status.rb b/jobs/gitlab_build_status.rb index 7febcae..f2f669a 100644 --- a/jobs/gitlab_build_status.rb +++ b/jobs/gitlab_build_status.rb @@ -1,8 +1,9 @@ require 'gitlab_stats' -SCHEDULER.every '3000s', :first_in => 0 do +SCHEDULER.every '300s', :first_in => 0 do broken_builds = [] + started = Time.now GitlabStats.projects.each do |proj| GitlabStats.add_pipeline_stats_to proj @@ -16,4 +17,5 @@ SCHEDULER.every '3000s', :first_in => 0 do failed = broken_builds.size > 0 send_event('gitlab-builds', { failed: failed, header: "Gitlab builds", broken_builds: broken_builds }) + puts "Going through all projects took: #{started - Time.now}." end -- cgit v1.2.3 From fc531b2895eb57d675c0037cc5d3381ff664f5d4 Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 18 Jul 2017 14:13:16 +0200 Subject: feat: only check builds where enabled and not archived also reduced time between checks to 2 minutes and looking at last 50 builds. --- jobs/gitlab_build_status.rb | 8 +++++--- lib/gitlab_stats.rb | 8 ++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/jobs/gitlab_build_status.rb b/jobs/gitlab_build_status.rb index f2f669a..fdfe8a8 100644 --- a/jobs/gitlab_build_status.rb +++ b/jobs/gitlab_build_status.rb @@ -1,21 +1,23 @@ require 'gitlab_stats' -SCHEDULER.every '300s', :first_in => 0 do +SCHEDULER.every '120s', :first_in => 0 do broken_builds = [] started = Time.now GitlabStats.projects.each do |proj| + next if proj[:archived] + next unless proj[:builds_enabled] GitlabStats.add_pipeline_stats_to proj + puts "#{proj[:name]} (#{proj[:ref]}): #{proj[:status]}" unless proj[:status] =~ /^success|running|No builds configured/ broken_builds << proj end - puts proj end failed = broken_builds.size > 0 send_event('gitlab-builds', { failed: failed, header: "Gitlab builds", broken_builds: broken_builds }) - puts "Going through all projects took: #{started - Time.now}." + puts "Going through all projects took: #{Time.now - started}." end diff --git a/lib/gitlab_stats.rb b/lib/gitlab_stats.rb index 5b41263..20b040a 100644 --- a/lib/gitlab_stats.rb +++ b/lib/gitlab_stats.rb @@ -13,13 +13,17 @@ module GitlabStats # get a list of all projects def self.projects Gitlab.group(ENV['GITLAB_GROUP_ID']).projects.map do |proj| - { :id => proj['id'], :name => proj['name'], :path => proj['path_with_namespace'], + { :id => proj['id'], + :name => proj['name'], + :path => proj['path_with_namespace'], + :archived => proj['archived'], + :builds_enabled => proj['builds_enabled'], :default_branch => proj['default_branch']} end end def self.add_pipeline_stats_to(proj) - pipelines = Gitlab.pipelines(proj[:id], { per_page: 10 }) + pipelines = Gitlab.pipelines(proj[:id], { per_page: 50 }) #pp pipeline[0] pipeline = pipelines.find{|p| p.ref == proj[:default_branch]} if pipeline.nil? -- cgit v1.2.3 From 053edccaa06a27bd6e93e8c6aeaee8ef718c8ad4 Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 18 Jul 2017 19:54:59 +0200 Subject: gitlab: prefix log with timestamp and gitlab --- jobs/gitlab_build_status.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jobs/gitlab_build_status.rb b/jobs/gitlab_build_status.rb index fdfe8a8..57fe0b0 100644 --- a/jobs/gitlab_build_status.rb +++ b/jobs/gitlab_build_status.rb @@ -10,7 +10,7 @@ SCHEDULER.every '120s', :first_in => 0 do next unless proj[:builds_enabled] GitlabStats.add_pipeline_stats_to proj - puts "#{proj[:name]} (#{proj[:ref]}): #{proj[:status]}" + puts "#{Time.now} gitlab: #{proj[:name]} (#{proj[:ref]}): #{proj[:status]}" unless proj[:status] =~ /^success|running|No builds configured/ broken_builds << proj end @@ -19,5 +19,5 @@ SCHEDULER.every '120s', :first_in => 0 do failed = broken_builds.size > 0 send_event('gitlab-builds', { failed: failed, header: "Gitlab builds", broken_builds: broken_builds }) - puts "Going through all projects took: #{Time.now - started}." + puts "#{Time.now} gitlab: Going through all projects took: #{Time.now - started}." end -- cgit v1.2.3 From 929138d1a99bac1ef12eea10425fbeb291535c2b Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 18 Jul 2017 20:20:03 +0200 Subject: style: highlight failing projects rather than timestamp --- dashboards/dashboard.erb | 4 ++-- widgets/gitlab_build_status/gitlab_build_status.html | 2 +- widgets/gitlab_build_status/gitlab_build_status.scss | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dashboards/dashboard.erb b/dashboards/dashboard.erb index 5c86d7f..50b81c6 100644 --- a/dashboards/dashboard.erb +++ b/dashboards/dashboard.erb @@ -1,5 +1,5 @@ -
  • -
    +
  • +
  • diff --git a/widgets/gitlab_build_status/gitlab_build_status.html b/widgets/gitlab_build_status/gitlab_build_status.html index 00d799b..ebac828 100644 --- a/widgets/gitlab_build_status/gitlab_build_status.html +++ b/widgets/gitlab_build_status/gitlab_build_status.html @@ -4,7 +4,7 @@
  • -
    -
    ()
    +
    ( )
  • diff --git a/widgets/gitlab_build_status/gitlab_build_status.scss b/widgets/gitlab_build_status/gitlab_build_status.scss index 46e867d..2d9a9f6 100644 --- a/widgets/gitlab_build_status/gitlab_build_status.scss +++ b/widgets/gitlab_build_status/gitlab_build_status.scss @@ -3,8 +3,8 @@ // ---------------------------------------------------------------------------- $background-color: #ec663c; $title-color: rgba(255, 255, 255, 0.7); -$label-color: rgba(255, 255, 255, 0.7); -$value-color: #fff; +$label-color: #fff; +$value-color: rgba(255, 255, 255, 0.7); // ---------------------------------------------------------------------------- // Widget-text styles @@ -32,12 +32,12 @@ $value-color: #fff; } .label { + font-weight: 600; color: $label-color; } .value { margin-left: 12px; - font-weight: 600; color: $value-color; } -- cgit v1.2.3 From 742cc0787663280fc917bb0435e078c2c1aa376b Mon Sep 17 00:00:00 2001 From: Azul Date: Wed, 19 Jul 2017 11:27:02 +0200 Subject: gitlab: show running builds --- jobs/gitlab_build_status.rb | 11 ++++++++++- widgets/gitlab_build_status/gitlab_build_status.coffee | 12 ++++++++++-- widgets/gitlab_build_status/gitlab_build_status.html | 11 +++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/jobs/gitlab_build_status.rb b/jobs/gitlab_build_status.rb index 57fe0b0..78867cd 100644 --- a/jobs/gitlab_build_status.rb +++ b/jobs/gitlab_build_status.rb @@ -3,6 +3,7 @@ require 'gitlab_stats' SCHEDULER.every '120s', :first_in => 0 do broken_builds = [] + running_builds = [] started = Time.now GitlabStats.projects.each do |proj| @@ -14,10 +15,18 @@ SCHEDULER.every '120s', :first_in => 0 do unless proj[:status] =~ /^success|running|No builds configured/ broken_builds << proj end + if proj[:status] == 'running' + running_builds << proj + end end failed = broken_builds.size > 0 + running = running_builds.size > 0 - send_event('gitlab-builds', { failed: failed, header: "Gitlab builds", broken_builds: broken_builds }) + send_event 'gitlab-builds', failed: failed, + running: running, + header: "Gitlab builds", + broken_builds: broken_builds, + running_builds: running_builds puts "#{Time.now} gitlab: Going through all projects took: #{Time.now - started}." end diff --git a/widgets/gitlab_build_status/gitlab_build_status.coffee b/widgets/gitlab_build_status/gitlab_build_status.coffee index e501aed..d2faf3e 100644 --- a/widgets/gitlab_build_status/gitlab_build_status.coffee +++ b/widgets/gitlab_build_status/gitlab_build_status.coffee @@ -1,11 +1,19 @@ class Dashing.GitlabBuildStatus extends Dashing.Widget onData: (data) -> + color = "#50BA5B" + if data.running + color = "#DA9A30" + $(@node).find('div.gitlab-build-running').show() + else + $(@node).find('div.gitlab-build-running').hide() + if data.failed + color = "red" $(@node).find('div.gitlab-build-failed').show() $(@node).find('div.gitlab-build-succeeded').hide() - $(@node).css("background-color", "red") else $(@node).find('div.gitlab-build-failed').hide() $(@node).find('div.gitlab-build-succeeded').show() - $(@node).css("background-color", "#50BA5B") + + $(@node).css("background-color", color) diff --git a/widgets/gitlab_build_status/gitlab_build_status.html b/widgets/gitlab_build_status/gitlab_build_status.html index ebac828..cb2f566 100644 --- a/widgets/gitlab_build_status/gitlab_build_status.html +++ b/widgets/gitlab_build_status/gitlab_build_status.html @@ -13,4 +13,15 @@

    All Gitlab builds are successful

    +
    +

    RUNNING

    +
      +
    • + +
      -
      +
      ( )
      +
    • +
    +
    +

    -- cgit v1.2.3 From 8fbcfe80e29f480b6acac313817d2d311b2f9514 Mon Sep 17 00:00:00 2001 From: Azul Date: Wed, 19 Jul 2017 11:38:09 +0200 Subject: initial gitlab ci --- .gitlab-ci.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..f68ed76 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,7 @@ +language: ruby +rvm: + - 2.3.0 + - 2.2.4 + - 2.1.8 + +script: "rake test" -- cgit v1.2.3 From bd5a828018b97363e694c18ed4f15b6df8ecf187 Mon Sep 17 00:00:00 2001 From: Azul Date: Wed, 19 Jul 2017 11:43:39 +0200 Subject: ci: get tests to run on gitlab --- .gitlab-ci.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f68ed76..1043af1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,4 @@ -language: ruby -rvm: - - 2.3.0 - - 2.2.4 - - 2.1.8 +image: 0xacab.org:4567/leap/docker/ruby:latest -script: "rake test" +test: + script: "rake test" -- cgit v1.2.3 From ca7951262b984f128ac6a663d0c39f4e00e4f9d0 Mon Sep 17 00:00:00 2001 From: Azul Date: Wed, 19 Jul 2017 11:59:46 +0200 Subject: ci: add deploy stage --- .gitlab-ci.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1043af1..5907ab5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,31 @@ image: 0xacab.org:4567/leap/docker/ruby:latest test: + stage: test script: "rake test" + +deploy: + stage: deploy + environment: + name: production + script: + - ssh dashboard@hare.leap.se "cd dashboard ; git pull" + - ssh dashboard@hare.leap.se "cd dashboard ; source credentials && bin/dashing stop ; bin/dashing start" + before_script: + # https://docs.gitlab.com/ce/ci/ssh_keys/README.html + # Install ssh-agent if not already installed, it is required by Docker. + # (change apt-get to yum if you use a CentOS-based image) + - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )' + + # Run ssh-agent (inside the build environment) + - eval $(ssh-agent -s) + + # Add the SSH key stored in SSH_PRIVATE_KEY variable to the agent store + - ssh-add <(echo "$SSH_PRIVATE_KEY") + + # For Docker builds disable host key checking. Be aware that by adding that + # you are suspectible to man-in-the-middle attacks. + # WARNING: Use this only with the Docker executor, if you use it with shell + # you will overwrite your user's SSH config. + - mkdir -p ~/.ssh + - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' -- cgit v1.2.3 From c61771fad424a2642a67300f7c5d69cc2cfb5611 Mon Sep 17 00:00:00 2001 From: Azul Date: Wed, 19 Jul 2017 12:03:12 +0200 Subject: ci: bundle gems for running tests --- .gitlab-ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5907ab5..e329f88 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,7 +2,9 @@ image: 0xacab.org:4567/leap/docker/ruby:latest test: stage: test - script: "rake test" + script: "bundle exec rake test" + before_script: + - bundle deploy: stage: deploy -- cgit v1.2.3 From 758b0de80525957059e579b5391c367c82be9da8 Mon Sep 17 00:00:00 2001 From: Azul Date: Wed, 19 Jul 2017 12:06:13 +0200 Subject: ci: cache gems between builds --- .gitlab-ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e329f88..ccbc2c6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,11 @@ image: 0xacab.org:4567/leap/docker/ruby:latest +# Cache gems in between builds +cache: + key: shared + paths: + - vendor/ruby + test: stage: test script: "bundle exec rake test" -- cgit v1.2.3 From b80d7af9fe536a0f401b1b33c321654c12f7a57c Mon Sep 17 00:00:00 2001 From: Azul Date: Wed, 19 Jul 2017 12:09:46 +0200 Subject: ci: add javascript runtime --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ccbc2c6..fa28ee7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,7 +10,9 @@ test: stage: test script: "bundle exec rake test" before_script: + - sudo apt update && sudo apt install nodejs - bundle + allow_failure: true deploy: stage: deploy -- cgit v1.2.3 From d1238e27e1f7cd846fc698b59b5ad042c3c2217b Mon Sep 17 00:00:00 2001 From: Varac Date: Wed, 19 Jul 2017 17:22:17 +0200 Subject: Remove broken test stage --- .gitlab-ci.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fa28ee7..ab245e1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,13 +6,14 @@ cache: paths: - vendor/ruby -test: - stage: test - script: "bundle exec rake test" - before_script: - - sudo apt update && sudo apt install nodejs - - bundle - allow_failure: true +#test: +# stage: test +# script: "bundle exec rake test" +# before_script: +# - sudo apt update && sudo apt install nodejs +# - bundle +# allow_failure: true + deploy: stage: deploy -- cgit v1.2.3 From a903156109a4b93ddb3bcd6fb1401f92f88056eb Mon Sep 17 00:00:00 2001 From: Varac Date: Wed, 19 Jul 2017 18:10:53 +0200 Subject: Use proper systemd user service for dashboard --- .gitlab-ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ab245e1..37a6059 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -20,8 +20,7 @@ deploy: environment: name: production script: - - ssh dashboard@hare.leap.se "cd dashboard ; git pull" - - ssh dashboard@hare.leap.se "cd dashboard ; source credentials && bin/dashing stop ; bin/dashing start" + - ssh dashboard@hare.leap.se "cd ~/dashboard ; git pull; systemctl --user restart dashboard.service" before_script: # https://docs.gitlab.com/ce/ci/ssh_keys/README.html # Install ssh-agent if not already installed, it is required by Docker. -- cgit v1.2.3 From d88c47105d804a9166fb865ea6fedf589d054b3c Mon Sep 17 00:00:00 2001 From: Varac Date: Wed, 19 Jul 2017 18:42:27 +0200 Subject: Set XDG_RUNTIME_DIR properly Otherwise systemctl will complain with: Failed to get D-Bus connection: Connection refused --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 37a6059..9fae8a8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,8 +19,6 @@ deploy: stage: deploy environment: name: production - script: - - ssh dashboard@hare.leap.se "cd ~/dashboard ; git pull; systemctl --user restart dashboard.service" before_script: # https://docs.gitlab.com/ce/ci/ssh_keys/README.html # Install ssh-agent if not already installed, it is required by Docker. @@ -39,3 +37,5 @@ deploy: # you will overwrite your user's SSH config. - mkdir -p ~/.ssh - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' + script: + - ssh dashboard@hare.leap.se "export XDG_RUNTIME_DIR=/run/user/$UID; cd ~/dashboard ; git pull; systemctl --user restart dashboard.service" -- cgit v1.2.3 From a5dba836fc567f6943d9f3032706e92ad59de209 Mon Sep 17 00:00:00 2001 From: Varac Date: Wed, 19 Jul 2017 18:44:17 +0200 Subject: Dont expand variable locally --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9fae8a8..f3e5183 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -38,4 +38,4 @@ deploy: - mkdir -p ~/.ssh - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' script: - - ssh dashboard@hare.leap.se "export XDG_RUNTIME_DIR=/run/user/$UID; cd ~/dashboard ; git pull; systemctl --user restart dashboard.service" + - ssh dashboard@hare.leap.se 'export XDG_RUNTIME_DIR=/run/user/$UID; cd ~/dashboard ; git pull; systemctl --user restart dashboard.service' -- cgit v1.2.3 From 5f1f1c0edfa877f18cb28682be087b4ac24e033f Mon Sep 17 00:00:00 2001 From: Varac Date: Mon, 11 Sep 2017 12:41:09 +0200 Subject: Code blocks show up nices in editor --- README.md | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index faa31e9..e157049 100644 --- a/README.md +++ b/README.md @@ -6,36 +6,29 @@ Check out http://shopify.github.com/dashing for more information. Install bundler if required -``` -gem install bundler -``` + gem install bundler Install app dependencies using bundler: -``` -bundle install --path=vendor/bundle --deployment -``` + bundle install --path=vendor/bundle --deployment If `bundle install` fails along the way, you're probably missing Xcode command line utilities: `xcode-select --install` If you're getting `fatal error: 'openssl/ssl.h' file not found` installing `eventmachine` dependency, you can try install it using the following command (make sure you have openssl installed): -```bash -gem install eventmachine -v '1.0.7' -- --with-cppflags='-I/usr/local/opt/openssl/include' -``` + + gem install eventmachine -v '1.0.7' -- --with-cppflags='-I/usr/local/opt/openssl/include' To gather the data, the dashboard needs access to your GoCI account & a GitHub token. You can store them and make them available to the app like so: -```bash -cp credentials_example credentials -source credentials -``` + + cp credentials_example credentials + source credentials For this dashboard you furthermore need nodejs installed, e.g. -```bash -nodenv local 0.10.36 -``` + + nodenv local 0.10.36 Use dashing to build the dashboard or start it locally. -``` -dashing start -``` + + dashing start + See `dashing --help` for usage. -- cgit v1.2.3 From 4433b0815b89797c870d6f74186b45bdc360c25a Mon Sep 17 00:00:00 2001 From: Varac Date: Mon, 11 Sep 2017 12:53:14 +0200 Subject: Fix matching of domains so cdev issues wont show up under dev --- jobs/nagios.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jobs/nagios.rb b/jobs/nagios.rb index 1ea1444..71dadc4 100644 --- a/jobs/nagios.rb +++ b/jobs/nagios.rb @@ -67,7 +67,7 @@ SCHEDULER.every '60s' do unknown_services = Array.new unacked.each do |alert| - next if ! alert["host"].include? env[:domain] + next if ! alert["host"].include? '.'+env[:domain] next if ! tried_at_maximum(alert["attempts"]) if alert["status"].eql? "CRITICAL" critical_count += 1 -- cgit v1.2.3 From 2407b513ff0e3763b6b8755d7804ef69a4a53893 Mon Sep 17 00:00:00 2001 From: Varac Date: Wed, 13 Sep 2017 09:30:23 +0200 Subject: Rename Nagios leap.se to Nagios Infrastructure --- README.md | 2 +- dashboards/dashboard.erb | 3 +-- jobs/nagios.rb | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e157049..7d04bcd 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,6 @@ For this dashboard you furthermore need nodejs installed, e.g. Use dashing to build the dashboard or start it locally. - dashing start + ./bin/dashing start See `dashing --help` for usage. diff --git a/dashboards/dashboard.erb b/dashboards/dashboard.erb index 50b81c6..fd677f9 100644 --- a/dashboards/dashboard.erb +++ b/dashboards/dashboard.erb @@ -9,7 +9,7 @@
  • -
    +
  • @@ -22,4 +22,3 @@
  • - diff --git a/jobs/nagios.rb b/jobs/nagios.rb index 71dadc4..97e5ae8 100644 --- a/jobs/nagios.rb +++ b/jobs/nagios.rb @@ -38,12 +38,12 @@ SCHEDULER.every '60s' do username: 'nagiosadmin', password: ENV['NAGIOS_UNSTABLE_PASS'] }, - leap: { + infrastructure: { domain: 'leap.se', query_url: 'https://hare.leap.se/cgi-bin/nagios3/', home_url: 'https://hare.leap.se/nagios3/', username: 'nagiosadmin', - password: ENV['NAGIOS_LEAP_PASS'] + password: ENV['NAGIOS_INFRASTRUCTURE_PASS'] }, } -- cgit v1.2.3 From 6be3364b35fd83e7923358d3402e45010847b39b Mon Sep 17 00:00:00 2001 From: Varac Date: Wed, 13 Sep 2017 09:49:30 +0200 Subject: Restructure dashboard layot --- dashboards/dashboard.erb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dashboards/dashboard.erb b/dashboards/dashboard.erb index fd677f9..d7a5a06 100644 --- a/dashboards/dashboard.erb +++ b/dashboards/dashboard.erb @@ -5,20 +5,20 @@
  • -
  • -
    -
  • -
  • -
    -
  • - -
  • + +
  • +
    +
  • + +
  • +
    +
  • -- cgit v1.2.3 From 96f37b3d7d659fda3e38fba6edba7cd374e6866b Mon Sep 17 00:00:00 2001 From: Varac Date: Wed, 13 Sep 2017 10:38:17 +0200 Subject: Add user.leap.se --- dashboards/dashboard.erb | 3 +++ jobs/nagios.rb | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/dashboards/dashboard.erb b/dashboards/dashboard.erb index d7a5a06..619d594 100644 --- a/dashboards/dashboard.erb +++ b/dashboards/dashboard.erb @@ -16,6 +16,9 @@
    +
  • +
    +
  • diff --git a/jobs/nagios.rb b/jobs/nagios.rb index 97e5ae8..2542f20 100644 --- a/jobs/nagios.rb +++ b/jobs/nagios.rb @@ -45,6 +45,13 @@ SCHEDULER.every '60s' do username: 'nagiosadmin', password: ENV['NAGIOS_INFRASTRUCTURE_PASS'] }, + user_leap_se: { + domain: 'leap.i', + query_url: 'https://user.leap.se/cgi-bin/nagios3/', + home_url: 'https://user.leap.se/nagios3/', + username: 'nagiosadmin', + password: ENV['NAGIOS_USER_LEAP_SE_PASS'] + }, } @@ -52,7 +59,7 @@ SCHEDULER.every '60s' do environments.each do |key, env| begin nag = NagiosHarder::Site.new(env[:query_url], env[:username], env[:password],'3','iso8601') - #puts nag + puts nag unacked = nag.service_status( :host_status_types => [:all], :service_status_types => [:warning, :critical, :unknown], -- cgit v1.2.3 From e6a20df9d772cfc1ae1224ad846e827b846f42c3 Mon Sep 17 00:00:00 2001 From: Varac Date: Tue, 3 Oct 2017 18:18:21 +0200 Subject: Dont show "manual" build status as warning on dashboard --- jobs/gitlab_build_status.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jobs/gitlab_build_status.rb b/jobs/gitlab_build_status.rb index 78867cd..560bec3 100644 --- a/jobs/gitlab_build_status.rb +++ b/jobs/gitlab_build_status.rb @@ -12,7 +12,7 @@ SCHEDULER.every '120s', :first_in => 0 do GitlabStats.add_pipeline_stats_to proj puts "#{Time.now} gitlab: #{proj[:name]} (#{proj[:ref]}): #{proj[:status]}" - unless proj[:status] =~ /^success|running|No builds configured/ + unless proj[:status] =~ /^success|running|manual|No builds configured/ broken_builds << proj end if proj[:status] == 'running' -- cgit v1.2.3 From 1fddfe5eaf83b1c54fa8761c7ef7a89ea913358e Mon Sep 17 00:00:00 2001 From: Varac Date: Wed, 4 Oct 2017 08:42:43 +0200 Subject: 0xacab.org: All Builds Are Complete --- widgets/gitlab_build_status/gitlab_build_status.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widgets/gitlab_build_status/gitlab_build_status.html b/widgets/gitlab_build_status/gitlab_build_status.html index cb2f566..504062d 100644 --- a/widgets/gitlab_build_status/gitlab_build_status.html +++ b/widgets/gitlab_build_status/gitlab_build_status.html @@ -10,7 +10,7 @@
    -

    All Gitlab builds are successful

    +

    0xacab.org: All Builds Are Complete!

    -- cgit v1.2.3 From 2917ad9519d6f872d51582d335a147bf2e5f935e Mon Sep 17 00:00:00 2001 From: Varac Date: Mon, 16 Oct 2017 14:17:27 +0200 Subject: Update Gemfile.lock --- Gemfile.lock | 64 +++++++++++++++++++++++++++++------------------------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 9629ed0..8b92256 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,42 +1,42 @@ PATH remote: . specs: - dashing (1.3.6) + smashing (1.0.0) coffee-script (~> 2.2.0) crack execjs (~> 2.0.2) gitlab nagiosharder rack (~> 1.5.4) - rufus-scheduler (~> 2.0.24) + rufus-scheduler (~> 3.2.0) sass (~> 3.2.12) sinatra (~> 1.4.4) sinatra-contrib (~> 1.4.2) sprockets (~> 2.10.1) thin (~> 1.6.1) - thor (> 0.18.1) + thor (~> 0.19) GEM remote: https://rubygems.org/ specs: - activesupport (4.2.7.1) + activesupport (4.2.10) i18n (~> 0.7) - json (~> 1.7, >= 1.7.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - backports (3.6.8) + backports (3.10.0) coffee-script (2.2.0) coffee-script-source execjs - coffee-script-source (1.10.0) + coffee-script-source (1.12.2) + concurrent-ruby (1.0.5) crack (0.4.3) safe_yaml (~> 1.0.0) daemons (1.2.4) docile (1.1.5) - domain_name (0.5.20160826) + domain_name (0.5.20170404) unf (>= 0.0.5, < 1.0.0) - eventmachine (1.2.0.1) + eventmachine (1.2.5) execjs (2.0.2) fakeweb (1.3.0) gitlab (4.2.0) @@ -48,18 +48,18 @@ GEM hike (1.2.3) http-cookie (1.0.3) domain_name (~> 0.5) - httparty (0.14.0) + httparty (0.15.6) multi_xml (>= 0.5.2) - i18n (0.7.0) - json (1.8.3) + i18n (0.9.0) + concurrent-ruby (~> 1.0) metaclass (0.0.4) mime-types (2.6.2) - mini_portile2 (2.1.0) + mini_portile2 (2.3.0) minitest (5.2.3) mocha (0.14.0) metaclass (~> 0.0.1) - multi_json (1.12.1) - multi_xml (0.5.5) + multi_json (1.12.2) + multi_xml (0.6.0) nagiosharder (0.5.0) activesupport hashie (~> 1.2.0) @@ -69,22 +69,19 @@ GEM rest-client terminal-table netrc (0.11.0) - nokogiri (1.6.8) - mini_portile2 (~> 2.1.0) - pkg-config (~> 1.1.7) - pkg-config (1.1.7) + nokogiri (1.8.1) + mini_portile2 (~> 2.3.0) rack (1.5.5) rack-protection (1.5.3) rack - rack-test (0.6.3) - rack (>= 1.0) + rack-test (0.7.0) + rack (>= 1.0, < 3) rake (10.1.1) rest-client (1.8.0) http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 3.0) netrc (~> 0.7) - rufus-scheduler (2.0.24) - tzinfo (>= 0.3.22) + rufus-scheduler (3.2.2) safe_yaml (1.0.4) sass (3.2.19) simplecov (0.8.2) @@ -92,7 +89,7 @@ GEM multi_json simplecov-html (~> 0.8.0) simplecov-html (0.8.0) - sinatra (1.4.7) + sinatra (1.4.8) rack (~> 1.5) rack-protection (~> 1.4) tilt (>= 1.3, < 3) @@ -108,37 +105,38 @@ GEM multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) - terminal-table (1.7.3) - unicode-display_width (~> 1.1.1) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) thin (1.6.4) daemons (~> 1.0, >= 1.0.9) eventmachine (~> 1.0, >= 1.0.4) rack (~> 1.0) - thor (0.19.1) - thread_safe (0.3.5) + thor (0.20.0) + thread_safe (0.3.6) tilt (1.4.1) - tzinfo (1.2.2) + tzinfo (1.2.3) thread_safe (~> 0.1) unf (0.1.4) unf_ext - unf_ext (0.0.7.2) - unicode-display_width (1.1.1) + unf_ext (0.0.7.4) + unicode-display_width (1.3.0) PLATFORMS ruby DEPENDENCIES activesupport (< 5.0.0) - dashing! fakeweb (~> 1.3.0) gitlab haml (~> 4.0.4) mime-types (= 2.6.2) minitest (~> 5.2.0) mocha (~> 0.14.0) + nagiosharder rake (~> 10.1.0) rest-client (< 2.0.0) simplecov (~> 0.8.2) + smashing! BUNDLED WITH - 1.14.6 + 1.15.2 -- cgit v1.2.3