Added GUI Environment
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/.bower.json b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/.bower.json
new file mode 100644
index 0000000..c26a5c2
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/.bower.json
@@ -0,0 +1,14 @@
+{
+  "name": "ui.bootstrap",
+  "homepage": "https://github.com/angular-ui/bootstrap",
+  "version": "0.14.3",
+  "_release": "0.14.3",
+  "_resolution": {
+    "type": "version",
+    "tag": "0.14.3",
+    "commit": "e9d6ec2a29998986cf7534f2d5ca6c5371639872"
+  },
+  "_source": "git://github.com/angular-ui/bootstrap.git",
+  "_target": "~0.14.3",
+  "_originalSource": "ui.bootstrap"
+}
\ No newline at end of file
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/.editorconfig b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/.editorconfig
new file mode 100644
index 0000000..8565360
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/.editorconfig
@@ -0,0 +1,18 @@
+# This file is for unifying the coding style for different editors and IDEs
+# editorconfig.org
+
+root = true
+
+[*]
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+# Tabs in JS unless otherwise specified
+[**.js]
+indent_style = space
+indent_size = 2
+
+[*.md]
+trim_trailing_whitespace = false
\ No newline at end of file
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/.gitattributes b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/.gitattributes
new file mode 100644
index 0000000..352204d
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/.gitattributes
@@ -0,0 +1,6 @@
+*.html  eol=lf

+*.css   eol=lf

+*.js    eol=lf

+*.md    eol=lf

+*.json  eol=lf

+*.yml   eol=lf

diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/.gitignore b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/.gitignore
new file mode 100644
index 0000000..d106a75
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/.gitignore
@@ -0,0 +1,23 @@
+lib-cov
+*.seed
+*.log
+*.csv
+*.dat
+*.out
+*.pid
+*.gz
+*.swp
+*.swo
+.DS_Store
+
+pids
+logs
+results
+dist
+# test coverage files
+coverage/
+
+node_modules
+npm-debug.log
+
+template/**/*.js
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/.jshintrc b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/.jshintrc
new file mode 100644
index 0000000..b36c317
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/.jshintrc
@@ -0,0 +1,30 @@
+{
+  "curly": true,
+  "immed": true,
+  "newcap": true,
+  "noarg": true,
+  "sub": true,
+  "boss": true,
+  "eqnull": true,
+  "quotmark": "single",
+  "trailing": true,
+  "undef": true,
+  "browser": true,
+  "jquery": true,
+  "globals": {
+    "angular": false,
+
+    // For Jasmine
+    "after"      : false,
+    "afterEach"  : false,
+    "before"     : false,
+    "beforeEach" : false,
+    "describe"   : false,
+    "expect"     : false,
+    "jasmine"    : false,
+    "module"     : false,
+    "spyOn"      : false,
+    "inject"     : false,
+    "it"         : false
+  }
+}
\ No newline at end of file
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/.npmignore b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/.npmignore
new file mode 100644
index 0000000..c447a4e
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/.npmignore
@@ -0,0 +1,39 @@
+lib-cov
+*.seed
+*.log
+*.csv
+*.dat
+*.out
+*.pid
+*.gz
+*.swp
+*.swo
+.DS_Store
+
+pids
+logs
+results
+# test coverage files
+coverage/
+
+node_modules
+npm-debug.log
+
+template/**/*.js
+
+.git
+docs
+misc
+src
+template
+.editorconfig
+.gitattributes
+.gitignore
+.jshintrc
+.travis.yml
+CONTRIBUTING.md
+Gruntfile.js
+karma.conf.js
+ROADMAP.md
+
+dist/assets
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/.travis.yml b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/.travis.yml
new file mode 100644
index 0000000..8be8967
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/.travis.yml
@@ -0,0 +1,11 @@
+language: node_js
+node_js:
+  - "0.12"
+
+before_install:
+  - export DISPLAY=:99.0
+  - sh -e /etc/init.d/xvfb start
+  - npm install --quiet -g grunt-cli karma
+
+script: grunt
+sudo: false
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/CHANGELOG.md b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/CHANGELOG.md
new file mode 100644
index 0000000..944768f
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/CHANGELOG.md
@@ -0,0 +1,1344 @@
+<a name="0.14.3"></a>
+## [0.14.3](https://github.com/angular-ui/bootstrap/compare/0.14.2...v0.14.3) (2015-10-23)
+
+
+### Bug Fixes
+
+* **alert:** allow interpolations with dismiss-on-timeout ([de24f46](https://github.com/angular-ui/bootstrap/commit/de24f46)), closes [#4665](https://github.com/angular-ui/bootstrap/issues/4665) [#4666](https://github.com/angular-ui/bootstrap/issues/4666)
+* **buttons:** double toggle on spacebar ([e8808d3](https://github.com/angular-ui/bootstrap/commit/e8808d3)), closes [#4474](https://github.com/angular-ui/bootstrap/issues/4474) [#4630](https://github.com/angular-ui/bootstrap/issues/4630)
+* **collapse:** fix collapse animation timing ([6d1cd0f](https://github.com/angular-ui/bootstrap/commit/6d1cd0f)), closes [#4493](https://github.com/angular-ui/bootstrap/issues/4493)
+* **collapse:** trigger digest after ([3144633](https://github.com/angular-ui/bootstrap/commit/3144633)), closes [#4647](https://github.com/angular-ui/bootstrap/issues/4647) [#4628](https://github.com/angular-ui/bootstrap/issues/4628) [#4561](https://github.com/angular-ui/bootstrap/issues/4561) [#4651](https://github.com/angular-ui/bootstrap/issues/4651)
+* **datepicker:** datepicker-popup nest in dropdown ([134086a](https://github.com/angular-ui/bootstrap/commit/134086a)), closes [#4197](https://github.com/angular-ui/bootstrap/issues/4197) [#4693](https://github.com/angular-ui/bootstrap/issues/4693)
+* **datepicker:** fix support for literal format on popup ([7c3c631](https://github.com/angular-ui/bootstrap/commit/7c3c631)), closes [#4635](https://github.com/angular-ui/bootstrap/issues/4635) [#4616](https://github.com/angular-ui/bootstrap/issues/4616)
+* **tooltip:** delay timeouts ([02425b8](https://github.com/angular-ui/bootstrap/commit/02425b8)), closes [#4621](https://github.com/angular-ui/bootstrap/issues/4621) [#4618](https://github.com/angular-ui/bootstrap/issues/4618)
+* **tooltip:** null scope check in isOpen watch ([1f94104](https://github.com/angular-ui/bootstrap/commit/1f94104)), closes [#4697](https://github.com/angular-ui/bootstrap/issues/4697) [#4683](https://github.com/angular-ui/bootstrap/issues/4683)
+* **tooltip:** scrollbar flashing ([6c82b2b](https://github.com/angular-ui/bootstrap/commit/6c82b2b)), closes [#4550](https://github.com/angular-ui/bootstrap/issues/4550) [#4623](https://github.com/angular-ui/bootstrap/issues/4623) [#4458](https://github.com/angular-ui/bootstrap/issues/4458)
+* **typeahead:** dangling event listeners ([94fb282](https://github.com/angular-ui/bootstrap/commit/94fb282)), closes [#4632](https://github.com/angular-ui/bootstrap/issues/4632) [#4636](https://github.com/angular-ui/bootstrap/issues/4636)
+
+### Features
+
+* **datepicker:** add templateUrl support for pickers ([1f65d87](https://github.com/angular-ui/bootstrap/commit/1f65d87)), closes [#4432](https://github.com/angular-ui/bootstrap/issues/4432)
+* **datepicker:** preserve timezone with model ([0d64aad](https://github.com/angular-ui/bootstrap/commit/0d64aad)), closes [#4676](https://github.com/angular-ui/bootstrap/issues/4676)
+* **modal:** support $uibModalInstance ([97fd37e](https://github.com/angular-ui/bootstrap/commit/97fd37e)), closes [#4638](https://github.com/angular-ui/bootstrap/issues/4638) [#4661](https://github.com/angular-ui/bootstrap/issues/4661)
+
+
+
+<a name="0.14.2"></a>
+## [0.14.2](https://github.com/angular-ui/bootstrap/compare/0.14.1...v0.14.2) (2015-10-14)
+
+
+### Bug Fixes
+
+* **progressbar:** fix percentage calculation ([feb689c](https://github.com/angular-ui/bootstrap/commit/feb689c)), closes [#4471](https://github.com/angular-ui/bootstrap/issues/4471) [#4588](https://github.com/angular-ui/bootstrap/issues/4588) [#4452](https://github.com/angular-ui/bootstrap/issues/4452)
+* **tooltip:** clean up stackedMap on scope destroy ([ebb5e18](https://github.com/angular-ui/bootstrap/commit/ebb5e18)), closes [#4610](https://github.com/angular-ui/bootstrap/issues/4610) [#4604](https://github.com/angular-ui/bootstrap/issues/4604)
+* **tooltip:** popup close delay not respected ([6daf871](https://github.com/angular-ui/bootstrap/commit/6daf871)), closes [#4597](https://github.com/angular-ui/bootstrap/issues/4597) [#4567](https://github.com/angular-ui/bootstrap/issues/4567)
+
+
+
+<a name="0.14.1"></a>
+## [0.14.1](https://github.com/angular-ui/bootstrap/compare/0.14.0...v0.14.1) (2015-10-11)
+
+
+### Bug Fixes
+
+* **accordion:** make deprecated controller work with 1.3.x ([c5e6042](https://github.com/angular-ui/bootstrap/commit/c5e6042)), closes [#4574](https://github.com/angular-ui/bootstrap/issues/4574)
+* **alert:** make deprecated controller work with 1.3.x ([e8c8ee6](https://github.com/angular-ui/bootstrap/commit/e8c8ee6)), closes [#4576](https://github.com/angular-ui/bootstrap/issues/4576)
+* **buttons:** make deprecated controller work with 1.3.x ([1e3cbd8](https://github.com/angular-ui/bootstrap/commit/1e3cbd8)), closes [#4577](https://github.com/angular-ui/bootstrap/issues/4577)
+* **carousel:** make deprecated controller work with 1.3.x ([f6c7931](https://github.com/angular-ui/bootstrap/commit/f6c7931)), closes [#4578](https://github.com/angular-ui/bootstrap/issues/4578)
+* **datepicker:** make deprecated controller work with 1.3.x ([18371ab](https://github.com/angular-ui/bootstrap/commit/18371ab)), closes [#4586](https://github.com/angular-ui/bootstrap/issues/4586)
+* **dropdown:** make deprecated controller work with 1.3.x ([ae1a87c](https://github.com/angular-ui/bootstrap/commit/ae1a87c)), closes [#4585](https://github.com/angular-ui/bootstrap/issues/4585)
+* **pagination:** make deprecated controller work with 1.3.x ([d50e8d2](https://github.com/angular-ui/bootstrap/commit/d50e8d2)), closes [#4580](https://github.com/angular-ui/bootstrap/issues/4580)
+* **progressbar:** make deprecated controller work with 1.3.x ([1c5e479](https://github.com/angular-ui/bootstrap/commit/1c5e479)), closes [#4581](https://github.com/angular-ui/bootstrap/issues/4581)
+* **rating:** make deprecated controller work with 1.3.x ([ce1114a](https://github.com/angular-ui/bootstrap/commit/ce1114a)), closes [#4582](https://github.com/angular-ui/bootstrap/issues/4582)
+* **tabs:** make deprecated controller work with 1.3.x ([685bd6a](https://github.com/angular-ui/bootstrap/commit/685bd6a)), closes [#4583](https://github.com/angular-ui/bootstrap/issues/4583)
+* **timepicker:** make deprecated controller work with 1.3.x ([00f60ee](https://github.com/angular-ui/bootstrap/commit/00f60ee)), closes [#4584](https://github.com/angular-ui/bootstrap/issues/4584)
+
+### Features
+
+* **timepicker:** add accessibility improvements ([4ebecbc](https://github.com/angular-ui/bootstrap/commit/4ebecbc)), closes [#4569](https://github.com/angular-ui/bootstrap/issues/4569) [#4573](https://github.com/angular-ui/bootstrap/issues/4573)
+
+
+
+<a name="0.14.0"></a>
+# [0.14.0](https://github.com/angular-ui/bootstrap/compare/0.13.4...0.14.0) (2015-10-09)
+
+
+### Bug Fixes
+
+* **accordion:** coerce to boolean ([b864aa9](https://github.com/angular-ui/bootstrap/commit/b864aa9)), closes [#4385](https://github.com/angular-ui/bootstrap/issues/4385)
+* **accordion:** re-expose AccordionController ([5382226](https://github.com/angular-ui/bootstrap/commit/5382226)), closes [#4524](https://github.com/angular-ui/bootstrap/issues/4524)
+* **alert:** properly pass $event as local ([eb2366f](https://github.com/angular-ui/bootstrap/commit/eb2366f)), closes [#4386](https://github.com/angular-ui/bootstrap/issues/4386) [#4387](https://github.com/angular-ui/bootstrap/issues/4387)
+* **alert:** re-expose AlertController ([f561aa9](https://github.com/angular-ui/bootstrap/commit/f561aa9)), closes [#4525](https://github.com/angular-ui/bootstrap/issues/4525)
+* **buttons:** re-expose ButtonsController ([c0dbf79](https://github.com/angular-ui/bootstrap/commit/c0dbf79)), closes [#4526](https://github.com/angular-ui/bootstrap/issues/4526)
+* **carousel:** fix reading of `noTransition` ([2e26815](https://github.com/angular-ui/bootstrap/commit/2e26815)), closes [#4325](https://github.com/angular-ui/bootstrap/issues/4325)
+* **carousel:** improve accessibility ([da71159](https://github.com/angular-ui/bootstrap/commit/da71159)), closes [#4478](https://github.com/angular-ui/bootstrap/issues/4478) [#4479](https://github.com/angular-ui/bootstrap/issues/4479)
+* **carousel:** re-enable deprecated directives ([30e8aa7](https://github.com/angular-ui/bootstrap/commit/30e8aa7)), closes [#4527](https://github.com/angular-ui/bootstrap/issues/4527)
+* **carousel:** reset $currentTransition when no slides ([0b3d5bd](https://github.com/angular-ui/bootstrap/commit/0b3d5bd)), closes [#4532](https://github.com/angular-ui/bootstrap/issues/4532) [#4390](https://github.com/angular-ui/bootstrap/issues/4390)
+* **datepicker:** add check for `contains` ([868c0e2](https://github.com/angular-ui/bootstrap/commit/868c0e2)), closes [#4423](https://github.com/angular-ui/bootstrap/issues/4423) [#4411](https://github.com/angular-ui/bootstrap/issues/4411)
+* **datepicker:** add custom class to year picker ([0ad7cb9](https://github.com/angular-ui/bootstrap/commit/0ad7cb9)), closes [#4558](https://github.com/angular-ui/bootstrap/issues/4558) [#4546](https://github.com/angular-ui/bootstrap/issues/4546)
+* **datepicker:** change to `$popup` ([65814f1](https://github.com/angular-ui/bootstrap/commit/65814f1))
+* **datepicker:** datepicker-popup nest in dropdown ([6b4267b](https://github.com/angular-ui/bootstrap/commit/6b4267b)), closes [#4489](https://github.com/angular-ui/bootstrap/issues/4489) [#4197](https://github.com/angular-ui/bootstrap/issues/4197)
+* **datepicker:** remove focus management on date selection by keyboard ([36ecf60](https://github.com/angular-ui/bootstrap/commit/36ecf60)), closes [#4409](https://github.com/angular-ui/bootstrap/issues/4409)
+* **dropdown:** ensure class is present in dropdown-menu ([92ab48e](https://github.com/angular-ui/bootstrap/commit/92ab48e)), closes [#4523](https://github.com/angular-ui/bootstrap/issues/4523) [#4442](https://github.com/angular-ui/bootstrap/issues/4442)
+* **dropdown:** restore deprecated directives ([e7c5879](https://github.com/angular-ui/bootstrap/commit/e7c5879)), closes [#4514](https://github.com/angular-ui/bootstrap/issues/4514)
+* **modal:** fix for conflicts with ngTouch module on mobile devices ([508aceb](https://github.com/angular-ui/bootstrap/commit/508aceb)), closes [#2280](https://github.com/angular-ui/bootstrap/issues/2280) [#4357](https://github.com/angular-ui/bootstrap/issues/4357)
+* **progressbar:** re-expose ProgressController ([5604e59](https://github.com/angular-ui/bootstrap/commit/5604e59)), closes [#4528](https://github.com/angular-ui/bootstrap/issues/4528)
+* **rating:** re-expose RatingController ([aede646](https://github.com/angular-ui/bootstrap/commit/aede646)), closes [#4529](https://github.com/angular-ui/bootstrap/issues/4529)
+* **tabs:** re-expose TabsetController ([435924f](https://github.com/angular-ui/bootstrap/commit/435924f)), closes [#4530](https://github.com/angular-ui/bootstrap/issues/4530)
+* **timepicker:** re-expose TimepickerController ([3aa9841](https://github.com/angular-ui/bootstrap/commit/3aa9841)), closes [#4531](https://github.com/angular-ui/bootstrap/issues/4531)
+* **tooltip:** add display block to style ([b413a22](https://github.com/angular-ui/bootstrap/commit/b413a22)), closes [#4363](https://github.com/angular-ui/bootstrap/issues/4363) [#4379](https://github.com/angular-ui/bootstrap/issues/4379)
+* **tooltip:** check for ttScope in $$postDigest ([01b9624](https://github.com/angular-ui/bootstrap/commit/01b9624)), closes [#4555](https://github.com/angular-ui/bootstrap/issues/4555) [#4552](https://github.com/angular-ui/bootstrap/issues/4552)
+* **tooltip:** correct flash of reposition ([8fee75d](https://github.com/angular-ui/bootstrap/commit/8fee75d)), closes [#4363](https://github.com/angular-ui/bootstrap/issues/4363) [#4195](https://github.com/angular-ui/bootstrap/issues/4195)
+* **tooltip:** do nothing if `$scope` doesn't exist ([1e039e8](https://github.com/angular-ui/bootstrap/commit/1e039e8)), closes [#4346](https://github.com/angular-ui/bootstrap/issues/4346) [#3347](https://github.com/angular-ui/bootstrap/issues/3347)
+* **tooltip:** fix binding to multiple triggers ([d6cda93](https://github.com/angular-ui/bootstrap/commit/d6cda93)), closes [#4371](https://github.com/angular-ui/bootstrap/issues/4371) [#4384](https://github.com/angular-ui/bootstrap/issues/4384)
+* **tooltip:** isOpen to work with expressions ([5f68280](https://github.com/angular-ui/bootstrap/commit/5f68280)), closes [#4380](https://github.com/angular-ui/bootstrap/issues/4380) [#4362](https://github.com/angular-ui/bootstrap/issues/4362)
+* **tooltip:** properly gc popupTimeout ([ff52f52](https://github.com/angular-ui/bootstrap/commit/ff52f52)), closes [#2786](https://github.com/angular-ui/bootstrap/issues/2786)
+* **tooltip:** set `visibility: hidden` to avoid flicker ([f7cb8bc](https://github.com/angular-ui/bootstrap/commit/f7cb8bc)), closes [#4342](https://github.com/angular-ui/bootstrap/issues/4342)
+
+### Features
+
+* **accordion:** use uib- prefix ([0328a76](https://github.com/angular-ui/bootstrap/commit/0328a76)), closes [#4389](https://github.com/angular-ui/bootstrap/issues/4389)
+* **accordion:** use uib- prefix ([298ec8c](https://github.com/angular-ui/bootstrap/commit/298ec8c)), closes [#4503](https://github.com/angular-ui/bootstrap/issues/4503)
+* **alert:** use uib- prefix ([5e3a87a](https://github.com/angular-ui/bootstrap/commit/5e3a87a)), closes [#4406](https://github.com/angular-ui/bootstrap/issues/4406)
+* **buttons:** use uib- prefix ([5a1c2c9](https://github.com/angular-ui/bootstrap/commit/5a1c2c9)), closes [#4445](https://github.com/angular-ui/bootstrap/issues/4445)
+* **carousel:** use uib- prefix ([2e5bfac](https://github.com/angular-ui/bootstrap/commit/2e5bfac)), closes [#4501](https://github.com/angular-ui/bootstrap/issues/4501)
+* **collapse:** convert to use `$animateCss` ([533a9f0](https://github.com/angular-ui/bootstrap/commit/533a9f0)), closes [#4257](https://github.com/angular-ui/bootstrap/issues/4257)
+* **collapse:** use uib- prefix ([9bdb32e](https://github.com/angular-ui/bootstrap/commit/9bdb32e)), closes [#4370](https://github.com/angular-ui/bootstrap/issues/4370)
+* **dateparser:** reset parsers when $locale.id changes ([d9a521a](https://github.com/angular-ui/bootstrap/commit/d9a521a)), closes [#4286](https://github.com/angular-ui/bootstrap/issues/4286) [#4425](https://github.com/angular-ui/bootstrap/issues/4425)
+* **dateparser:** use uib- prefix ([0fa851f](https://github.com/angular-ui/bootstrap/commit/0fa851f)), closes [#4504](https://github.com/angular-ui/bootstrap/issues/4504)
+* **datepicker:** add uib- prefix ([44354f6](https://github.com/angular-ui/bootstrap/commit/44354f6)), closes [#4509](https://github.com/angular-ui/bootstrap/issues/4509)
+* **dropdown:** uib- prefix ([5bc0851](https://github.com/angular-ui/bootstrap/commit/5bc0851)), closes [#4510](https://github.com/angular-ui/bootstrap/issues/4510)
+* **modal:** Added ability to add CSS class to top window ([bd38e8f](https://github.com/angular-ui/bootstrap/commit/bd38e8f)), closes [#2524](https://github.com/angular-ui/bootstrap/issues/2524)
+* **modal:** add uib- prefix ([8c7b9e4](https://github.com/angular-ui/bootstrap/commit/8c7b9e4)), closes [#4511](https://github.com/angular-ui/bootstrap/issues/4511)
+* **pagination:** add uib- prefix ([9aea856](https://github.com/angular-ui/bootstrap/commit/9aea856)), closes [#4536](https://github.com/angular-ui/bootstrap/issues/4536)
+* **position:** add uib- prefix ([6158091](https://github.com/angular-ui/bootstrap/commit/6158091)), closes [#4507](https://github.com/angular-ui/bootstrap/issues/4507)
+* **progressbar:** add `aria-labelledby` support ([e6f3b87](https://github.com/angular-ui/bootstrap/commit/e6f3b87)), closes [#4350](https://github.com/angular-ui/bootstrap/issues/4350) [#4347](https://github.com/angular-ui/bootstrap/issues/4347)
+* **rating:** add `aria-valuetext` attribute ([72de2d8](https://github.com/angular-ui/bootstrap/commit/72de2d8)), closes [#4349](https://github.com/angular-ui/bootstrap/issues/4349) [#4347](https://github.com/angular-ui/bootstrap/issues/4347)
+* **rating:** user uib- prefix ([377b4b7](https://github.com/angular-ui/bootstrap/commit/377b4b7)), closes [#4502](https://github.com/angular-ui/bootstrap/issues/4502)
+* **tabs:** use uib- prefix ([d25a8c2](https://github.com/angular-ui/bootstrap/commit/d25a8c2)), closes [#4449](https://github.com/angular-ui/bootstrap/issues/4449)
+* **timepicker:** use uib- prefix ([504e653](https://github.com/angular-ui/bootstrap/commit/504e653)), closes [#4505](https://github.com/angular-ui/bootstrap/issues/4505)
+* **tooltip:** add uib- prefix ([f8bc038](https://github.com/angular-ui/bootstrap/commit/f8bc038)), closes [#4515](https://github.com/angular-ui/bootstrap/issues/4515)
+* **tooltip:** allow custom closing delay ([5f7051b](https://github.com/angular-ui/bootstrap/commit/5f7051b)), closes [#3576](https://github.com/angular-ui/bootstrap/issues/3576)
+* **tooltip:** hide tooltip when `esc` is hit ([c08509a](https://github.com/angular-ui/bootstrap/commit/c08509a)), closes [#4367](https://github.com/angular-ui/bootstrap/issues/4367) [#4248](https://github.com/angular-ui/bootstrap/issues/4248)
+* **typeahead:** add `appendElementToId` ([fdf53e6](https://github.com/angular-ui/bootstrap/commit/fdf53e6)), closes [#4231](https://github.com/angular-ui/bootstrap/issues/4231) [#4497](https://github.com/angular-ui/bootstrap/issues/4497)
+* **typeahead:** add customClass support for dropdown ([fa1cdfc](https://github.com/angular-ui/bootstrap/commit/fa1cdfc)), closes [#4332](https://github.com/angular-ui/bootstrap/issues/4332) [#4410](https://github.com/angular-ui/bootstrap/issues/4410)
+* **typeahead:** add uib- prefix ([9e5e1a2](https://github.com/angular-ui/bootstrap/commit/9e5e1a2)), closes [#4542](https://github.com/angular-ui/bootstrap/issues/4542)
+
+### Reverts
+
+* **dropdown:** undo adding of `open` class on body ([6f9f1fc](https://github.com/angular-ui/bootstrap/commit/6f9f1fc))
+
+
+### BREAKING CHANGES
+
+* Removes focus on datepicker on selection of a date via keyboard for accessibility reasons
+
+
+<a name="0.13.4"></a>
+## [0.13.4](https://github.com/angular-ui/bootstrap/compare/0.13.3...0.13.4) (2015-09-03)
+
+
+### Bug Fixes
+
+* **accordion:** add custom open class support ([575eb85](https://github.com/angular-ui/bootstrap/commit/575eb85)), closes [#4198](https://github.com/angular-ui/bootstrap/issues/4198)
+* **datepicker:** ensure the original target is not in popup ([9b2f7ac](https://github.com/angular-ui/bootstrap/commit/9b2f7ac)), closes [#4316](https://github.com/angular-ui/bootstrap/issues/4316) [#4314](https://github.com/angular-ui/bootstrap/issues/4314)
+* **dropdown:** fix display when using with append-to-body ([bf63cef](https://github.com/angular-ui/bootstrap/commit/bf63cef)), closes [#4305](https://github.com/angular-ui/bootstrap/issues/4305) [#4240](https://github.com/angular-ui/bootstrap/issues/4240)
+* **dropdown:** fix up arrow nav support ([defcbbb](https://github.com/angular-ui/bootstrap/commit/defcbbb)), closes [#4330](https://github.com/angular-ui/bootstrap/issues/4330) [#4327](https://github.com/angular-ui/bootstrap/issues/4327)
+* **modal:** Wait for animation before focus. ([937a1f3](https://github.com/angular-ui/bootstrap/commit/937a1f3)), closes [#4300](https://github.com/angular-ui/bootstrap/issues/4300) [#4274](https://github.com/angular-ui/bootstrap/issues/4274)
+* **modal:** correctly remove custom class ([ba2ce24](https://github.com/angular-ui/bootstrap/commit/ba2ce24)), closes [#4175](https://github.com/angular-ui/bootstrap/issues/4175) [#4171](https://github.com/angular-ui/bootstrap/issues/4171)
+* **modal:** fix allowing promises to be resolved ([b1e98b1](https://github.com/angular-ui/bootstrap/commit/b1e98b1)), closes [#4310](https://github.com/angular-ui/bootstrap/issues/4310) [#4309](https://github.com/angular-ui/bootstrap/issues/4309)
+* **progress:** rename to avoid conflict ([07a938d](https://github.com/angular-ui/bootstrap/commit/07a938d)), closes [#4255](https://github.com/angular-ui/bootstrap/issues/4255)
+* **tabs:** ensure tab selection only occurs once ([7d3ba1e](https://github.com/angular-ui/bootstrap/commit/7d3ba1e)), closes [#3060](https://github.com/angular-ui/bootstrap/issues/3060) [#4230](https://github.com/angular-ui/bootstrap/issues/4230) [#2883](https://github.com/angular-ui/bootstrap/issues/2883)
+* **timepicker:** leave view alone if either input is invalid ([818f7e5](https://github.com/angular-ui/bootstrap/commit/818f7e5)), closes [#4160](https://github.com/angular-ui/bootstrap/issues/4160) [#3825](https://github.com/angular-ui/bootstrap/issues/3825)
+* **tooltip:** correctly position tooltip ([457f10c](https://github.com/angular-ui/bootstrap/commit/457f10c)), closes [#4311](https://github.com/angular-ui/bootstrap/issues/4311) [#4195](https://github.com/angular-ui/bootstrap/issues/4195)
+* **tooltip:** fix jshint error ([17cc39f](https://github.com/angular-ui/bootstrap/commit/17cc39f))
+* **tooltip:** properly gc timeout on toggle of disabled ([f8eab55](https://github.com/angular-ui/bootstrap/commit/f8eab55)), closes [#4210](https://github.com/angular-ui/bootstrap/issues/4210) [#4204](https://github.com/angular-ui/bootstrap/issues/4204)
+* **tooltip:** switch to use raw DOM event bindings ([7556bed](https://github.com/angular-ui/bootstrap/commit/7556bed)), closes [#4322](https://github.com/angular-ui/bootstrap/issues/4322) [#4060](https://github.com/angular-ui/bootstrap/issues/4060)
+* **typeahead:** add support for ngModelOptions getterSetter ([ccaa627](https://github.com/angular-ui/bootstrap/commit/ccaa627)), closes [#3865](https://github.com/angular-ui/bootstrap/issues/3865) [#3823](https://github.com/angular-ui/bootstrap/issues/3823)
+* **typeahead:** release references on destruction ([695db9d](https://github.com/angular-ui/bootstrap/commit/695db9d)), closes [#4299](https://github.com/angular-ui/bootstrap/issues/4299) [#4298](https://github.com/angular-ui/bootstrap/issues/4298)
+* **typeahead:** use ng-bind-html ([bb9fa1a](https://github.com/angular-ui/bootstrap/commit/bb9fa1a)), closes [#3463](https://github.com/angular-ui/bootstrap/issues/3463) [#4073](https://github.com/angular-ui/bootstrap/issues/4073)
+
+### Features
+
+* **accordion:** allow custom panel class ([5ee23a4](https://github.com/angular-ui/bootstrap/commit/5ee23a4)), closes [#4242](https://github.com/angular-ui/bootstrap/issues/4242) [#3968](https://github.com/angular-ui/bootstrap/issues/3968)
+* **accordion:** support spacebar to toggle group ([aa5a991](https://github.com/angular-ui/bootstrap/commit/aa5a991)), closes [#4319](https://github.com/angular-ui/bootstrap/issues/4319) [#4249](https://github.com/angular-ui/bootstrap/issues/4249)
+* **buttons:** allow toggling via spacebar when focused ([bdfb289](https://github.com/angular-ui/bootstrap/commit/bdfb289)), closes [#4252](https://github.com/angular-ui/bootstrap/issues/4252) [#4259](https://github.com/angular-ui/bootstrap/issues/4259)
+* **buttons:** hide nested inputs ([a06afe6](https://github.com/angular-ui/bootstrap/commit/a06afe6)), closes [#4282](https://github.com/angular-ui/bootstrap/issues/4282)
+* **carousel:** add model binding support to slide ([dac087e](https://github.com/angular-ui/bootstrap/commit/dac087e)), closes [#4202](https://github.com/angular-ui/bootstrap/issues/4202) [#4201](https://github.com/angular-ui/bootstrap/issues/4201)
+* **dateparser:** add support for the `h` format ([550fe20](https://github.com/angular-ui/bootstrap/commit/550fe20)), closes [#4220](https://github.com/angular-ui/bootstrap/issues/4220)
+* **datepicker:** disable today button if invalid ([71e0b8a](https://github.com/angular-ui/bootstrap/commit/71e0b8a)), closes [#4199](https://github.com/angular-ui/bootstrap/issues/4199) [#3988](https://github.com/angular-ui/bootstrap/issues/3988)
+* **modal:** complete modal open resolution in order ([1bba8b4](https://github.com/angular-ui/bootstrap/commit/1bba8b4)), closes [#2443](https://github.com/angular-ui/bootstrap/issues/2443) [#4302](https://github.com/angular-ui/bootstrap/issues/4302) [#2404](https://github.com/angular-ui/bootstrap/issues/2404)
+* **modal:** support multiple open classes ([3d01c59](https://github.com/angular-ui/bootstrap/commit/3d01c59)), closes [#4226](https://github.com/angular-ui/bootstrap/issues/4226) [#4184](https://github.com/angular-ui/bootstrap/issues/4184)
+* **pagination:** add `ngDisabled` support for the pager ([ba734b4](https://github.com/angular-ui/bootstrap/commit/ba734b4)), closes [#4217](https://github.com/angular-ui/bootstrap/issues/4217) [#2856](https://github.com/angular-ui/bootstrap/issues/2856)
+* **pagination:** add `templateUrl` support ([64b5289](https://github.com/angular-ui/bootstrap/commit/64b5289)), closes [#4162](https://github.com/angular-ui/bootstrap/issues/4162)
+* **tabs:** add support for `x-tab-heading` ([1abfd05](https://github.com/angular-ui/bootstrap/commit/1abfd05)), closes [#4166](https://github.com/angular-ui/bootstrap/issues/4166) [#1893](https://github.com/angular-ui/bootstrap/issues/1893)
+* **timepicker:** add `templateUrl` and `controllerAs` support ([639d511](https://github.com/angular-ui/bootstrap/commit/639d511)), closes [#4275](https://github.com/angular-ui/bootstrap/issues/4275) [#4284](https://github.com/angular-ui/bootstrap/issues/4284)
+* **tooltip:** expose isOpen property ([99b87cc](https://github.com/angular-ui/bootstrap/commit/99b87cc)), closes [#4179](https://github.com/angular-ui/bootstrap/issues/4179) [#2148](https://github.com/angular-ui/bootstrap/issues/2148) [#590](https://github.com/angular-ui/bootstrap/issues/590)
+* **typeahead:** add `typeaheadFocusOnSelect` ([b5ecda3](https://github.com/angular-ui/bootstrap/commit/b5ecda3)), closes [#4212](https://github.com/angular-ui/bootstrap/issues/4212) [#4211](https://github.com/angular-ui/bootstrap/issues/4211) [#4206](https://github.com/angular-ui/bootstrap/issues/4206)
+* **typeahead:** add custom popup template support ([4b02648](https://github.com/angular-ui/bootstrap/commit/4b02648)), closes [#4320](https://github.com/angular-ui/bootstrap/issues/4320) [#3774](https://github.com/angular-ui/bootstrap/issues/3774)
+
+
+### Breaking Changes
+
+* **buttons**
+  * hide nested `<input>` elements on `btn-radio` and `btn-checkbox` directives.
+
+  Fixes #3264
+  Closes #4282
+
+   ([a06afe6](https://github.com/angular-ui/bootstrap/commit/a06afe6))
+
+* **dropdown**
+  * when using `append-to-body`, both the `dropdown` and `open` classes are added to the `<body>` element.
+  * this differs from the existing behavior in that it will no longer toggle based on the existing `dropdown` directive element, but on the `body` element instead.
+
+  Fixes #4240
+  Closes #4305
+
+   ([bf63cef](https://github.com/angular-ui/bootstrap/commit/bf63cef))
+
+* **tooltip**
+  * Switch to use `addEventListener` and `removeEventListener` to prevent jqLite/jQuery bug where the events are swallowed on disabled elements
+  * this affects custom events, which must now be dispatched with `element[0].dispatchEvent(new Event('customEvent'))`, as opposed to `element.trigger('customEvent')`
+
+  Fixes #4060
+  Closes #4322
+
+   ([7556bed](https://github.com/angular-ui/bootstrap/commit/7556beda486f26b40fb860448316e8a32457e9e9))
+
+* **typeahead**
+  * for security reasons, only whitelisted HTML should be added.
+  * the typeahead match template now uses `ng-bind-html` instead of `bind-html-unsafe`.
+  * typeahead now uses the `$sce` service when `ngSanitize` is present and logs a warning if it is not.
+
+  Fixes #2884
+  Closes #3463
+  Closes #4073
+
+   ([bb9fa1a](https://github.com/angular-ui/bootstrap/commit/bb9fa1a))
+
+
+<a name"0.13.3"></a>
+### 0.13.3 (2015-08-09)
+
+
+#### Bug Fixes
+
+* **accordion:**
+  * add `open` class when expanded ([ead15e37](https://github.com/angular-ui/bootstrap/commit/ead15e37), closes [#4152](https://github.com/angular-ui/bootstrap/issues/4152), [#3419](https://github.com/angular-ui/bootstrap/issues/3419))
+  * revert to empty href ([b18dc8f9](https://github.com/angular-ui/bootstrap/commit/b18dc8f9), closes [#4104](https://github.com/angular-ui/bootstrap/issues/4104))
+* **buttons:**
+  * change to use `attrs.disabled` ([c9b0d0b0](https://github.com/angular-ui/bootstrap/commit/c9b0d0b0), closes [#4088](https://github.com/angular-ui/bootstrap/issues/4088))
+  * allow selection of undisabled button ([707fbf55](https://github.com/angular-ui/bootstrap/commit/707fbf55), closes [#4088](https://github.com/angular-ui/bootstrap/issues/4088))
+* **carousel:**
+  * fix animation direction ([8359d73f](https://github.com/angular-ui/bootstrap/commit/8359d73f), closes [#4092](https://github.com/angular-ui/bootstrap/issues/4092), [#4087](https://github.com/angular-ui/bootstrap/issues/4087))
+  * fix sorting of indicators ([8056368e](https://github.com/angular-ui/bootstrap/commit/8056368e), closes [#4071](https://github.com/angular-ui/bootstrap/issues/4071), [#3764](https://github.com/angular-ui/bootstrap/issues/3764))
+* **dateparser:** Support 12-hour format and AM/PM ([1ecd82ce](https://github.com/angular-ui/bootstrap/commit/1ecd82ce), closes [#4117](https://github.com/angular-ui/bootstrap/issues/4117))
+* **datepicker:**
+  * commit safe apply on destruction ([74a8be4c](https://github.com/angular-ui/bootstrap/commit/74a8be4c), closes [#4079](https://github.com/angular-ui/bootstrap/issues/4079), [#4076](https://github.com/angular-ui/bootstrap/issues/4076))
+  * change to `dateDisabled` ([5245ccad](https://github.com/angular-ui/bootstrap/commit/5245ccad), closes [#2773](https://github.com/angular-ui/bootstrap/issues/2773), [#4080](https://github.com/angular-ui/bootstrap/issues/4080))
+* **dropdown:** handle `keyboard-nav` correctly ([0b37f088](https://github.com/angular-ui/bootstrap/commit/0b37f088), closes [#4110](https://github.com/angular-ui/bootstrap/issues/4110), [#4091](https://github.com/angular-ui/bootstrap/issues/4091))
+* **modal:**
+  * skipping ESC handling for form inputs ([a05b9c1a](https://github.com/angular-ui/bootstrap/commit/a05b9c1a), closes [#3551](https://github.com/angular-ui/bootstrap/issues/3551), [#2544](https://github.com/angular-ui/bootstrap/issues/2544))
+  * add `$animateCss` support ([c7f19d58](https://github.com/angular-ui/bootstrap/commit/c7f19d58), closes [#4121](https://github.com/angular-ui/bootstrap/issues/4121), [#4119](https://github.com/angular-ui/bootstrap/issues/4119))
+  * fix test ([e60c3ff6](https://github.com/angular-ui/bootstrap/commit/e60c3ff6))
+  * dismiss modal on unschedule destruction ([3584061f](https://github.com/angular-ui/bootstrap/commit/3584061f), closes [#4097](https://github.com/angular-ui/bootstrap/issues/4097), [#3694](https://github.com/angular-ui/bootstrap/issues/3694))
+* **progressbar:** fix `min-width` for Bootstrap 3.2 ([8dc13be9](https://github.com/angular-ui/bootstrap/commit/8dc13be9), closes [#4081](https://github.com/angular-ui/bootstrap/issues/4081), [#2511](https://github.com/angular-ui/bootstrap/issues/2511))
+* **tooltip:**
+  * add safety to `$apply` ([22b16f01](https://github.com/angular-ui/bootstrap/commit/22b16f01), closes [#3943](https://github.com/angular-ui/bootstrap/issues/3943), [#4150](https://github.com/angular-ui/bootstrap/issues/4150), [#516](https://github.com/angular-ui/bootstrap/issues/516))
+  * tooltip w/ template position ([895a2281](https://github.com/angular-ui/bootstrap/commit/895a2281))
+  * prevent opening when `tooltipPopupDelay` is present ([12c527af](https://github.com/angular-ui/bootstrap/commit/12c527af), closes [#4098](https://github.com/angular-ui/bootstrap/issues/4098), [#3611](https://github.com/angular-ui/bootstrap/issues/3611))
+* **typeahead:** return `null` if empty ([c7d3a660](https://github.com/angular-ui/bootstrap/commit/c7d3a660), closes [#4078](https://github.com/angular-ui/bootstrap/issues/4078), [#3176](https://github.com/angular-ui/bootstrap/issues/3176))
+
+
+#### Features
+
+* **accordion:**
+  * add `controllerAs` support ([9865ee8e](https://github.com/angular-ui/bootstrap/commit/9865ee8e), closes [#4138](https://github.com/angular-ui/bootstrap/issues/4138))
+  * add `templateUrl` support ([f777c320](https://github.com/angular-ui/bootstrap/commit/f777c320), closes [#4084](https://github.com/angular-ui/bootstrap/issues/4084))
+* **alert:** add `templateUrl` support ([88a885ca](https://github.com/angular-ui/bootstrap/commit/88a885ca), closes [#4139](https://github.com/angular-ui/bootstrap/issues/4139))
+* **buttons:** add `controllerAs` support ([02872dc1](https://github.com/angular-ui/bootstrap/commit/02872dc1), closes [#4140](https://github.com/angular-ui/bootstrap/issues/4140))
+* **carousel:**
+  * add `templateUrl` support ([a29c8f20](https://github.com/angular-ui/bootstrap/commit/a29c8f20), closes [#4141](https://github.com/angular-ui/bootstrap/issues/4141))
+  * expose carousel controller via `controllerAs` ([bfec07e4](https://github.com/angular-ui/bootstrap/commit/bfec07e4), closes [#4131](https://github.com/angular-ui/bootstrap/issues/4131))
+* **datepicker:**
+  * allow custom templates ([e04b06d7](https://github.com/angular-ui/bootstrap/commit/e04b06d7), closes [#4157](https://github.com/angular-ui/bootstrap/issues/4157), [#1913](https://github.com/angular-ui/bootstrap/issues/1913))
+  * add `onOpenFocus` support ([68afc4c6](https://github.com/angular-ui/bootstrap/commit/68afc4c6), closes [#2303](https://github.com/angular-ui/bootstrap/issues/2303), [#2546](https://github.com/angular-ui/bootstrap/issues/2546), [#4146](https://github.com/angular-ui/bootstrap/issues/4146))
+  * add support for dynamic `min-mode` and `max-mode` ([f3d263e1](https://github.com/angular-ui/bootstrap/commit/f3d263e1), closes [#3843](https://github.com/angular-ui/bootstrap/issues/3843), [#2618](https://github.com/angular-ui/bootstrap/issues/2618))
+  * allow suppression of log error ([bab1d375](https://github.com/angular-ui/bootstrap/commit/bab1d375), closes [#3836](https://github.com/angular-ui/bootstrap/issues/3836), [#4115](https://github.com/angular-ui/bootstrap/issues/4115))
+* **docs:**
+  * add explanation of eye icon ([265d429b](https://github.com/angular-ui/bootstrap/commit/265d429b), closes [#4120](https://github.com/angular-ui/bootstrap/issues/4120))
+  * add ngAnimate Plunker support ([a8a22cff](https://github.com/angular-ui/bootstrap/commit/a8a22cff), closes [#3648](https://github.com/angular-ui/bootstrap/issues/3648), [#4072](https://github.com/angular-ui/bootstrap/issues/4072))
+* **modal:**
+  * add ability to change class on body ([5a28ff76](https://github.com/angular-ui/bootstrap/commit/5a28ff76), closes [#2633](https://github.com/angular-ui/bootstrap/issues/2633), [#4132](https://github.com/angular-ui/bootstrap/issues/4132))
+  * allow users to resolve with strings ([89368856](https://github.com/angular-ui/bootstrap/commit/89368856), closes [#2676](https://github.com/angular-ui/bootstrap/issues/2676), [#4124](https://github.com/angular-ui/bootstrap/issues/4124))
+* **pagination:**
+  * add classes to assist with styling ([b21c9abd](https://github.com/angular-ui/bootstrap/commit/b21c9abd), closes [#4130](https://github.com/angular-ui/bootstrap/issues/4130), [#4142](https://github.com/angular-ui/bootstrap/issues/4142))
+  * add `templateUrl` support ([a0e1c91c](https://github.com/angular-ui/bootstrap/commit/a0e1c91c), closes [#4137](https://github.com/angular-ui/bootstrap/issues/4137))
+* **timepicker:**
+  * add documentation for max/min ([87fc242d](https://github.com/angular-ui/bootstrap/commit/87fc242d))
+  * Added min/max attributes for timepicker. ([6c0010be](https://github.com/angular-ui/bootstrap/commit/6c0010be), closes [#4019](https://github.com/angular-ui/bootstrap/issues/4019))
+* **tooltip:**
+  * remove unnecessary `$digest` ([901a7c66](https://github.com/angular-ui/bootstrap/commit/901a7c66), closes [#4151](https://github.com/angular-ui/bootstrap/issues/4151))
+  * add multiple trigger support ([ca9196fa](https://github.com/angular-ui/bootstrap/commit/ca9196fa), closes [#3987](https://github.com/angular-ui/bootstrap/issues/3987), [#4077](https://github.com/angular-ui/bootstrap/issues/4077))
+
+
+#### Breaking Changes
+
+* add `open` class to accordion group when expanded
+
+Closes #4152
+Closes #3419
+
+ ([ead15e37](https://github.com/angular-ui/bootstrap/commit/ead15e37))
+* Allow the user to hit `esc` inside an element in a modal and not exit the modal if the event has been `defaultPrevented`
+
+Closes #3551
+Fixes #2544
+
+ ([a05b9c1a](https://github.com/angular-ui/bootstrap/commit/a05b9c1a))
+* Change validation key to `dateDisabled` to align better with Angular's convention
+
+Closes #2773
+Closes #4080
+
+ ([5245ccad](https://github.com/angular-ui/bootstrap/commit/5245ccad))
+
+
+<a name"0.13.2"></a>
+### 0.13.2 (2015-08-02)
+
+
+#### Bug Fixes
+
+* **accordion:** apply disabled style to accordion-header ([0643fd3e](https://github.com/angular-ui/bootstrap/commit/0643fd3e), closes [#3599](https://github.com/angular-ui/bootstrap/issues/3599), [#3233](https://github.com/angular-ui/bootstrap/issues/3233))
+* **buttons:** respect disabled attribute ([42e1af5c](https://github.com/angular-ui/bootstrap/commit/42e1af5c), closes [#4026](https://github.com/angular-ui/bootstrap/issues/4026), [#4013](https://github.com/angular-ui/bootstrap/issues/4013))
+* **carousel:**
+  * fix animations with 1.4 ([f45b4a4c](https://github.com/angular-ui/bootstrap/commit/f45b4a4c), closes [#3946](https://github.com/angular-ui/bootstrap/issues/3946), [#4041](https://github.com/angular-ui/bootstrap/issues/4041), [#3811](https://github.com/angular-ui/bootstrap/issues/3811))
+  * clear `currentSlide` when there are no slides ([0c78026b](https://github.com/angular-ui/bootstrap/commit/0c78026b), closes [#4021](https://github.com/angular-ui/bootstrap/issues/4021))
+* **dateparser:** add type and validity check ([4f1e03f1](https://github.com/angular-ui/bootstrap/commit/4f1e03f1), closes [#3701](https://github.com/angular-ui/bootstrap/issues/3701), [#3759](https://github.com/angular-ui/bootstrap/issues/3759), [#3933](https://github.com/angular-ui/bootstrap/issues/3933), [#3609](https://github.com/angular-ui/bootstrap/issues/3609), [#3713](https://github.com/angular-ui/bootstrap/issues/3713), [#3736](https://github.com/angular-ui/bootstrap/issues/3736), [#3875](https://github.com/angular-ui/bootstrap/issues/3875), [#3937](https://github.com/angular-ui/bootstrap/issues/3937), [#3976](https://github.com/angular-ui/bootstrap/issues/3976))
+* **datepicker:**
+  * change to contains ([9f73d240](https://github.com/angular-ui/bootstrap/commit/9f73d240), closes [#4066](https://github.com/angular-ui/bootstrap/issues/4066), [#3076](https://github.com/angular-ui/bootstrap/issues/3076))
+  * *BREAKING CHANGE* remove `new Date` fallback ([ab4580fd](https://github.com/angular-ui/bootstrap/commit/ab4580fd), closes [#2513](https://github.com/angular-ui/bootstrap/issues/2513), [#3294](https://github.com/angular-ui/bootstrap/issues/3294), [#3344](https://github.com/angular-ui/bootstrap/issues/3344), [#3682](https://github.com/angular-ui/bootstrap/issues/3682), [#4092](https://github.com/angular-ui/bootstrap/issues/4092), [#1289](https://github.com/angular-ui/bootstrap/issues/1289), [#2446](https://github.com/angular-ui/bootstrap/issues/2446), [#3037](https://github.com/angular-ui/bootstrap/issues/3037), [#3104](https://github.com/angular-ui/bootstrap/issues/3104), [#3196](https://github.com/angular-ui/bootstrap/issues/3196), [#3206](https://github.com/angular-ui/bootstrap/issues/3206), [#3342](https://github.com/angular-ui/bootstrap/issues/3342), [#3617](https://github.com/angular-ui/bootstrap/issues/3617), [#3644](https://github.com/angular-ui/bootstrap/issues/3644))
+  * ensure `initDate` is on an object ([577b2a2a](https://github.com/angular-ui/bootstrap/commit/577b2a2a), closes [#3625](https://github.com/angular-ui/bootstrap/issues/3625))
+  * change to higher max date ([32e73280](https://github.com/angular-ui/bootstrap/commit/32e73280), closes [#4042](https://github.com/angular-ui/bootstrap/issues/4042))
+  * fix validation with `ngRequired` ([fe0d954a](https://github.com/angular-ui/bootstrap/commit/fe0d954a), closes [#4002](https://github.com/angular-ui/bootstrap/issues/4002), [#3862](https://github.com/angular-ui/bootstrap/issues/3862))
+  * set to `null` if not present ([a65a5fa1](https://github.com/angular-ui/bootstrap/commit/a65a5fa1), closes [#4014](https://github.com/angular-ui/bootstrap/issues/4014))
+* **dropdown:** add safety check for setIsOpen ([60e43160](https://github.com/angular-ui/bootstrap/commit/60e43160), closes [#4030](https://github.com/angular-ui/bootstrap/issues/4030))
+* **modal:**
+  * properly garbage collect DOM node ([1e8297be](https://github.com/angular-ui/bootstrap/commit/1e8297be), closes [#2875](https://github.com/angular-ui/bootstrap/issues/2875))
+  * fix `bindToController` implementation ([811bf96e](https://github.com/angular-ui/bootstrap/commit/811bf96e), closes [#4054](https://github.com/angular-ui/bootstrap/issues/4054), [#4051](https://github.com/angular-ui/bootstrap/issues/4051))
+  * animate backdrop concurrently with window ([c55ee4f5](https://github.com/angular-ui/bootstrap/commit/c55ee4f5), closes [#4039](https://github.com/angular-ui/bootstrap/issues/4039), [#4036](https://github.com/angular-ui/bootstrap/issues/4036))
+* **progressbar:**
+  * use more visible color ([1afc5d1d](https://github.com/angular-ui/bootstrap/commit/1afc5d1d), closes [#4044](https://github.com/angular-ui/bootstrap/issues/4044))
+  * allow max width of 100% ([2e9177e5](https://github.com/angular-ui/bootstrap/commit/2e9177e5), closes [#4027](https://github.com/angular-ui/bootstrap/issues/4027), [#4018](https://github.com/angular-ui/bootstrap/issues/4018))
+* **tooltip:**
+  * update tooltip placement dynamically ([13df1c93](https://github.com/angular-ui/bootstrap/commit/13df1c93), closes [#3980](https://github.com/angular-ui/bootstrap/issues/3980), [#3978](https://github.com/angular-ui/bootstrap/issues/3978))
+  * prevent 1px shift in Webkit/Blink ([632aa820](https://github.com/angular-ui/bootstrap/commit/632aa820), closes [#3964](https://github.com/angular-ui/bootstrap/issues/3964))
+* **typeahead:**
+  * reset matches if enter is hit ([25704838](https://github.com/angular-ui/bootstrap/commit/25704838), closes [#4063](https://github.com/angular-ui/bootstrap/issues/4063), [#3545](https://github.com/angular-ui/bootstrap/issues/3545))
+  * only reset matches if matches are present ([97e077e1](https://github.com/angular-ui/bootstrap/commit/97e077e1), closes [#3119](https://github.com/angular-ui/bootstrap/issues/3119))
+
+
+#### Features
+
+* **build:** add support for npm publishing ([27f7ca26](https://github.com/angular-ui/bootstrap/commit/27f7ca26), closes [#3108](https://github.com/angular-ui/bootstrap/issues/3108))
+* **modal:** trap focus in modal for tabbing ([a028d2aa](https://github.com/angular-ui/bootstrap/commit/a028d2aa), closes [#3689](https://github.com/angular-ui/bootstrap/issues/3689), [#4004](https://github.com/angular-ui/bootstrap/issues/4004), [#738](https://github.com/angular-ui/bootstrap/issues/738))
+* **popover:** add custom template support ([a9d3d253](https://github.com/angular-ui/bootstrap/commit/a9d3d253), closes [#4056](https://github.com/angular-ui/bootstrap/issues/4056), [#4057](https://github.com/angular-ui/bootstrap/issues/4057))
+* **rating:** add title support for stars ([713c8487](https://github.com/angular-ui/bootstrap/commit/713c8487), closes [#3621](https://github.com/angular-ui/bootstrap/issues/3621))
+* **typeahead:**
+  * add `noResults` indicator binding ([647cdd93](https://github.com/angular-ui/bootstrap/commit/647cdd93), closes [#2016](https://github.com/angular-ui/bootstrap/issues/2016), [#2792](https://github.com/angular-ui/bootstrap/issues/2792), [#4068](https://github.com/angular-ui/bootstrap/issues/4068))
+  * add `typeaheadSelectOnExact` support ([277b30ca](https://github.com/angular-ui/bootstrap/commit/277b30ca), closes [#3365](https://github.com/angular-ui/bootstrap/issues/3365), [#3310](https://github.com/angular-ui/bootstrap/issues/3310))
+
+
+<a name"0.13.1"></a>
+### 0.13.1 (2015-07-23)
+
+
+#### Bug Fixes
+
+* **accordion:** add CSP compliance for accordion & typeahead ([fb302c60](https://github.com/angular-ui/bootstrap/commit/fb302c60), closes [#3909](https://github.com/angular-ui/bootstrap/issues/3909), [#3904](https://github.com/angular-ui/bootstrap/issues/3904))
+* **alert:**
+  * adjust check for close attribute ([13a0354f](https://github.com/angular-ui/bootstrap/commit/13a0354f), closes [#3864](https://github.com/angular-ui/bootstrap/issues/3864), [#3890](https://github.com/angular-ui/bootstrap/issues/3890), [#3848](https://github.com/angular-ui/bootstrap/issues/3848))
+  * rename alert-dismissable to alert-dismissible ([d631af5a](https://github.com/angular-ui/bootstrap/commit/d631af5a))
+* **carousel:**
+  * change to avoid references to debug info ([ca07ad7c](https://github.com/angular-ui/bootstrap/commit/ca07ad7c), closes [#3795](https://github.com/angular-ui/bootstrap/issues/3795), [#3794](https://github.com/angular-ui/bootstrap/issues/3794))
+  * ensure there are slides present ([115d490a](https://github.com/angular-ui/bootstrap/commit/115d490a), closes [#3755](https://github.com/angular-ui/bootstrap/issues/3755))
+  * disable transition until animation completes ([ef45ecf8](https://github.com/angular-ui/bootstrap/commit/ef45ecf8), closes [#3729](https://github.com/angular-ui/bootstrap/issues/3729), [#3757](https://github.com/angular-ui/bootstrap/issues/3757))
+* **collapse:** fix occasional flickering ([ede9ea46](https://github.com/angular-ui/bootstrap/commit/ede9ea46), closes [#3804](https://github.com/angular-ui/bootstrap/issues/3804), [#3801](https://github.com/angular-ui/bootstrap/issues/3801))
+* **datepicker:**
+  * change to min width cells ([5567c432](https://github.com/angular-ui/bootstrap/commit/5567c432), closes [#4000](https://github.com/angular-ui/bootstrap/issues/4000), [#3941](https://github.com/angular-ui/bootstrap/issues/3941))
+  * fix OS dependent time zone issue ([f1412014](https://github.com/angular-ui/bootstrap/commit/f1412014), closes [#3079](https://github.com/angular-ui/bootstrap/issues/3079))
+  * Apply custom class to month ([eb3b32ec](https://github.com/angular-ui/bootstrap/commit/eb3b32ec), closes [#3863](https://github.com/angular-ui/bootstrap/issues/3863))
+  * check if getter.assign is function ([ed10899d](https://github.com/angular-ui/bootstrap/commit/ed10899d), closes [#3155](https://github.com/angular-ui/bootstrap/issues/3155), [#3345](https://github.com/angular-ui/bootstrap/issues/3345), [#3719](https://github.com/angular-ui/bootstrap/issues/3719))
+* **dropdown:**
+  * do not autoclose with outsideClick and append to body ([cc66a068](https://github.com/angular-ui/bootstrap/commit/cc66a068), closes [#3792](https://github.com/angular-ui/bootstrap/issues/3792), [#3645](https://github.com/angular-ui/bootstrap/issues/3645))
+  * avoid matching 138 & 140 ([41ebd984](https://github.com/angular-ui/bootstrap/commit/41ebd984))
+  * align when using dropdown-menu-body ([2332f14d](https://github.com/angular-ui/bootstrap/commit/2332f14d), closes [#3913](https://github.com/angular-ui/bootstrap/issues/3913), [#3820](https://github.com/angular-ui/bootstrap/issues/3820))
+  * call toggle after animation ([054341b7](https://github.com/angular-ui/bootstrap/commit/054341b7), closes [#3513](https://github.com/angular-ui/bootstrap/issues/3513), [#3655](https://github.com/angular-ui/bootstrap/issues/3655), [#3511](https://github.com/angular-ui/bootstrap/issues/3511))
+  * do not close on $locationChangeSuccess ([e5a1e88f](https://github.com/angular-ui/bootstrap/commit/e5a1e88f), closes [#3683](https://github.com/angular-ui/bootstrap/issues/3683), [#3704](https://github.com/angular-ui/bootstrap/issues/3704))
+* **modal:**
+  * backdrop animation on AngularJS 1.4 ([158d2676](https://github.com/angular-ui/bootstrap/commit/158d2676), closes [#3896](https://github.com/angular-ui/bootstrap/issues/3896))
+  * closing breaks on missing scope, 1.4 ([0286828b](https://github.com/angular-ui/bootstrap/commit/0286828b), closes [#3787](https://github.com/angular-ui/bootstrap/issues/3787), [#3806](https://github.com/angular-ui/bootstrap/issues/3806), [#3873](https://github.com/angular-ui/bootstrap/issues/3873), [#3888](https://github.com/angular-ui/bootstrap/issues/3888))
+  * remove illegal character ([dd4f3cc8](https://github.com/angular-ui/bootstrap/commit/dd4f3cc8), closes [#3893](https://github.com/angular-ui/bootstrap/issues/3893), [#3892](https://github.com/angular-ui/bootstrap/issues/3892))
+  * focus on body if element disappears ([988336cc](https://github.com/angular-ui/bootstrap/commit/988336cc), closes [#3653](https://github.com/angular-ui/bootstrap/issues/3653), [#3639](https://github.com/angular-ui/bootstrap/issues/3639))
+* **progressbar:** use max value on stacked progress bar ([36e0f0ea](https://github.com/angular-ui/bootstrap/commit/36e0f0ea), closes [#3830](https://github.com/angular-ui/bootstrap/issues/3830), [#3618](https://github.com/angular-ui/bootstrap/issues/3618))
+* **rating:** Set rating to 0 when same value is selected ([dbceec76](https://github.com/angular-ui/bootstrap/commit/dbceec76), closes [#3963](https://github.com/angular-ui/bootstrap/issues/3963), [#3246](https://github.com/angular-ui/bootstrap/issues/3246))
+* **tabs:** fix empty href ([2b27dcbf](https://github.com/angular-ui/bootstrap/commit/2b27dcbf), closes [#3799](https://github.com/angular-ui/bootstrap/issues/3799))
+* **typeahead:**
+  * select match on tab for iOS webview ([5b37bb8b](https://github.com/angular-ui/bootstrap/commit/5b37bb8b), closes [#3762](https://github.com/angular-ui/bootstrap/issues/3762), [#3699](https://github.com/angular-ui/bootstrap/issues/3699))
+  * don't close popup on right click ([7d1c4600](https://github.com/angular-ui/bootstrap/commit/7d1c4600), closes [#3975](https://github.com/angular-ui/bootstrap/issues/3975), [#3973](https://github.com/angular-ui/bootstrap/issues/3973))
+  * close dropdown on tab with no selection ([493510d0](https://github.com/angular-ui/bootstrap/commit/493510d0), closes [#3340](https://github.com/angular-ui/bootstrap/issues/3340))
+  * do not execute unnecessary $digest ([0d96221f](https://github.com/angular-ui/bootstrap/commit/0d96221f), closes [#2652](https://github.com/angular-ui/bootstrap/issues/2652), [#3791](https://github.com/angular-ui/bootstrap/issues/3791))
+  * add href to show cursor as pointer ([195e520e](https://github.com/angular-ui/bootstrap/commit/195e520e), closes [#3649](https://github.com/angular-ui/bootstrap/issues/3649))
+
+
+#### Features
+
+* **alert:** pass $event to close() ([44e06425](https://github.com/angular-ui/bootstrap/commit/44e06425), closes [#3828](https://github.com/angular-ui/bootstrap/issues/3828), [#3827](https://github.com/angular-ui/bootstrap/issues/3827))
+* **carousel:** add `noWrap` option to prevent re-cycling of slides ([7fb3840f](https://github.com/angular-ui/bootstrap/commit/7fb3840f), closes [#3462](https://github.com/angular-ui/bootstrap/issues/3462), [#3397](https://github.com/angular-ui/bootstrap/issues/3397))
+* **collapse:** add accessibility support ([92551342](https://github.com/angular-ui/bootstrap/commit/92551342), closes [#3920](https://github.com/angular-ui/bootstrap/issues/3920))
+* **dropdown:**
+  * add dropdown classes dynamically ([4af83ade](https://github.com/angular-ui/bootstrap/commit/4af83ade), closes [#3984](https://github.com/angular-ui/bootstrap/issues/3984), [#3986](https://github.com/angular-ui/bootstrap/issues/3986))
+  * add accessibility attributes ([14689e05](https://github.com/angular-ui/bootstrap/commit/14689e05), closes [#3951](https://github.com/angular-ui/bootstrap/issues/3951))
+  * add keynav support to dropdown ([62359370](https://github.com/angular-ui/bootstrap/commit/62359370), closes [#3685](https://github.com/angular-ui/bootstrap/issues/3685), [#3212](https://github.com/angular-ui/bootstrap/issues/3212), [#1228](https://github.com/angular-ui/bootstrap/issues/1228))
+  * support optional templates for dropdown menus ([83c4266c](https://github.com/angular-ui/bootstrap/commit/83c4266c))
+* **modal:** add support for bindToController ([8adfc833](https://github.com/angular-ui/bootstrap/commit/8adfc833), closes [#3965](https://github.com/angular-ui/bootstrap/issues/3965), [#3404](https://github.com/angular-ui/bootstrap/issues/3404))
+* **pagination:** add support for `ng-disabled` ([f6edfa5d](https://github.com/angular-ui/bootstrap/commit/f6edfa5d), closes [#3956](https://github.com/angular-ui/bootstrap/issues/3956))
+* **timepicker:** add `showSpinner` flag ([1f760eb3](https://github.com/angular-ui/bootstrap/commit/1f760eb3))
+* **typeahead:**
+  * add 'select on blur' option. ([68cac59a](https://github.com/angular-ui/bootstrap/commit/68cac59a), closes [#3445](https://github.com/angular-ui/bootstrap/issues/3445))
+  * popup position ([86bfec19](https://github.com/angular-ui/bootstrap/commit/86bfec19), closes [#3874](https://github.com/angular-ui/bootstrap/issues/3874))
+  * handles min-length of 0 ([a5a25141](https://github.com/angular-ui/bootstrap/commit/a5a25141), closes [#3600](https://github.com/angular-ui/bootstrap/issues/3600))
+
+
+<a name="0.13.0"></a>
+## 0.13.0 (2015-05-02)
+
+
+#### Bug Fixes
+
+* **accordion:**
+  * Made accordion heading tab-able for IE9-10 ([6abad509](https://github.com/angular-ui/bootstrap/commit/6abad509cd4d44c3ca432f2f21c9ecea0a206b53))
+  * noop for href in header to prevent page refresh with nested buttons canceling ev ([9ca4ec39](https://github.com/angular-ui/bootstrap/commit/9ca4ec399be0e0e8f6c3fe6fd924a7d94ce669b5))
+* **buttons:** add unit tests for buttons ([9468d723](https://github.com/angular-ui/bootstrap/commit/9468d7239dd6eed4a3a2945f6761f7a2fa97222b), closes [#3030](https://github.com/angular-ui/bootstrap/issues/3030))
+* **carousel:** respect the order of the slides ([b5f220fa](https://github.com/angular-ui/bootstrap/commit/b5f220fa8483f5743ba6ab3610f5064bf5c71be7), closes [#488](https://github.com/angular-ui/bootstrap/issues/488))
+* **changelog:** add comment on breaking change ([f02c1bbb](https://github.com/angular-ui/bootstrap/commit/f02c1bbbf7777bee9cbb4a038730fb475337a2c5), closes [#2675](https://github.com/angular-ui/bootstrap/issues/2675))
+* **dateparser:** add extra validation constraint to days ([c19b8879](https://github.com/angular-ui/bootstrap/commit/c19b8879e902e8753b4077e7983ec629242436fc))
+* **datepicker:**
+  * week count issues ([39e5fd3e](https://github.com/angular-ui/bootstrap/commit/39e5fd3e4981f311d237d21c37a89cde9f42f0d6), closes [#2506](https://github.com/angular-ui/bootstrap/issues/2506), [#3120](https://github.com/angular-ui/bootstrap/issues/3120), [#2306](https://github.com/angular-ui/bootstrap/issues/2306))
+  * make 'show-weeks' work on datepickerPopup ([d0cc7284](https://github.com/angular-ui/bootstrap/commit/d0cc72841b9641b768cc5eb184de2dbbaa804e2d), closes [#3143](https://github.com/angular-ui/bootstrap/issues/3143), [#3149](https://github.com/angular-ui/bootstrap/issues/3149))
+  * datepicker-popup compatibility with ngModelOptions ([d024dd77](https://github.com/angular-ui/bootstrap/commit/d024dd77ed6a20983bea5b90b75481c93f17980f), closes [#3349](https://github.com/angular-ui/bootstrap/issues/3349))
+  * disable title button when in max mode ([35b8512a](https://github.com/angular-ui/bootstrap/commit/35b8512ac7949eba7d432760f83748bb2bb893c1), closes [#3012](https://github.com/angular-ui/bootstrap/issues/3012))
+  * add shortcutPropagation to datepickerPopup ([13bd516c](https://github.com/angular-ui/bootstrap/commit/13bd516cfcc1b2ee940112675830f7ce3d93ea4f))
+  * fixed shortcut event kill by adding option ([89ab4580](https://github.com/angular-ui/bootstrap/commit/89ab4580cd3d0bbdf63e02fbebe2cb3a9f2616dd))
+  * fix initDate implementation in datepicker ([98e2bdfc](https://github.com/angular-ui/bootstrap/commit/98e2bdfc8b4dd108acdf3fac75a795675889bbc7))
+  * Fix init-date not applying on datepicker-popup ([c5b63ded](https://github.com/angular-ui/bootstrap/commit/c5b63ded0aebd7ae80f238bd0a33f1d9c0746dba))
+  * Make datepicker respect dateFormat inside ng-if ([2c2dba6d](https://github.com/angular-ui/bootstrap/commit/2c2dba6d145c95500efe7f6be6248d56ae1aebee))
+  * `ng-model` value can be a timestamp ([d253208b](https://github.com/angular-ui/bootstrap/commit/d253208bd7ebbd6209c6a25f70e010123198fcd7), closes [#2345](https://github.com/angular-ui/bootstrap/issues/2345))
+  * Parse date from $viewValue instead of $modelValue ([0ecf7faa](https://github.com/angular-ui/bootstrap/commit/0ecf7faad3b340e171e5c8ca17a17597fa6e6596))
+  * don't stop ESC propagation unless dropdown is open ([c2e5b284](https://github.com/angular-ui/bootstrap/commit/c2e5b284a31fc992b8230b89dc98ed757d155493), closes [#3096](https://github.com/angular-ui/bootstrap/issues/3096), [#3179](https://github.com/angular-ui/bootstrap/issues/3179))
+  * date formatting when using angular 1.3 fixes #2659 ([23936f9f](https://github.com/angular-ui/bootstrap/commit/23936f9f6ec7170b56c7f13345ae022c74fa3034), closes [#3293](https://github.com/angular-ui/bootstrap/issues/3293), [#3279](https://github.com/angular-ui/bootstrap/issues/3279), [#2440](https://github.com/angular-ui/bootstrap/issues/2440), [#2932](https://github.com/angular-ui/bootstrap/issues/2932), [#3074](https://github.com/angular-ui/bootstrap/issues/3074), [#2943](https://github.com/angular-ui/bootstrap/issues/2943), [#2733](https://github.com/angular-ui/bootstrap/issues/2733), [#3047](https://github.com/angular-ui/bootstrap/issues/3047), [#2659](https://github.com/angular-ui/bootstrap/issues/2659), [#2681](https://github.com/angular-ui/bootstrap/issues/2681))
+  * date formatting when using angular 1.3 fixes #2659 ([5f9afe5a](https://github.com/angular-ui/bootstrap/commit/5f9afe5a86af0e207eaacd6a9b1f202fd78d7009))
+* **demo:** Modify the demo app to play nice with Angular 1.3 ([aa0b6392](https://github.com/angular-ui/bootstrap/commit/aa0b6392db57d436d7c203cf05555d708c945322), closes [#3098](https://github.com/angular-ui/bootstrap/issues/3098))
+* **dropdown:** Fix $digest:inprog on dropdown dismissal ([4a06adba](https://github.com/angular-ui/bootstrap/commit/4a06adbac6c1c1fa6b0182ca9bd7335eda89c43f), closes [#3274](https://github.com/angular-ui/bootstrap/issues/3274))
+* **grunt:** fix typo in gruntfile ([f0cadb1f](https://github.com/angular-ui/bootstrap/commit/f0cadb1f1a6b8d994c2df3313a2121e92a36bddb), closes [#3589](https://github.com/angular-ui/bootstrap/issues/3589))
+* **modal:**
+  * Use attribute observe and add a render promise. ([99af5f8a](https://github.com/angular-ui/bootstrap/commit/99af5f8a369b13b018ec1e592d82a1a140cbb6bb))
+  * fix minor grammar error ([22a21448](https://github.com/angular-ui/bootstrap/commit/22a21448cbad1448bd4541bd0ce3fc8d3515943d), closes [#3519](https://github.com/angular-ui/bootstrap/issues/3519))
+  * Fix focus when the dialog is close or cancelled ([e6b105ae](https://github.com/angular-ui/bootstrap/commit/e6b105ae39bcbcf468c3e32057c6acb5ab50d4ce), closes [#2888](https://github.com/angular-ui/bootstrap/issues/2888))
+  * allow for custom user modal sizes ([85eeb954](https://github.com/angular-ui/bootstrap/commit/85eeb95428e0dd367cf3e251f2d01fc8dc899dd4), closes [#3429](https://github.com/angular-ui/bootstrap/issues/3429), [#3431](https://github.com/angular-ui/bootstrap/issues/3431))
+  * Autofocus corrects the second time that the modal is open ([e5f5f75b](https://github.com/angular-ui/bootstrap/commit/e5f5f75b370b6d4806da87bec575b7084e30c520), closes [#2802](https://github.com/angular-ui/bootstrap/issues/2802))
+  * fix messages on modal test failed ([ab919f9f](https://github.com/angular-ui/bootstrap/commit/ab919f9f89aeaaa336b5d3fe1aed1e0617b5a482))
+* **pagination:**
+  * remove focus from prior clicked elements ([33269bb6](https://github.com/angular-ui/bootstrap/commit/33269bb6b444cdca18e08063814d39c5ef502589), closes [#3488](https://github.com/angular-ui/bootstrap/issues/3488), [#3486](https://github.com/angular-ui/bootstrap/issues/3486))
+  * fixes issue when init called after watch triggered ([26b40903](https://github.com/angular-ui/bootstrap/commit/26b40903abf012b5d3d35154d6210119ac9a76d4), closes [#2257](https://github.com/angular-ui/bootstrap/issues/2257), [#2227](https://github.com/angular-ui/bootstrap/issues/2227))
+* **popover:**
+  * prevent wrong positioning from title ([c8156c7e](https://github.com/angular-ui/bootstrap/commit/c8156c7e21d0f3a61fc19d59644ae24c423eb5c6), closes [#3518](https://github.com/angular-ui/bootstrap/issues/3518))
+  * animations with ngAnimate ([c2ace472](https://github.com/angular-ui/bootstrap/commit/c2ace47225ecb337328a16d2c95744e0d37ce80f), closes [#3509](https://github.com/angular-ui/bootstrap/issues/3509), [#3375](https://github.com/angular-ui/bootstrap/issues/3375), [#3506](https://github.com/angular-ui/bootstrap/issues/3506))
+  * make it work with ngAnimate ([461087b5](https://github.com/angular-ui/bootstrap/commit/461087b5ff360e1d06d6724375ab29169a89fcf2), closes [#3482](https://github.com/angular-ui/bootstrap/issues/3482), [#3375](https://github.com/angular-ui/bootstrap/issues/3375))
+* **progressbar:** limit max width to 100% ([489961e1](https://github.com/angular-ui/bootstrap/commit/489961e1a0036ddab4e719a772382209a595f163), closes [#3005](https://github.com/angular-ui/bootstrap/issues/3005))
+* **tab:** change to `disable` attribute ([4bfae223](https://github.com/angular-ui/bootstrap/commit/4bfae2238d08d7edea8e6dfe99051192a16d6587), closes [#2677](https://github.com/angular-ui/bootstrap/issues/2677))
+* **timepicker:**
+  * move render logic to formatter ([b4bbc019](https://github.com/angular-ui/bootstrap/commit/b4bbc0198284d90085bd85840dffd030345d5211), closes [#3160](https://github.com/angular-ui/bootstrap/issues/3160), [#3427](https://github.com/angular-ui/bootstrap/issues/3427))
+  * remove ng-mousewheel binding ([a726b7cd](https://github.com/angular-ui/bootstrap/commit/a726b7cd47d417ed656aef3af012e7b6c7b00bf4), closes [#3442](https://github.com/angular-ui/bootstrap/issues/3442))
+  * fix widths of inputs when inside form-inline ([8e89440b](https://github.com/angular-ui/bootstrap/commit/8e89440ba86d26d74ed799843cde7f9f2bf26c0b))
+  * Stringify pad return when value >= 10 ([405dab65](https://github.com/angular-ui/bootstrap/commit/405dab65f72f5bed488eb8f5dfa5a1a848c4a606))
+* **tooltip:**
+  * template type should respect popup class ([6af627a8](https://github.com/angular-ui/bootstrap/commit/6af627a8edcbce0370ca2d90f9a575cd6770f6c1), closes [#3569](https://github.com/angular-ui/bootstrap/issues/3569))
+  * tooltip-html should not open if empty ([34044a77](https://github.com/angular-ui/bootstrap/commit/34044a77070bc809220e2ed6b628393889c40b86), closes [#3563](https://github.com/angular-ui/bootstrap/issues/3563))
+  * use correct prefix for -template ([9ca9d7f5](https://github.com/angular-ui/bootstrap/commit/9ca9d7f5263d8d5e0a9de18c2b75f64aca7c5255), closes [#3498](https://github.com/angular-ui/bootstrap/issues/3498), [#3473](https://github.com/angular-ui/bootstrap/issues/3473))
+  * Fix for issue #3167 ([87a36076](https://github.com/angular-ui/bootstrap/commit/87a3607632cae03ead57b377bb91ca27610e7730))
+* **typeahead:**
+  * Fix for memory-leak in typeahead ([b5a80c08](https://github.com/angular-ui/bootstrap/commit/b5a80c08f2364db2170766366cc7433661e737ae))
+  * reset 'parse' validation key ([c0a9c707](https://github.com/angular-ui/bootstrap/commit/c0a9c707903fa3dfc662887cd348ee2b0bf13f85), closes [#3166](https://github.com/angular-ui/bootstrap/issues/3166))
+  * resolve property length of undefined error ([950c22cd](https://github.com/angular-ui/bootstrap/commit/950c22cdab3d0b3479a0d0515ed64e57c9ff046b), closes [#2999](https://github.com/angular-ui/bootstrap/issues/2999), [#3178](https://github.com/angular-ui/bootstrap/issues/3178))
+  * set validity if model is set manually ([b0044433](https://github.com/angular-ui/bootstrap/commit/b004443371d75bb61e9a17a00a58b485c1279aee), closes [#3318](https://github.com/angular-ui/bootstrap/issues/3318))
+  * $compile match template after adding to DOM ([03446c56](https://github.com/angular-ui/bootstrap/commit/03446c56335d5ee0970f9730af215fea1dd53f48))
+
+
+#### Features
+
+* **dateparser:**
+  * Add support for HH, H, mm, m, ss, s formats ([971a1b57](https://github.com/angular-ui/bootstrap/commit/971a1b57394c1e1088564e0809c1341620586aa0), closes [#2509](https://github.com/angular-ui/bootstrap/issues/2509), [#3159](https://github.com/angular-ui/bootstrap/issues/3159), [#3417](https://github.com/angular-ui/bootstrap/issues/3417))
+  * add support for milliseconds ([82cb637d](https://github.com/angular-ui/bootstrap/commit/82cb637dd12288d9fa0d42783f672fc71b94ffda), closes [#3537](https://github.com/angular-ui/bootstrap/issues/3537))
+* **datepicker:**
+  * support HTML5 month input type ([aef8953c](https://github.com/angular-ui/bootstrap/commit/aef8953c792066c008d8ce97918153f756a39112), closes [#3499](https://github.com/angular-ui/bootstrap/issues/3499))
+  * support HTML5 date input type ([1a9e88fe](https://github.com/angular-ui/bootstrap/commit/1a9e88fe9e6a078943273012429788e742f8ebbd), closes [#3499](https://github.com/angular-ui/bootstrap/issues/3499))
+  * Add custom class to specific days via outside logic ([0bcd30c4](https://github.com/angular-ui/bootstrap/commit/0bcd30c4a6d65763e1c4d1b6820a4537a11e95ee))
+* **dropdown:**
+  * Dropdown append-to-body ([dfe9854b](https://github.com/angular-ui/bootstrap/commit/dfe9854be3d0ae82f361cefec19a6f692aa41591), closes [#3411](https://github.com/angular-ui/bootstrap/issues/3411), [#1030](https://github.com/angular-ui/bootstrap/issues/1030))
+  * Make Auto-Close Dropdowns optional. ([a50f1120](https://github.com/angular-ui/bootstrap/commit/a50f11201eeb18569dca0ac3bb157e43cb9d9076), closes [#2218](https://github.com/angular-ui/bootstrap/issues/2218), [#3045](https://github.com/angular-ui/bootstrap/issues/3045))
+* **modal:**
+  * Add a vetoable modal.closing event ([a5a82d9b](https://github.com/angular-ui/bootstrap/commit/a5a82d9be7dc0bd1cfd510bacf9aa411c6efe1bc))
+  * pass reason when opened promise rejected ([0ad208d6](https://github.com/angular-ui/bootstrap/commit/0ad208d6a0164517b85d8e8181e4a0fc98f4f5ec), closes [#2978](https://github.com/angular-ui/bootstrap/issues/2978))
+  * add option to disable animations ([5e661d47](https://github.com/angular-ui/bootstrap/commit/5e661d47d6698fdfebb52402eb1687e5697c0040), closes [#1007](https://github.com/angular-ui/bootstrap/issues/1007), [#2725](https://github.com/angular-ui/bootstrap/issues/2725))
+* **popover:**
+  * respect popover-class option ([88a41dce](https://github.com/angular-ui/bootstrap/commit/88a41dce86c33bf1e0665d880ca68d3535aed553), closes [#3569](https://github.com/angular-ui/bootstrap/issues/3569))
+  * use expression to fix usage with $sce ([422c8234](https://github.com/angular-ui/bootstrap/commit/422c823460663151e3482275d9c6749bb714a415), closes [#3558](https://github.com/angular-ui/bootstrap/issues/3558))
+  * add popover-template directive ([7e3179ab](https://github.com/angular-ui/bootstrap/commit/7e3179ab9fdbba6c00ff1c30f97b432b58f9eafb))
+* **progressbar:** allow dynamic update to max ([7ccff028](https://github.com/angular-ui/bootstrap/commit/7ccff028ff5ed19af63d9cf1f5e078223924f3a0))
+* **rating:** add rounding logic to rating value ([b076483c](https://github.com/angular-ui/bootstrap/commit/b076483caa9b672068ae8d0cbdfeaed68f530861), closes [#3413](https://github.com/angular-ui/bootstrap/issues/3413), [#3415](https://github.com/angular-ui/bootstrap/issues/3415))
+* **tabs:** it should not select first not active tab as selected ([91b5fb62](https://github.com/angular-ui/bootstrap/commit/91b5fb62eedbb600d6a6abe32376846f327a903d))
+* **timepicker:**
+  * always pad minutes ([6324486d](https://github.com/angular-ui/bootstrap/commit/6324486d70edc01fb28f61f0a68a6a490378d602), closes [#1598](https://github.com/angular-ui/bootstrap/issues/1598), [#3533](https://github.com/angular-ui/bootstrap/issues/3533))
+  * have up/down arrow keys control time selection ([22961157](https://github.com/angular-ui/bootstrap/commit/22961157ad4636db12336abba54cc893554b99d6))
+* **tooltip:**
+  * use expression to fix usage with $sce ([d867f830](https://github.com/angular-ui/bootstrap/commit/d867f8302d4632a34c81d151923b497f9af3fcfd), closes [#3558](https://github.com/angular-ui/bootstrap/issues/3558))
+  * add tooltip-html directive ([e31fcf0f](https://github.com/angular-ui/bootstrap/commit/e31fcf0fcb06580064d1e6375dbedb69f1c95f25), closes [#3496](https://github.com/angular-ui/bootstrap/issues/3496))
+  * add tooltip-template directive ([a1695114](https://github.com/angular-ui/bootstrap/commit/a1695114a245312878d315dfc9e369f98d573eae), closes [#220](https://github.com/angular-ui/bootstrap/issues/220))
+  * update position dynamically ([853fa457](https://github.com/angular-ui/bootstrap/commit/853fa4578a1f127fee9283e725d6e19789882121), closes [#96](https://github.com/angular-ui/bootstrap/issues/96), [#1109](https://github.com/angular-ui/bootstrap/issues/1109), [#2816](https://github.com/angular-ui/bootstrap/issues/2816), [#3435](https://github.com/angular-ui/bootstrap/issues/3435))
+  * Support for tooltip-class configuration ([d784354a](https://github.com/angular-ui/bootstrap/commit/d784354a53fa40597cb8c124405ea9a90b6f0b8e), closes [#3126](https://github.com/angular-ui/bootstrap/issues/3126))
+* **transition:** deprecate transition module ([8a552443](https://github.com/angular-ui/bootstrap/commit/8a552443741d1e5b4b29d9da9c7e9990fa37886c), closes [#3497](https://github.com/angular-ui/bootstrap/issues/3497))
+
+
+# 0.12.1 (2015-02-20)
+
+## Bug Fixes
+
+- **tooltip:** 
+  - incorrect position when text wraps ([5726e3ef](http://github.com/angular-ui/bootstrap/commit/5726e3ef))   
+
+<a name="0.12.0"></a>
+# 0.12.0 (2014-11-16)
+
+
+## Bug Fixes
+
+* **accordion:** make header links keyboard accessible ([992b2329](http://github.com/angular-ui/bootstrap/commit/992b23297cd100ab4e236fba469e3a70566a4163), closes [#2869](http://github.com/angular-ui/bootstrap/issues/2869))
+* **build:** make custom builds on demo site work ([390f2bf6](http://github.com/angular-ui/bootstrap/commit/390f2bf6b0846ee640e86ad87bbae8c449e53026), closes [#2960](http://github.com/angular-ui/bootstrap/issues/2960), [#2847](http://github.com/angular-ui/bootstrap/issues/2847), [#2625](http://github.com/angular-ui/bootstrap/issues/2625), [#2489](http://github.com/angular-ui/bootstrap/issues/2489), [#2357](http://github.com/angular-ui/bootstrap/issues/2357), [#2176](http://github.com/angular-ui/bootstrap/issues/2176), [#2892](http://github.com/angular-ui/bootstrap/issues/2892))
+* **carousel:** replaced $timeout with $interval when it was wrong ([392c0ad1](http://github.com/angular-ui/bootstrap/commit/392c0ad13ca9b65be5e77ac0c68de24ead8ea2ce), closes [#1308](http://github.com/angular-ui/bootstrap/issues/1308), [#2454](http://github.com/angular-ui/bootstrap/issues/2454), [#2776](http://github.com/angular-ui/bootstrap/issues/2776))
+* **datepicker:** correct button alignment when using bootstrap v3.2.0 ([460fbec7](http://github.com/angular-ui/bootstrap/commit/460fbec776c6d08d0e7db40aedd29d10ac48d7e9), closes [#2728](http://github.com/angular-ui/bootstrap/issues/2728))
+* **demo:** initial load of fragment URLs ([eab6daf6](http://github.com/angular-ui/bootstrap/commit/eab6daf64b3c963d8e285e254c75af5f97c24ec1), closes [#2762](http://github.com/angular-ui/bootstrap/issues/2762))
+* **dropdown:**
+  * compatibility with `$location` url rewriting ([ef095170](http://github.com/angular-ui/bootstrap/commit/ef09517061b0b4c0c9e9f85086635af33207ec54), closes [#2343](http://github.com/angular-ui/bootstrap/issues/2343))
+  * remove `C` restrictions to avoid conflicts ([192768e1](http://github.com/angular-ui/bootstrap/commit/192768e109b5c4a50c7dcd320e09d05fedd4298a), closes [#2156](http://github.com/angular-ui/bootstrap/issues/2156), [#2170](http://github.com/angular-ui/bootstrap/issues/2170))
+* **tabs:**
+  * make tab links keyboard accessible ([5df524b7](http://github.com/angular-ui/bootstrap/commit/5df524b77114bccdc9a49540e1eb52a564ee5dfd), closes [#2226](http://github.com/angular-ui/bootstrap/issues/2226), [#2290](http://github.com/angular-ui/bootstrap/issues/2290), [#2870](http://github.com/angular-ui/bootstrap/issues/2870), [#2304](http://github.com/angular-ui/bootstrap/issues/2304))
+  * don't select tabs on destroy ([9939867a](http://github.com/angular-ui/bootstrap/commit/9939867aba0b7b763588b18829b557c052ea69ba), closes [#2155](http://github.com/angular-ui/bootstrap/issues/2155), [#2596](http://github.com/angular-ui/bootstrap/issues/2596))
+* **tests:** usage of undefined variables ([34273ff0](http://github.com/angular-ui/bootstrap/commit/34273ff0107ecfa28438a7389d94ca619b8704e5))
+* **tooltip:**
+  * remove extra digest causing incompatibility ([32c4704b](http://github.com/angular-ui/bootstrap/commit/32c4704b748cecf2de4c651f2e5157c1ef6c88b1), closes [#2951](http://github.com/angular-ui/bootstrap/issues/2951), [#2959](http://github.com/angular-ui/bootstrap/issues/2959))
+  * show correct tooltip on `ng-repeat` ([b4832c4b](http://github.com/angular-ui/bootstrap/commit/b4832c4b551af7e580ed65d9e5aaee1ef9e6c53e), closes [#2935](http://github.com/angular-ui/bootstrap/issues/2935))
+  * memory leak on show/hide ([faf38d20](http://github.com/angular-ui/bootstrap/commit/faf38d20a49176f2016f7f7d4fa49a5c438a986e), closes [#2709](http://github.com/angular-ui/bootstrap/issues/2709), [#2919](http://github.com/angular-ui/bootstrap/issues/2919))
+  * remove child scope requirement ([8204c808](http://github.com/angular-ui/bootstrap/commit/8204c8088139165ac9b2ad3977a2c20570e434cb), closes [#1269](http://github.com/angular-ui/bootstrap/issues/1269), [#2320](http://github.com/angular-ui/bootstrap/issues/2320), [#2203](http://github.com/angular-ui/bootstrap/issues/2203))
+  * evaluate appendToBody on init ([e10d561f](http://github.com/angular-ui/bootstrap/commit/e10d561f92c2927be0ec429761fa229520fb9a51), closes [#2921](http://github.com/angular-ui/bootstrap/issues/2921))
+  * don't use an empty transclusion fn ([689c4d01](http://github.com/angular-ui/bootstrap/commit/689c4d017d303b6d758164ee12837a172bb01139), closes [#2825](http://github.com/angular-ui/bootstrap/issues/2825))
+* **typeahead:** don't leak DOM nodes ([1f6c3c92](http://github.com/angular-ui/bootstrap/commit/1f6c3c92af0e343c7e34b85ea6d270ac79bf6755))
+
+
+## Features
+
+* **alert:** allow alerts to be closed from a controller ([ca6fad67](http://github.com/angular-ui/bootstrap/commit/ca6fad675bf2aa793896bf3e086330667a5d9051), closes [#2399](http://github.com/angular-ui/bootstrap/issues/2399), [#2854](http://github.com/angular-ui/bootstrap/issues/2854))
+* **typeahead:** add focus-first option ([35d0cc1d](http://github.com/angular-ui/bootstrap/commit/35d0cc1d57302883840f7ad54a03918ae2df001d), closes [#908](http://github.com/angular-ui/bootstrap/issues/908), [#2916](http://github.com/angular-ui/bootstrap/issues/2916))
+
+
+## Breaking Changes
+
+* `tooltip-trigger` and `popover-trigger` are no longer watched
+attributes.
+([a65bea95](http://github.com/angular-ui/bootstrap/commit/a65bea95338802b026fd213805b095b5a0b5b393))  
+This affects both popovers and tooltips. The *triggers are now set up
+once* and can no longer be changed after initialization.
+
+* `dropdown` and `dropdown-toggle` are attribute-only directives. ([192768e1](http://github.com/angular-ui/bootstrap/commit/192768e109b5c4a50c7dcd320e09d05fedd4298a))
+
+  Before:
+    ```html
+    <button class="dropdown-toggle" ...>
+    ```
+    After:
+    ```html
+    <button class="dropdown-toggle" dropdown-toggle ...>
+    ```
+
+
+# 0.11.2 (2014-09-26)
+
+Revert breaking change in **dropdown** ([1a998c4](http://github.com/angular-ui/bootstrap/commit/1a998c4))
+
+# 0.11.1 (2014-09-26)
+
+## Features
+
+- **modal:** 
+  - add backdropClass option, similar to windowClass option ([353e6127](http://github.com/angular-ui/bootstrap/commit/353e6127))  
+  - support alternative controllerAs syntax ([8d7c2a26](http://github.com/angular-ui/bootstrap/commit/8d7c2a26))  
+  - allow templateUrl to be a function ([990015fb](http://github.com/angular-ui/bootstrap/commit/990015fb))   
+
+## Bug Fixes
+
+- **alert:** 
+  - correct binding of alert type class ([aa188aec](http://github.com/angular-ui/bootstrap/commit/aa188aec))  
+- **dateparser:** 
+  - do not parse if no format specified ([42cc3f26](http://github.com/angular-ui/bootstrap/commit/42cc3f26))  
+- **datepicker:** 
+  - correct `datepicker-mode` binding for popup ([63ae06c9](http://github.com/angular-ui/bootstrap/commit/63ae06c9))  
+  - memory leak fix for datepicker ([08c150e1](http://github.com/angular-ui/bootstrap/commit/08c150e1))  
+- **dropdown:** 
+  - close after selecting an item ([3ac3b487](http://github.com/angular-ui/bootstrap/commit/3ac3b487))  
+  - remove `C` restrictions to avoid conflicts ([7512b93f](http://github.com/angular-ui/bootstrap/commit/7512b93f))  
+- **modal:** 
+  - allow modal.{dismiss,close} to be called again ([1590920c](http://github.com/angular-ui/bootstrap/commit/1590920c))  
+  - add a work-around for transclusion scope ([0b31e865](http://github.com/angular-ui/bootstrap/commit/0b31e865))  
+  - allow in-lined controller-as controllers ([79105368](http://github.com/angular-ui/bootstrap/commit/79105368))  
+  - respect autofocus on child elements ([e62ab94a](http://github.com/angular-ui/bootstrap/commit/e62ab94a))  
+  - controllerAs not checked ([7b7cdf84](http://github.com/angular-ui/bootstrap/commit/7b7cdf84))  
+- **tabs:** 
+  - remove leading newline from a template ([a708fe6d](http://github.com/angular-ui/bootstrap/commit/a708fe6d))  
+- **typeahead:** 
+  - timeout cancellation when deleting characters ([5dc57927](http://github.com/angular-ui/bootstrap/commit/5dc57927))  
+  - allow multiple line expression ([c7db0df4](http://github.com/angular-ui/bootstrap/commit/c7db0df4))  
+  - replace ng-if with ng-show in matches popup ([a0be450d](http://github.com/angular-ui/bootstrap/commit/a0be450d))   
+
+# 0.11.0 (2014-05-01)
+
+## Features
+
+- **accordion:** 
+  - support `is-disabled` state ([9c43ae7c](http://github.com/angular-ui/bootstrap/commit/9c43ae7c))  
+- **alert:** 
+  - add WAI-ARIA markup ([9a2638bf](http://github.com/angular-ui/bootstrap/commit/9a2638bf))  
+- **button:** 
+  - allow uncheckable radio button ([82df4fb1](http://github.com/angular-ui/bootstrap/commit/82df4fb1))  
+- **carousel:** 
+  - Support swipe for touchscreen devices ([85140f84](http://github.com/angular-ui/bootstrap/commit/85140f84))  
+- **dateParser:** 
+  - add `dateParser` service ([bd2ae0ee](http://github.com/angular-ui/bootstrap/commit/bd2ae0ee))  
+- **datepicker:** 
+  - add `datepicker-mode`, `init-date` & today hint ([7f4b40eb](http://github.com/angular-ui/bootstrap/commit/7f4b40eb))  
+  - make widget accessible ([2423f6d4](http://github.com/angular-ui/bootstrap/commit/2423f6d4))  
+  - full six-week calendar ([b0b14343](http://github.com/angular-ui/bootstrap/commit/b0b14343))  
+- **dropdown:** 
+  - add WAI-ARIA attributes ([22ebd230](http://github.com/angular-ui/bootstrap/commit/22ebd230))  
+  - focus toggle element when opening or closing with Esc` ([f715d052](http://github.com/angular-ui/bootstrap/commit/f715d052))  
+- **dropdownToggle:** 
+  - support programmatic trigger & toggle callback ([ae31079c](http://github.com/angular-ui/bootstrap/commit/ae31079c))  
+  - add support for `escape` key ([1417c548](http://github.com/angular-ui/bootstrap/commit/1417c548))  
+- **modal:** 
+  - support custom template for modal window ([96def3d6](http://github.com/angular-ui/bootstrap/commit/96def3d6))  
+  - support modal window sizes ([976f6083](http://github.com/angular-ui/bootstrap/commit/976f6083))  
+  - improve accessibility - add role='dialog' ([60cee9dc](http://github.com/angular-ui/bootstrap/commit/60cee9dc))  
+- **pagination:** 
+  - plug into `ngModel` controller ([d65901cf](http://github.com/angular-ui/bootstrap/commit/d65901cf))  
+- **progressbar:** 
+  - make widget accessible ([9dfe3157](http://github.com/angular-ui/bootstrap/commit/9dfe3157))  
+- **rating:** 
+  - plug into `ngModel` controller ([47e227f6](http://github.com/angular-ui/bootstrap/commit/47e227f6))  
+  - make widget accessible ([4f56e60e](http://github.com/angular-ui/bootstrap/commit/4f56e60e))  
+- **tooltip:** 
+  - support more positioning options ([3704db9a](http://github.com/angular-ui/bootstrap/commit/3704db9a))  
+- **typeahead:** 
+  - add WAI-ARIA markup ([5ca23e97](http://github.com/angular-ui/bootstrap/commit/5ca23e97))  
+  - add `aria-owns` & `aria-activedescendant` roles ([4c76a858](http://github.com/angular-ui/bootstrap/commit/4c76a858))   
+
+## Bug Fixes
+
+- **alert:** 
+  - use interpolation for type attribute ([f0a129ad](http://github.com/angular-ui/bootstrap/commit/f0a129ad))  
+  - add `alert-dismissable` class ([794954af](http://github.com/angular-ui/bootstrap/commit/794954af))  
+- **carousel:** 
+  - correct glyphicon ([3b6ab25b](http://github.com/angular-ui/bootstrap/commit/3b6ab25b))  
+- **datepicker:** 
+  - remove unneeded date creation ([68cb2e5a](http://github.com/angular-ui/bootstrap/commit/68cb2e5a))  
+  - `Today` button should not set time ([e1993491](http://github.com/angular-ui/bootstrap/commit/e1993491))  
+  - mark input field as invalid if the date is invalid ([467dd159](http://github.com/angular-ui/bootstrap/commit/467dd159))  
+  - rename `dateFormat` to `datepickerPopup` in datepickerPopupConfig ([93da30d5](http://github.com/angular-ui/bootstrap/commit/93da30d5))  
+  - parse input using dateParser ([e0eb1bce](http://github.com/angular-ui/bootstrap/commit/e0eb1bce))  
+- **dropdown:**
+  - use $animate for adding and removing classes ([e8d5fefc](http://github.com/angular-ui/bootstrap/commit/e8d5fefc))  
+  - unbind toggle element event on scope destroy ([890e2d37](http://github.com/angular-ui/bootstrap/commit/890e2d37))  
+  - do not call `on-toggle` initially ([004dd1de](http://github.com/angular-ui/bootstrap/commit/004dd1de))  
+  - ensure `on-toggle` works when `is-open` is not used ([06ad3bd5](http://github.com/angular-ui/bootstrap/commit/06ad3bd5))  
+- **modal:**
+  - destroy modal scope after animation end ([dfc36fd9](http://github.com/angular-ui/bootstrap/commit/dfc36fd9))  
+  - backdrop z-index when stacking modals ([94a7f593](http://github.com/angular-ui/bootstrap/commit/94a7f593))  
+  - give a reason of rejection when escape key pressed ([cb31b875](http://github.com/angular-ui/bootstrap/commit/cb31b875))  
+  - prevent default event when closing via escape key ([da951222](http://github.com/angular-ui/bootstrap/commit/da951222))
+  - toggle 'modal-open' class after animation ([4d641ca7](http://github.com/angular-ui/bootstrap/commit/4d641ca7))
+- **pagination:** 
+  - take maxSize defaults into account ([a294c87f](http://github.com/angular-ui/bootstrap/commit/a294c87f))  
+- **position:** 
+  - remove deprecated body scrollTop and scrollLeft ([1ba07c1b](http://github.com/angular-ui/bootstrap/commit/1ba07c1b))  
+- **progressbar:** 
+  - allow fractional values for bar width ([0daa7a74](http://github.com/angular-ui/bootstrap/commit/0daa7a74))  
+  - number filter in bar template and only for percent ([378a9337](http://github.com/angular-ui/bootstrap/commit/378a9337))  
+- **tabs:** 
+  - fire deselect before select callback ([7474c47b](http://github.com/angular-ui/bootstrap/commit/7474c47b))  
+  - use interpolation for type attribute ([83ceb78a](http://github.com/angular-ui/bootstrap/commit/83ceb78a))  
+  - remove `tabbable` class required for left/right tabs ([19468331](http://github.com/angular-ui/bootstrap/commit/19468331))  
+- **timepicker:** 
+  - evaluate correctly the `readonly-input` attribute ([f9b6c496](http://github.com/angular-ui/bootstrap/commit/f9b6c496))  
+- **tooltip:** 
+  - animation causes tooltip to hide on show ([2b429f5d](http://github.com/angular-ui/bootstrap/commit/2b429f5d))  
+- **typeahead:** 
+  - correctly handle append to body attribute ([10785736](http://github.com/angular-ui/bootstrap/commit/10785736))  
+  - correctly higlight numeric matches ([09678b12](http://github.com/angular-ui/bootstrap/commit/09678b12))  
+  - loading callback updates after blur ([6a830116](http://github.com/angular-ui/bootstrap/commit/6a830116))  
+  - incompatibility with ng-focus ([d0024931](http://github.com/angular-ui/bootstrap/commit/d0024931))   
+
+## Breaking Changes
+
+- **alert:** 
+ Use interpolation for type attribute.
+
+  Before:
+
+  ```html
+  <alert type="'info'" ...></alert >
+  ```
+  or
+  ```html
+  <alert type="alert.type" ...></alert >
+  ```
+
+  After:
+
+  ```html
+  <alert type="info" ...></alert >
+  ```
+  or
+  ```html
+  <alert type="{{alert.type}}" ...></alert >
+  ```
+
+- **datepicker:** 
+
+`show-weeks` is no longer a watched attribute
+`*-format` attributes have been renamed to `format-*`
+`min` attribute has been renamed to `min-date`
+`max` attribute has been renamed to `max-date`
+`Open on focus` has been removed. Read more on this ([comment](https://github.com/angular-ui/bootstrap/pull/1922#issuecomment-40491716)). 
+`dateFormat` renamed to `datepickerPopup` in datepickerPopupConfig
+
+- **dropdown:**
+
+ Elements with the `dropdown-toggle` directive must have a parent element with the `dropdown` directive.
+
+- **pagination:**
+
+ Both `pagination` and `pager` are now integrated with `ngModelController`.
+ * `page` is replaced from `ng-model`.
+ * `on-select-page` is removed since `ng-change` can now be used.
+
+  Before:
+  
+  ```html
+  <pagination page="current" on-select-page="changed(page)" ...></pagination>
+  ```
+  
+  After:
+
+  ```html
+  <pagination ng-model="current" ng-change="changed()" ...></pagination>
+  ```
+  
+- **rating:** 
+ `rating` is now integrated with `ngModelController`.
+ * `value` is replaced from `ng-model`.
+
+  Before:
+
+  ```html
+  <rating value="rate" ...></rating>
+  ```
+  
+  After:
+  
+  ```html
+  <rating ng-model="rate" ...></rating>
+  ```
+  
+- **tabs:**
+
+ Use interpolation for type attribute.
+
+  Before:
+  
+  ```html
+  <tabset type="'pills'" ...></tabset >
+  <!-- or -->
+  <tabset type="navtype" ...></tabset>
+  ```
+  
+  After:
+  
+  ```html
+  <tabset type="pills" ...></tabset>
+  <!-- or -->
+  <tabset type="{{navtype}}" ...></tabset>
+  ```
+  
+# 0.10.0 (2014-01-13)
+
+_This release adds AngularJS 1.2 support_
+
+## Features
+
+- **modal:** 
+  - expose dismissAll on $modalStack ([bc8d21c1](http://github.com/angular-ui/bootstrap/commit/bc8d21c1))   
+
+## Bug Fixes
+
+- **datepicker:** 
+  - evaluate `show-weeks` from `datepicker-options` ([92c1715f](http://github.com/angular-ui/bootstrap/commit/92c1715f))  
+- **modal:** 
+  - leaking watchers due to scope re-use ([0754ad7b](http://github.com/angular-ui/bootstrap/commit/0754ad7b))  
+  - support close animation ([1933488c](http://github.com/angular-ui/bootstrap/commit/1933488c))  
+- **timepicker:** 
+  - add correct type for meridian button ([bcf39efe](http://github.com/angular-ui/bootstrap/commit/bcf39efe))  
+- **tooltip:** 
+  - performance and scope fixes ([c0df3201](http://github.com/angular-ui/bootstrap/commit/c0df3201))   
+
+# 0.9.0 (2013-12-28)
+
+_This release adds Bootstrap3 support_
+
+## Features
+
+- **accordion:** 
+  - convert to bootstrap3 panel styling ([458a9bd3](http://github.com/angular-ui/bootstrap/commit/458a9bd3))  
+- **carousel:** 
+  - some changes for Bootstrap3 ([1f632b65](http://github.com/angular-ui/bootstrap/commit/1f632b65))  
+- **collapse:** 
+  - make collapse work with bootstrap3 ([517dff6e](http://github.com/angular-ui/bootstrap/commit/517dff6e))  
+- **datepicker:** 
+  - update to Bootstrap 3 ([37684330](http://github.com/angular-ui/bootstrap/commit/37684330))  
+- **modal:** 
+  - added bootstrap3 support ([444c488d](http://github.com/angular-ui/bootstrap/commit/444c488d))  
+- **pagination:** 
+  - support bootstrap3 ([3db699d7](http://github.com/angular-ui/bootstrap/commit/3db699d7))  
+- **progressbar:** 
+  - update to bootstrap3 ([5bcff623](http://github.com/angular-ui/bootstrap/commit/5bcff623))  
+- **rating:** 
+  - update rating to bootstrap3 ([7e60284e](http://github.com/angular-ui/bootstrap/commit/7e60284e))  
+- **tabs:** 
+  - add nav-justified ([3199dd88](http://github.com/angular-ui/bootstrap/commit/3199dd88))  
+- **timepicker:** 
+  - restyled for bootstrap 3 ([6724a721](http://github.com/angular-ui/bootstrap/commit/6724a721))  
+- **typeahead:** 
+  - update to Bootstrap 3 ([eadf934a](http://github.com/angular-ui/bootstrap/commit/eadf934a))   
+
+## Bug Fixes
+
+- **alert:** 
+  - update template to Bootstrap 3 ([dfc3b0bd](http://github.com/angular-ui/bootstrap/commit/dfc3b0bd))  
+- **collapse:** 
+  - Prevent consecutive transitions & tidy up code ([b0032d68](http://github.com/angular-ui/bootstrap/commit/b0032d68))  
+  - fixes after rebase ([dc02ad1d](http://github.com/angular-ui/bootstrap/commit/dc02ad1d))  
+- **rating:** 
+  - user glyhicon classes ([d221d517](http://github.com/angular-ui/bootstrap/commit/d221d517))  
+- **timepicker:** 
+  - fix look with bootstrap3 ([9613b61b](http://github.com/angular-ui/bootstrap/commit/9613b61b))  
+- **tooltip:** 
+  - re-position tooltip after draw ([a99b3608](http://github.com/angular-ui/bootstrap/commit/a99b3608))   
+
+# 0.8.0 (2013-12-28)
+
+## Features
+
+- **datepicker:** 
+  - option whether to display button bar in popup ([4d158e0d](http://github.com/angular-ui/bootstrap/commit/4d158e0d))  
+- **modal:** 
+  - add modal-open class to body on modal open ([e76512fa](http://github.com/angular-ui/bootstrap/commit/e76512fa))  
+- **progressbar:** 
+  - add `max` attribute & support transclusion ([365573ab](http://github.com/angular-ui/bootstrap/commit/365573ab))  
+- **timepicker:** 
+  - default meridian labels based on locale ([8b1ab79a](http://github.com/angular-ui/bootstrap/commit/8b1ab79a))  
+- **typeahead:** 
+  - add typeahead-append-to-body option ([dd8eac22](http://github.com/angular-ui/bootstrap/commit/dd8eac22))   
+
+## Bug Fixes
+
+- **accordion:** 
+  - correct `is-open` handling for dynamic groups ([9ec21286](http://github.com/angular-ui/bootstrap/commit/9ec21286))  
+- **carousel:** 
+  - cancel timer on scope destruction ([5b9d929c](http://github.com/angular-ui/bootstrap/commit/5b9d929c))  
+  - cancel goNext on scope destruction ([7515df45](http://github.com/angular-ui/bootstrap/commit/7515df45))  
+- **collapse:** 
+  - dont animate height changes from 0 to 0 ([81e014a8](http://github.com/angular-ui/bootstrap/commit/81e014a8))  
+- **datepicker:** 
+  - set default zero time after no date selected ([93cd0df8](http://github.com/angular-ui/bootstrap/commit/93cd0df8))  
+  - fire `ngChange` on today/clear button press ([6b1c68fb](http://github.com/angular-ui/bootstrap/commit/6b1c68fb))  
+  - remove datepicker's popup on scope destroy ([48955d69](http://github.com/angular-ui/bootstrap/commit/48955d69))  
+  - remove edge case position updates ([1fbcb5d6](http://github.com/angular-ui/bootstrap/commit/1fbcb5d6))  
+- **modal:** 
+  - put backdrop in before window ([d64f4a97](http://github.com/angular-ui/bootstrap/commit/d64f4a97))  
+  - grab reference to body when it is needed in lieu of when the factory is created ([dd415a98](http://github.com/angular-ui/bootstrap/commit/dd415a98))
+  - focus freshly opened modal ([709e679c](http://github.com/angular-ui/bootstrap/commit/709e679c))  
+  - properly animate backdrops on each modal opening ([672a557a](http://github.com/angular-ui/bootstrap/commit/672a557a))  
+- **tabs:** 
+  - make nested tabs work ([c9acebbe](http://github.com/angular-ui/bootstrap/commit/c9acebbe))  
+- **tooltip:** 
+  - update tooltip content when empty ([60515ae1](http://github.com/angular-ui/bootstrap/commit/60515ae1))  
+  - support IE8 ([5dd98238](http://github.com/angular-ui/bootstrap/commit/5dd98238))  
+  - unbind element events on scope destroy ([3fe7aa8c](http://github.com/angular-ui/bootstrap/commit/3fe7aa8c))  
+  - respect animate attribute ([54e614a8](http://github.com/angular-ui/bootstrap/commit/54e614a8))   
+
+## Breaking Changes
+
+- **progressbar:** 
+ The onFull/onEmpty handlers & auto/stacked types have been removed.
+
+  To migrate your code change your markup like below.
+
+  Before:
+
+```html
+  <progress percent="var" class="progress-warning"></progress>
+```
+
+  After:
+
+```html
+  <progressbar value="var" type="warning"></progressbar>
+```
+
+  and for stacked instead of passing array/objects you can do:
+
+```html
+  <progress><bar ng-repeat="obj in objs" value="obj.var" type="{{obj.type}}"></bar></progress>
+```
+
+# 0.7.0 (2013-11-22)
+
+## Features
+
+- **datepicker:** 
+  - add i18n support for bar buttons in popup ([c6ba8d7f](http://github.com/angular-ui/bootstrap/commit/c6ba8d7f))  
+  - dynamic date format for popup ([aa3eaa91](http://github.com/angular-ui/bootstrap/commit/aa3eaa91))  
+  - datepicker-append-to-body attribute ([0cdc4609](http://github.com/angular-ui/bootstrap/commit/0cdc4609))  
+- **dropdownToggle:** 
+  - disable dropdown when it has the disabled class ([104bdd1b](http://github.com/angular-ui/bootstrap/commit/104bdd1b))  
+- **tooltip:** 
+  - add ability to enable / disable tooltip ([5d9bd058](http://github.com/angular-ui/bootstrap/commit/5d9bd058))   
+
+## Bug Fixes
+
+- **accordion:** 
+  - assign `is-open` to correct scope ([157f614a](http://github.com/angular-ui/bootstrap/commit/157f614a))  
+- **collapse:** 
+  - remove element height watching ([a72c635c](http://github.com/angular-ui/bootstrap/commit/a72c635c))  
+  - add the "in" class for expanded panels ([9eca35a8](http://github.com/angular-ui/bootstrap/commit/9eca35a8))  
+- **datepicker:**
+  - some IE8 compatibility improvements ([4540476f](http://github.com/angular-ui/bootstrap/commit/4540476f))  
+  - set popup initial position in append-to-body case ([78a1e9d7](http://github.com/angular-ui/bootstrap/commit/78a1e9d7))
+  - properly handle showWeeks config option ([570dba90](http://github.com/angular-ui/bootstrap/commit/570dba90))  
+- **modal:** 
+  - correctly close modals with no backdrop ([e55c2de3](http://github.com/angular-ui/bootstrap/commit/e55c2de3))  
+- **pagination:** 
+  - fix altering of current page caused by totals change ([81164dae](http://github.com/angular-ui/bootstrap/commit/81164dae))  
+  - handle extreme values for `total-items` ([8ecf93ed](http://github.com/angular-ui/bootstrap/commit/8ecf93ed))  
+- **position:** 
+  - correct positioning for SVG elements ([968e5407](http://github.com/angular-ui/bootstrap/commit/968e5407))  
+- **tabs:** 
+  - initial tab selection ([a08173ec](http://github.com/angular-ui/bootstrap/commit/a08173ec))  
+- **timepicker:** 
+  - use html5 for input elements ([53709f0f](http://github.com/angular-ui/bootstrap/commit/53709f0f))  
+- **tooltip:** 
+  - restore html-unsafe compatibility with AngularJS 1.2 ([08d8b21d](http://github.com/angular-ui/bootstrap/commit/08d8b21d))  
+  - hide tooltips when content becomes empty ([cf5c27ae](http://github.com/angular-ui/bootstrap/commit/cf5c27ae))  
+  - tackle DOM node and event handlers leak ([0d810acd](http://github.com/angular-ui/bootstrap/commit/0d810acd))  
+- **typeahead:** 
+  - do not set editable error when input is empty ([006986db](http://github.com/angular-ui/bootstrap/commit/006986db))  
+  - remove popup flickering ([dde804b6](http://github.com/angular-ui/bootstrap/commit/dde804b6))  
+  - don't show matches if an element is not focused ([d1f94530](http://github.com/angular-ui/bootstrap/commit/d1f94530))  
+  - fix loading callback when deleting characters ([0149eff6](http://github.com/angular-ui/bootstrap/commit/0149eff6))  
+  - prevent accidental form submission on ENTER ([253c49ff](http://github.com/angular-ui/bootstrap/commit/253c49ff))  
+  - evaluate matches source against a correct scope ([fd21214d](http://github.com/angular-ui/bootstrap/commit/fd21214d))  
+  - support IE8 ([0e9f9980](http://github.com/angular-ui/bootstrap/commit/0e9f9980))   
+
+# 0.6.0 (2013-09-08)
+
+## Features
+
+- **modal:** 
+  - rewrite $dialog as $modal ([d7a48523](http://github.com/angular-ui/bootstrap/commit/d7a48523))  
+  - add support for custom window settings ([015625d1](http://github.com/angular-ui/bootstrap/commit/015625d1))  
+  - expose $close and $dismiss options on modal's scope ([8d153acb](http://github.com/angular-ui/bootstrap/commit/8d153acb))  
+- **pagination:** 
+  - `total-items` & optional `items-per-page` API ([e55d9063](http://github.com/angular-ui/bootstrap/commit/e55d9063))  
+- **rating:** 
+  - add support for custom icons per instance ([20ab01ad](http://github.com/angular-ui/bootstrap/commit/20ab01ad))  
+- **timepicker:** 
+  - plug into `ngModel` controller ([b08e993f](http://github.com/angular-ui/bootstrap/commit/b08e993f))   
+
+## Bug Fixes
+
+- **carousel:** 
+  - correct reflow triggering on FFox and Safari ([d34f2de1](http://github.com/angular-ui/bootstrap/commit/d34f2de1))  
+- **datepicker:** 
+  - correctly manage focus without jQuery present ([d474824b](http://github.com/angular-ui/bootstrap/commit/d474824b))  
+  - compatibility with angular 1.1.5 and no jquery ([bf30898d](http://github.com/angular-ui/bootstrap/commit/bf30898d))  
+  - use $setViewValue for inner changes ([dd99f35d](http://github.com/angular-ui/bootstrap/commit/dd99f35d))
+- **modal:**
+  - insert backdrop before modal window ([d870f212](http://github.com/angular-ui/bootstrap/commit/d870f212))
+  - ie8 fix after $modal rewrite ([ff9d969e](http://github.com/angular-ui/bootstrap/commit/ff9d969e))
+  - opening a modal should not change default options ([82532d1b](http://github.com/angular-ui/bootstrap/commit/82532d1b))
+  - backdrop should cover previously opened modals ([7fce2fe8](http://github.com/angular-ui/bootstrap/commit/7fce2fe8))
+  - allow replacing object with default options ([8e7fbf06](http://github.com/angular-ui/bootstrap/commit/8e7fbf06))
+- **position:**
+  - fallback for IE8's scrollTop/Left for offset ([9aecd4ed](http://github.com/angular-ui/bootstrap/commit/9aecd4ed))  
+- **tabs:** 
+  - add DI array-style annotations ([aac4a0dd](http://github.com/angular-ui/bootstrap/commit/aac4a0dd))  
+  - evaluate `vertical` on parent scope ([9af6f96e](http://github.com/angular-ui/bootstrap/commit/9af6f96e))  
+- **timepicker:** 
+  - add type attribute for meridian button ([1f89fd4b](http://github.com/angular-ui/bootstrap/commit/1f89fd4b))  
+- **tooltip:** 
+  - remove placement='mouse' option ([17163c22](http://github.com/angular-ui/bootstrap/commit/17163c22))  
+- **typeahead:** 
+  - fix label rendering for equal model and items names ([5de71216](http://github.com/angular-ui/bootstrap/commit/5de71216))  
+  - set validity flag for non-editable inputs ([366e0c8a](http://github.com/angular-ui/bootstrap/commit/366e0c8a))  
+  - plug in front of existing parsers ([80cef614](http://github.com/angular-ui/bootstrap/commit/80cef614))  
+  - highlight return match if no query ([45dd9be1](http://github.com/angular-ui/bootstrap/commit/45dd9be1))  
+  - keep pop-up on clicking input ([5f9e270d](http://github.com/angular-ui/bootstrap/commit/5f9e270d))  
+  - remove dependency on ng-bind-html-unsafe ([75893393](http://github.com/angular-ui/bootstrap/commit/75893393))   
+
+## Breaking Changes
+
+- **modal:**
+
+* `$dialog` service was refactored into `$modal`
+* `modal` directive was removed - use the `$modal` service instead
+
+Check the documentation for the `$modal` service to migrate from `$dialog`
+
+- **pagination:** 
+ API has undergone some changes in order to be easier to use.
+ * `current-page` is replaced from `page`.
+ * Number of pages is not defined by `num-pages`, but from `total-items` &
+  `items-per-page` instead. If `items-per-page` is missing, default is 10.
+ * `num-pages` still exists but is just readonly.
+
+  Before:
+
+```html
+  <pagination num-pages="10" ...></pagination>
+```
+
+  After:
+
+```html
+  <pagination total-items="100" ...></pagination>
+```
+
+- **tooltip:** 
+
+
+The placment='mouse' is gone with no equivalent
+ 
+# 0.5.0 (2013-08-04)
+
+## Features
+
+- **buttons:** 
+  - support dynamic true / false values in btn-checkbox ([3e30cd94](http://github.com/angular-ui/bootstrap/commit/3e30cd94))  
+- **datepicker:** 
+  - `ngModelController` plug & new `datepickerPopup` ([dab18336](http://github.com/angular-ui/bootstrap/commit/dab18336))  
+- **rating:** 
+  - added onHover and onLeave. ([5b1115e3](http://github.com/angular-ui/bootstrap/commit/5b1115e3))  
+- **tabs:** 
+  - added onDeselect callback, used similarly as onSelect ([fe47c9bb](http://github.com/angular-ui/bootstrap/commit/fe47c9bb))  
+  - add the ability to set the direction of the tabs ([220e7b60](http://github.com/angular-ui/bootstrap/commit/220e7b60))  
+- **typeahead:** 
+  - support custom templates for matched items ([e2238174](http://github.com/angular-ui/bootstrap/commit/e2238174))  
+  - expose index to custom templates ([5ffae83d](http://github.com/angular-ui/bootstrap/commit/5ffae83d))   
+
+## Bug Fixes
+
+- **datepicker:** 
+  - handle correctly `min`/`max` when cleared ([566bdd16](http://github.com/angular-ui/bootstrap/commit/566bdd16))  
+  - add type attribute for buttons ([25caf5fb](http://github.com/angular-ui/bootstrap/commit/25caf5fb))  
+- **pagination:** 
+  - handle `currentPage` number as string ([b1fa7bb8](http://github.com/angular-ui/bootstrap/commit/b1fa7bb8))  
+  - use interpolation for text attributes ([f45815cb](http://github.com/angular-ui/bootstrap/commit/f45815cb))  
+- **popover:** 
+  - don't unbind event handlers created by other directives ([56f624a2](http://github.com/angular-ui/bootstrap/commit/56f624a2))  
+  - correctly position popovers appended to body ([93a82af0](http://github.com/angular-ui/bootstrap/commit/93a82af0))  
+- **rating:** 
+  - evaluate `max` attribute on parent scope ([60619d51](http://github.com/angular-ui/bootstrap/commit/60619d51))  
+- **tabs:** 
+  - make tab contents be correctly connected to parent (#524) ([be7ecff0](http://github.com/angular-ui/bootstrap/commit/be7ecff0))  
+  - Make tabset template correctly use tabset attributes (#584) ([8868f236](http://github.com/angular-ui/bootstrap/commit/8868f236))  
+  - fix tab content compiling wrong (Closes #599, #631, #574) ([224bc2f5](http://github.com/angular-ui/bootstrap/commit/224bc2f5))  
+  - make tabs added with active=true be selected ([360cd5ca](http://github.com/angular-ui/bootstrap/commit/360cd5ca))  
+  - if tab is active at start, always select it ([ba1f741d](http://github.com/angular-ui/bootstrap/commit/ba1f741d))  
+- **timepicker:** 
+  - prevent date change ([ee741707](http://github.com/angular-ui/bootstrap/commit/ee741707))  
+  - added wheel event to enable mousewheel on Firefox ([8dc92afa](http://github.com/angular-ui/bootstrap/commit/8dc92afa))  
+- **tooltip:** 
+  - fix positioning inside scrolling element ([63ae7e12](http://github.com/angular-ui/bootstrap/commit/63ae7e12))  
+  - triggers should be local to tooltip instances ([58e8ef4f](http://github.com/angular-ui/bootstrap/commit/58e8ef4f))  
+  - correctly handle initial events unbinding ([4fd5bf43](http://github.com/angular-ui/bootstrap/commit/4fd5bf43))  
+  - bind correct 'hide' event handler ([d50b0547](http://github.com/angular-ui/bootstrap/commit/d50b0547))  
+- **typeahead:** 
+  - play nicelly with existing formatters ([d2df0b35](http://github.com/angular-ui/bootstrap/commit/d2df0b35))  
+  - properly render initial input value ([c4e169cb](http://github.com/angular-ui/bootstrap/commit/c4e169cb))  
+  - separate text field rendering and drop down rendering ([ea1e858a](http://github.com/angular-ui/bootstrap/commit/ea1e858a))  
+  - fixed waitTime functionality ([90a8aa79](http://github.com/angular-ui/bootstrap/commit/90a8aa79))  
+  - correctly close popup on match selection ([624fd5f5](http://github.com/angular-ui/bootstrap/commit/624fd5f5))   
+
+## Breaking Changes
+
+- **pagination:** 
+ The 'first-text', 'previous-text', 'next-text' and 'last-text'
+  attributes are now interpolated.
+
+  To migrate your code, remove quotes for constant attributes and/or
+  interpolate scope variables.
+
+  Before:
+
+```html
+  <pagination first-text="'<<'" ...></pagination>
+```
+  and/or
+
+```html
+  $scope.var1 = '<<';
+  <pagination first-text="var1" ...></pagination>
+```
+  After:
+
+```html
+  <pagination first-text="<<" ...></pagination>
+```
+  and/or
+
+```html
+  $scope.var1 = '<<';
+  <pagination first-text="{{var1}}" ...></pagination>
+```
+
+# 0.4.0 (2013-06-24)
+
+## Features
+
+- **buttons:** 
+  - support dynamic values in btn-radio ([e8c5b548](http://github.com/angular-ui/bootstrap/commit/e8c5b548))  
+- **carousel:** 
+  - add option to prevent pause ([5f895c13](http://github.com/angular-ui/bootstrap/commit/5f895c13))  
+- **datepicker:** 
+  - add datepicker directive ([30a00a07](http://github.com/angular-ui/bootstrap/commit/30a00a07))  
+- **pagination:** 
+  - option for different mode when maxSize ([a023d082](http://github.com/angular-ui/bootstrap/commit/a023d082))  
+  - add pager directive ([d9526475](http://github.com/angular-ui/bootstrap/commit/d9526475))  
+- **tabs:** 
+  - Change directive name, add features ([c5326595](http://github.com/angular-ui/bootstrap/commit/c5326595))  
+  - support disabled state ([2b78dd16](http://github.com/angular-ui/bootstrap/commit/2b78dd16))  
+  - add support for vertical option ([88d17a75](http://github.com/angular-ui/bootstrap/commit/88d17a75))  
+  - add support for other navigation types, like 'pills' ([53e0a39f](http://github.com/angular-ui/bootstrap/commit/53e0a39f))  
+- **timepicker:** 
+  - add timepicker directive ([9bc5207b](http://github.com/angular-ui/bootstrap/commit/9bc5207b))  
+- **tooltip:** 
+  - add mouse placement option ([ace7bc60](http://github.com/angular-ui/bootstrap/commit/ace7bc60))
+  - add *-append-to-body attribute ([d0896263](http://github.com/angular-ui/bootstrap/commit/d0896263))  
+  - add custom trigger support ([dfa53155](http://github.com/angular-ui/bootstrap/commit/dfa53155))  
+- **typeahead:** 
+  - support typeahead-on-select callback ([91ac17c9](http://github.com/angular-ui/bootstrap/commit/91ac17c9))  
+  - support wait-ms option ([7f35a3f2](http://github.com/angular-ui/bootstrap/commit/7f35a3f2))   
+
+## Bug Fixes
+
+- **accordion:** 
+  - allow accordion heading directives as attributes. ([25f6e55c](http://github.com/angular-ui/bootstrap/commit/25f6e55c))
+- **carousel:** 
+  - do not allow user to change slide if transitioning ([1d19663f](http://github.com/angular-ui/bootstrap/commit/1d19663f))  
+  - make slide 'active' binding optional ([17d6c3b5](http://github.com/angular-ui/bootstrap/commit/17d6c3b5))  
+  - fix error with deleting multiple slides at once ([3fcb70f0](http://github.com/angular-ui/bootstrap/commit/3fcb70f0))  
+- **dialog:** 
+  - remove dialogOpenClass to get in line with v2.3 ([f009b23f](http://github.com/angular-ui/bootstrap/commit/f009b23f))  
+- **pagination:** 
+  - bind *-text attributes ([e1bff6b7](http://github.com/angular-ui/bootstrap/commit/e1bff6b7))  
+- **progressbar:** 
+  - user `percent` attribute instead of `value`. ([58efec80](http://github.com/angular-ui/bootstrap/commit/58efec80))  
+- **tooltip:** 
+  - fix positioning error when appendToBody is set to true ([76fee1f9](http://github.com/angular-ui/bootstrap/commit/76fee1f9))  
+  - close tooltips appended to body on location change ([041261b5](http://github.com/angular-ui/bootstrap/commit/041261b5))  
+  - tooltips will hide on scope.$destroy ([3e5a58e5](http://github.com/angular-ui/bootstrap/commit/3e5a58e5))  
+  - support of custom $interpolate.startSymbol ([88c94ee6](http://github.com/angular-ui/bootstrap/commit/88c94ee6))  
+  - make sure tooltip scope is evicted from cache ([9246905a](http://github.com/angular-ui/bootstrap/commit/9246905a))  
+- **typeahead:** 
+  - return focus to the input after selecting a suggestion ([04a21e33](http://github.com/angular-ui/bootstrap/commit/04a21e33))   
+
+## Breaking Changes
+
+- **pagination:** 
+ The 'first-text', 'previous-text', 'next-text' and 'last-text'
+  attributes are now binded to parent scope.
+
+  To migrate your code, surround the text of these attributes with quotes.
+
+  Before:
+      
+    ```html
+    <pagination first-text="<<"></pagination>
+    ```
+
+  After:
+    
+    ```html
+    <pagination first-text="'<<'"></pagination>
+    ```
+
+- **progressbar:** 
+ The 'value' is replaced by 'percent'.
+
+  Before:
+    
+    ```html
+    <progress value="..."></progress>
+    ```
+
+  After:
+    
+    ```html
+    <progress percent="..."></progress>
+    ```
+
+- **tabs:** 
+ The 'tabs' directive has been renamed to 'tabset', and
+ the 'pane' directive has been renamed to 'tab'.
+
+    To migrate your code, follow the example below.
+
+  Before:
+
+    ```html
+    <tabs>
+      <pane heading="one">
+        First Content
+      </pane>
+      <pane ng-repeat="apple in basket" heading="{{apple.heading}}">
+        {{apple.content}}
+      </pane>
+    </tabs>
+    ```
+
+  After:
+
+    ```html
+    <tabset>
+      <tab heading="one">
+        First Content
+      </tab>
+      <tab ng-repeat="apple in basket" heading="{{apple.heading}}">
+        {{apple.content}}
+      </tab>
+    </tabset>
+    ```
+
+ 
+# 0.3.0 (2013-04-30)
+
+## Features
+
+- **progressbar:**
+  - add progressbar directive ([261f2072](https://github.com/angular-ui/bootstrap/commit/261f2072))
+- **rating:**
+  - add rating directive ([6b5e6369](https://github.com/angular-ui/bootstrap/commit/6b5e6369))
+- **typeahead:**
+  - support the editable property ([a40c3fbe](https://github.com/angular-ui/bootstrap/commit/a40c3fbe))
+  - support typeahead-loading bindable expression ([b58c9c88](https://github.com/angular-ui/bootstrap/commit/b58c9c88))
+- **tooltip:**
+  - added popup-delay option ([a79a2ba8](https://github.com/angular-ui/bootstrap/commit/a79a2ba8))
+  - added appendToBody to $tooltip ([1ee467f8](https://github.com/angular-ui/bootstrap/commit/1ee467f8))
+  - added tooltip-html-unsafe directive ([45ed2805](https://github.com/angular-ui/bootstrap/commit/45ed2805))
+  - support for custom triggers ([b1ba821b](https://github.com/angular-ui/bootstrap/commit/b1ba821b))
+
+## Bug Fixes
+
+- **alert:**
+  - don't show close button if no close callback specified ([c2645f4a](https://github.com/angular-ui/bootstrap/commit/c2645f4a))
+- **carousel:**
+  - Hide navigation indicators if only one slide ([aedc0565](https://github.com/angular-ui/bootstrap/commit/aedc0565))
+- **collapse:**
+  - remove reference to msTransition for IE10 ([55437b16](https://github.com/angular-ui/bootstrap/commit/55437b16))
+- **dialog:**
+  - set _open to false on init ([dcc9ef31](https://github.com/angular-ui/bootstrap/commit/dcc9ef31))
+  - close dialog on location change ([474ce52e](https://github.com/angular-ui/bootstrap/commit/474ce52e))
+  - IE8 fix to not set data() against text nodes ([a6c540e5](https://github.com/angular-ui/bootstrap/commit/a6c540e5))
+  - fix $apply in progres on $location change ([77e6acb9](https://github.com/angular-ui/bootstrap/commit/77e6acb9))
+- **tabs:**
+  - remove superfluous href from tabs template ([38c1badd](https://github.com/angular-ui/bootstrap/commit/38c1badd))
+- **tooltip:**
+  - fix positioning issues in tooltips and popovers ([6458f487](https://github.com/angular-ui/bootstrap/commit/6458f487))
+- **typeahead:**
+  - close matches popup on click outside typeahead ([acca7dcd](https://github.com/angular-ui/bootstrap/commit/acca7dcd))
+  - stop keydown event propagation when ESC pressed to discard matches ([22a00cd0](https://github.com/angular-ui/bootstrap/commit/22a00cd0))
+  - correctly render initial model value ([929a46fa](https://github.com/angular-ui/bootstrap/commit/929a46fa))
+  - correctly higlight matches if query contains regexp-special chars ([467afcd6](https://github.com/angular-ui/bootstrap/commit/467afcd6))
+  - fix matches pop-up positioning issues ([74beecdb](https://github.com/angular-ui/bootstrap/commit/74beecdb))
+
+# 0.2.0 (2013-03-03)
+
+## Features
+
+- **dialog:**
+  - Make $dialog 'resolve' property to work the same way of $routeProvider.when ([739f86f](https://github.com/angular-ui/bootstrap/commit/739f86f))
+- **modal:**
+  - allow global override of modal options ([acaf72b](https://github.com/angular-ui/bootstrap/commit/acaf72b))
+- **buttons:**
+  - add checkbox and radio buttons ([571ccf4](https://github.com/angular-ui/bootstrap/commit/571ccf4))
+- **carousel:**
+  - add slide indicators ([3b677ee](https://github.com/angular-ui/bootstrap/commit/3b677ee))
+- **typeahead:**
+  - add typeahead directive ([6a97da2](https://github.com/angular-ui/bootstrap/commit/6a97da2))
+- **accordion:**
+  - enable HTML in accordion headings ([3afcaa4](https://github.com/angular-ui/bootstrap/commit/3afcaa4))
+- **pagination:**
+  - add first/last link & constant congif options ([0ff0454](https://github.com/angular-ui/bootstrap/commit/0ff0454))
+
+## Bug fixes
+
+- **dialog:**
+  - update resolve section to new syntax ([1f87486](https://github.com/angular-ui/bootstrap/commit/1f87486))
+  - $compile entire modal ([7575b3c](https://github.com/angular-ui/bootstrap/commit/7575b3c))
+- **tooltip:**
+  - don't show tooltips if there is no content to show ([030901e](https://github.com/angular-ui/bootstrap/commit/030901e))
+  - fix placement issues ([a2bbf4d](https://github.com/angular-ui/bootstrap/commit/a2bbf4d))
+- **collapse:**
+  - Avoids fixed height on collapse ([ff5d119](https://github.com/angular-ui/bootstrap/commit/ff5d119))
+- **accordion:**
+  - fix minification issues ([f4da4d6](https://github.com/angular-ui/bootstrap/commit/f4da4d6))
+- **typeahead:**
+  -  update inputs value on mapping where label is not derived from the model ([a5f64de](https://github.com/angular-ui/bootstrap/commit/a5f64de))
+
+# 0.1.0 (2013-02-02)
+
+_Very first, initial release_.
+
+## Features
+
+Version `0.1.0` was released with the following directives:
+
+* accordion
+* alert
+* carousel
+* dialog
+* dropdownToggle
+* modal
+* pagination
+* popover
+* tabs
+* tooltip
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/CODE_OF_CONDUCT.md b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..65c05c5
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/CODE_OF_CONDUCT.md
@@ -0,0 +1,22 @@
+# Contributor Code of Conduct
+
+As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
+
+We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery
+* Personal attacks
+* Trolling or insulting/derogatory comments
+* Public or private harassment
+* Publishing other's private information, such as physical or electronic addresses, without explicit permission
+* Other unethical or unprofessional conduct.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.
+
+This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
+
+This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
\ No newline at end of file
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/CONTRIBUTING.md b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/CONTRIBUTING.md
new file mode 100644
index 0000000..3bbe218
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/CONTRIBUTING.md
@@ -0,0 +1,47 @@
+## Got a question or problem?
+
+Firstly, please go over our FAQ: https://github.com/angular-ui/bootstrap/wiki/FAQ
+
+Please, do not open issues for the general support questions as we want to keep GitHub issues for bug reports and feature requests. You've got much better chances of getting your question answered on [StackOverflow](http://stackoverflow.com/questions/tagged/angular-ui-bootstrap) where maintainers are looking at questions questions tagged with `angular-ui-bootstrap`.
+
+StackOverflow is a much better place to ask questions since:
+* there are hundreds of people willing to help on StackOverflow
+* questions and answers stay available for public viewing so your question / answer might help someone else
+* SO voting system assures that the best answers are prominently visible.
+
+To save your and our time we will be systematically closing all the issues that are requests for general support and redirecting people to StackOverflow.
+
+## You think you've found a bug?
+
+Oh, we are ashamed and want to fix it asap! But before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs we will systematically ask you to provide a _minimal_ reproduce scenario using http://plnkr.co/. Having a live reproduce scenario gives us wealth of important information without going back & forth to you with additional questions like:
+* version of AngularJS used
+* version of this library that you are using
+* 3rd-party libraries used, if any
+* and most importantly - a use-case that fails
+
+A minimal reproduce scenario using http://plnkr.co/ allows us to quickly confirm a bug (or point out coding problem) as well as confirm that we are fixing the right problem.
+
+We will be insisting on a minimal reproduce scenario in order to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal plunk. We understand that sometimes it might be hard to extract essentials bits of code from a larger code-base but we really need to isolate the problem before we can fix it.
+
+The best part is that you don't need to create plunks from scratch - you can use one from our [demo page](http://angular-ui.github.io/bootstrap/).
+
+Unfortunately we are not able to investigate / fix bugs without a minimal reproduce scenario using http://plnkr.co/, so if we don't hear back from you we are going to close an issue that don't have enough info to be reproduced.
+
+
+## You want to contribute some code?
+
+**NOTE: We are migrating all our components to use a prefix, in the meantime you will see both the new and the old directives separated by a comment. If you want to contribute with code, make all your changes on top of that comment.** 
+
+We are always looking for the quality contributions and will be happy to accept your Pull Requests as long as those adhere to some basic rules:
+
+* Please make sure that your contribution fits well in the project's context:
+  * we are aiming at rebuilding bootstrap directives in pure AngularJS, without any dependencies on any external JavaScript library;
+  * the only dependency should be bootstrap CSS and its markup structure;
+  * directives should be html-agnostic as much as possible which in practice means:
+        * templates should be referred to using the `templateUrl` property
+        * it should be easy to change a default template to a custom one
+        * directives shouldn't manipulate DOM structure directly (when possible)
+* Please assure that you are submitting quality code, specifically make sure that:
+  * your directive has accompanying tests and all the tests are passing; don't hesitate to contact us (angular-ui@googlegroups.com) if you need any help with unit testing
+  * your PR doesn't break the build; check the Travis-CI build status after opening a PR and push corrective commits if anything goes wrong
+  * your commits conform to the conventions established [here](https://github.com/ajoslin/conventional-changelog/blob/master/conventions/angular.md)
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/Gruntfile.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/Gruntfile.js
new file mode 100644
index 0000000..147adb4
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/Gruntfile.js
@@ -0,0 +1,492 @@
+/* jshint node: true */
+var markdown = require('node-markdown').Markdown;
+var fs = require('fs');
+
+module.exports = function(grunt) {
+
+  grunt.loadNpmTasks('grunt-contrib-watch');
+  grunt.loadNpmTasks('grunt-contrib-concat');
+  grunt.loadNpmTasks('grunt-contrib-copy');
+  grunt.loadNpmTasks('grunt-contrib-jshint');
+  grunt.loadNpmTasks('grunt-contrib-uglify');
+  grunt.loadNpmTasks('grunt-html2js');
+  grunt.loadNpmTasks('grunt-karma');
+  grunt.loadNpmTasks('grunt-conventional-changelog');
+  grunt.loadNpmTasks('grunt-ddescribe-iit');
+
+  // Project configuration.
+  grunt.util.linefeed = '\n';
+
+  grunt.initConfig({
+    ngversion: '1.4.7',
+    bsversion: '3.1.1',
+    modules: [],//to be filled in by build task
+    pkg: grunt.file.readJSON('package.json'),
+    dist: 'dist',
+    filename: 'ui-bootstrap',
+    filenamecustom: '<%= filename %>-custom',
+    meta: {
+      modules: 'angular.module("ui.bootstrap", [<%= srcModules %>]);',
+      tplmodules: 'angular.module("ui.bootstrap.tpls", [<%= tplModules %>]);',
+      all: 'angular.module("ui.bootstrap", ["ui.bootstrap.tpls", <%= srcModules %>]);',
+      cssInclude: '',
+      cssFileBanner: '/* Include this file in your html if you are using the CSP mode. */\n\n',
+      cssFileDest: '<%= dist %>/<%= filename %>-<%= pkg.version %>-csp.css',
+      banner: ['/*',
+               ' * <%= pkg.name %>',
+               ' * <%= pkg.homepage %>\n',
+               ' * Version: <%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>',
+               ' * License: <%= pkg.license %>',
+               ' */\n'].join('\n')
+    },
+    delta: {
+      docs: {
+        files: ['misc/demo/index.html'],
+        tasks: ['after-test']
+      },
+      html: {
+        files: ['template/**/*.html'],
+        tasks: ['html2js', 'karma:watch:run']
+      },
+      js: {
+        files: ['src/**/*.js'],
+        //we don't need to jshint here, it slows down everything else
+        tasks: ['karma:watch:run']
+      }
+    },
+    concat: {
+      dist: {
+        options: {
+          banner: '<%= meta.banner %><%= meta.modules %>\n',
+          footer: '<%= meta.cssInclude %>'
+        },
+        src: [], //src filled in by build task
+        dest: '<%= dist %>/<%= filename %>-<%= pkg.version %>.js'
+      },
+      dist_tpls: {
+        options: {
+          banner: '<%= meta.banner %><%= meta.all %>\n<%= meta.tplmodules %>\n',
+          footer: '<%= meta.cssInclude %>'
+        },
+        src: [], //src filled in by build task
+        dest: '<%= dist %>/<%= filename %>-tpls-<%= pkg.version %>.js'
+      }
+    },
+    copy: {
+      demohtml: {
+        options: {
+          //process html files with gruntfile config
+          processContent: grunt.template.process
+        },
+        files: [{
+          expand: true,
+          src: ['**/*.html'],
+          cwd: 'misc/demo/',
+          dest: 'dist/'
+        }]
+      },
+      demoassets: {
+        files: [{
+          expand: true,
+          //Don't re-copy html files, we process those
+          src: ['**/**/*', '!**/*.html'],
+          cwd: 'misc/demo',
+          dest: 'dist/'
+        }]
+      }
+    },
+    uglify: {
+      options: {
+        banner: '<%= meta.banner %>'
+      },
+      dist:{
+        src:['<%= concat.dist.dest %>'],
+        dest:'<%= dist %>/<%= filename %>-<%= pkg.version %>.min.js'
+      },
+      dist_tpls:{
+        src:['<%= concat.dist_tpls.dest %>'],
+        dest:'<%= dist %>/<%= filename %>-tpls-<%= pkg.version %>.min.js'
+      }
+    },
+    html2js: {
+      dist: {
+        options: {
+          module: null, // no bundle module for all the html2js templates
+          base: '.'
+        },
+        files: [{
+          expand: true,
+          src: ['template/**/*.html'],
+          ext: '.html.js'
+        }]
+      }
+    },
+    jshint: {
+      files: ['Gruntfile.js','src/**/*.js'],
+      options: {
+        jshintrc: '.jshintrc'
+      }
+    },
+    karma: {
+      options: {
+        configFile: 'karma.conf.js'
+      },
+      watch: {
+        background: true
+      },
+      continuous: {
+        singleRun: true
+      },
+      jenkins: {
+        singleRun: true,
+        autoWatch: false,
+        colors: false,
+        reporters: ['dots', 'junit'],
+        browsers: ['Chrome', 'ChromeCanary', 'Firefox', 'Opera', '/Users/jenkins/bin/safari.sh']
+      },
+      travis: {
+        singleRun: true,
+        autoWatch: false,
+        reporters: ['dots'],
+        browsers: ['Firefox']
+      },
+      coverage: {
+        preprocessors: {
+          'src/*/*.js': 'coverage'
+        },
+        reporters: ['progress', 'coverage']
+      }
+    },
+    conventionalChangelog: {
+      options: {
+        changelogOpts: {
+          preset: 'angular'
+        },
+        templateFile: 'misc/changelog.tpl.md'
+      },
+      release: {
+        src: 'CHANGELOG.md'
+      }
+    },
+    shell: {
+      //We use %version% and evluate it at run-time, because <%= pkg.version %>
+      //is only evaluated once
+      'release-prepare': [
+        'grunt before-test after-test',
+        'grunt version', //remove "-SNAPSHOT"
+        'grunt conventionalChangelog'
+      ],
+      'release-complete': [
+        'git commit CHANGELOG.md package.json -m "chore(release): v%version%"',
+        'git tag %version%'
+      ],
+      'release-start': [
+        'grunt version:minor:"SNAPSHOT"',
+        'git commit package.json -m "chore(release): Starting v%version%"'
+      ]
+    },
+    'ddescribe-iit': {
+      files: [
+        'src/**/*.spec.js'
+      ]
+    }
+  });
+
+  //register before and after test tasks so we've don't have to change cli
+  //options on the google's CI server
+  grunt.registerTask('before-test', ['enforce', 'ddescribe-iit', 'jshint', 'html2js']);
+  grunt.registerTask('after-test', ['build', 'copy']);
+
+  //Rename our watch task to 'delta', then make actual 'watch'
+  //task build things, then start test server
+  grunt.renameTask('watch', 'delta');
+  grunt.registerTask('watch', ['before-test', 'after-test', 'karma:watch', 'delta']);
+
+  // Default task.
+  grunt.registerTask('default', ['before-test', 'test', 'after-test']);
+
+  grunt.registerTask('enforce', 'Install commit message enforce script if it doesn\'t exist', function() {
+    if (!grunt.file.exists('.git/hooks/commit-msg')) {
+      grunt.file.copy('misc/validate-commit-msg.js', '.git/hooks/commit-msg');
+      require('fs').chmodSync('.git/hooks/commit-msg', '0755');
+    }
+  });
+
+  //Common ui.bootstrap module containing all modules for src and templates
+  //findModule: Adds a given module to config
+  var foundModules = {};
+  function findModule(name) {
+    if (foundModules[name]) { return; }
+    foundModules[name] = true;
+
+    function breakup(text, separator) {
+      return text.replace(/[A-Z]/g, function (match) {
+        return separator + match;
+      });
+    }
+    function ucwords(text) {
+      return text.replace(/^([a-z])|\s+([a-z])/g, function ($1) {
+        return $1.toUpperCase();
+      });
+    }
+    function enquote(str) {
+      return '"' + str + '"';
+    }
+
+    var module = {
+      name: name,
+      moduleName: enquote('ui.bootstrap.' + name),
+      displayName: ucwords(breakup(name, ' ')),
+      srcFiles: grunt.file.expand('src/'+name+'/*.js'),
+      cssFiles: grunt.file.expand('src/'+name+'/*.css'),
+      tplFiles: grunt.file.expand('template/'+name+'/*.html'),
+      tpljsFiles: grunt.file.expand('template/'+name+'/*.html.js'),
+      tplModules: grunt.file.expand('template/'+name+'/*.html').map(enquote),
+      dependencies: dependenciesForModule(name),
+      docs: {
+        md: grunt.file.expand('src/'+name+'/docs/*.md')
+          .map(grunt.file.read).map(markdown).join('\n'),
+        js: grunt.file.expand('src/'+name+'/docs/*.js')
+          .map(grunt.file.read).join('\n'),
+        html: grunt.file.expand('src/'+name+'/docs/*.html')
+          .map(grunt.file.read).join('\n')
+      }
+    };
+
+    var styles = {
+      css: [],
+      js: []
+    };
+    module.cssFiles.forEach(processCSS.bind(null, styles, true));
+    if (styles.css.length) {
+      module.css = styles.css.join('\n');
+      module.cssJs = styles.js.join('\n');
+    }
+
+    module.dependencies.forEach(findModule);
+    grunt.config('modules', grunt.config('modules').concat(module));
+  }
+
+  function dependenciesForModule(name) {
+    var deps = [];
+    grunt.file.expand('src/' + name + '/*.js')
+    .map(grunt.file.read)
+    .forEach(function(contents) {
+      //Strategy: find where module is declared,
+      //and from there get everything inside the [] and split them by comma
+      var moduleDeclIndex = contents.indexOf('angular.module(');
+      var depArrayStart = contents.indexOf('[', moduleDeclIndex);
+      var depArrayEnd = contents.indexOf(']', depArrayStart);
+      var dependencies = contents.substring(depArrayStart + 1, depArrayEnd);
+      dependencies.split(',').forEach(function(dep) {
+        if (dep.indexOf('ui.bootstrap.') > -1) {
+          var depName = dep.trim().replace('ui.bootstrap.','').replace(/['"]/g,'');
+          if (deps.indexOf(depName) < 0) {
+            deps.push(depName);
+            //Get dependencies for this new dependency
+            deps = deps.concat(dependenciesForModule(depName));
+          }
+        }
+      });
+    });
+    return deps;
+  }
+
+  grunt.registerTask('dist', 'Override dist directory', function() {
+    var dir = this.args[0];
+    if (dir) { grunt.config('dist', dir); }
+  });
+
+  grunt.registerTask('build', 'Create bootstrap build files', function() {
+    var _ = grunt.util._;
+
+    //If arguments define what modules to build, build those. Else, everything
+    if (this.args.length) {
+      this.args.forEach(findModule);
+      grunt.config('filename', grunt.config('filenamecustom'));
+    } else {
+      grunt.file.expand({
+        filter: 'isDirectory', cwd: '.'
+      }, 'src/*').forEach(function(dir) {
+        findModule(dir.split('/')[1]);
+      });
+    }
+
+    var modules = grunt.config('modules');
+    grunt.config('srcModules', _.pluck(modules, 'moduleName'));
+    grunt.config('tplModules', _.pluck(modules, 'tplModules').filter(function(tpls) { return tpls.length > 0;} ));
+    grunt.config('demoModules', modules
+      .filter(function(module) {
+        return module.docs.md && module.docs.js && module.docs.html;
+      })
+      .sort(function(a, b) {
+        if (a.name < b.name) { return -1; }
+        if (a.name > b.name) { return 1; }
+        return 0;
+      })
+    );
+
+    var cssStrings = _.flatten(_.compact(_.pluck(modules, 'css')));
+    var cssJsStrings = _.flatten(_.compact(_.pluck(modules, 'cssJs')));
+    if (cssStrings.length) {
+      grunt.config('meta.cssInclude', cssJsStrings.join('\n'));
+
+      grunt.file.write(grunt.config('meta.cssFileDest'), grunt.config('meta.cssFileBanner') +
+                       cssStrings.join('\n'));
+
+      grunt.log.writeln('File ' + grunt.config('meta.cssFileDest') + ' created');
+    }
+
+    var moduleFileMapping = _.clone(modules, true);
+    moduleFileMapping.forEach(function (module) {
+      delete module.docs;
+    });
+
+    grunt.config('moduleFileMapping', moduleFileMapping);
+
+    var srcFiles = _.pluck(modules, 'srcFiles');
+    var tpljsFiles = _.pluck(modules, 'tpljsFiles');
+    //Set the concat task to concatenate the given src modules
+    grunt.config('concat.dist.src', grunt.config('concat.dist.src')
+                 .concat(srcFiles));
+    //Set the concat-with-templates task to concat the given src & tpl modules
+    grunt.config('concat.dist_tpls.src', grunt.config('concat.dist_tpls.src')
+                 .concat(srcFiles).concat(tpljsFiles));
+
+    grunt.task.run(['concat', 'uglify', 'makeModuleMappingFile', 'makeRawFilesJs', 'makeVersionsMappingFile']);
+  });
+
+  grunt.registerTask('test', 'Run tests on singleRun karma server', function () {
+    //this task can be executed in 3 different environments: local, Travis-CI and Jenkins-CI
+    //we need to take settings for each one into account
+    if (process.env.TRAVIS) {
+      grunt.task.run('karma:travis');
+    } else {
+      var isToRunJenkinsTask = !!this.args.length;
+      if(grunt.option('coverage')) {
+        var karmaOptions = grunt.config.get('karma.options'),
+          coverageOpts = grunt.config.get('karma.coverage');
+        grunt.util._.extend(karmaOptions, coverageOpts);
+        grunt.config.set('karma.options', karmaOptions);
+      }
+      grunt.task.run(this.args.length ? 'karma:jenkins' : 'karma:continuous');
+    }
+  });
+
+  grunt.registerTask('makeModuleMappingFile', function () {
+    var _ = grunt.util._;
+    var moduleMappingJs = 'dist/assets/module-mapping.json';
+    var moduleMappings = grunt.config('moduleFileMapping');
+    var moduleMappingsMap = _.object(_.pluck(moduleMappings, 'name'), moduleMappings);
+    var jsContent = JSON.stringify(moduleMappingsMap);
+    grunt.file.write(moduleMappingJs, jsContent);
+    grunt.log.writeln('File ' + moduleMappingJs.cyan + ' created.');
+  });
+
+  grunt.registerTask('makeRawFilesJs', function () {
+    var _ = grunt.util._;
+    var jsFilename = 'dist/assets/raw-files.json';
+    var genRawFilesJs = require('./misc/raw-files-generator');
+
+    genRawFilesJs(grunt, jsFilename, _.flatten(grunt.config('concat.dist_tpls.src')),
+                  grunt.config('meta.banner'), grunt.config('meta.cssFileBanner'));
+  });
+
+  grunt.registerTask('makeVersionsMappingFile', function () {
+    var done = this.async();
+
+    var exec = require('child_process').exec;
+
+    var versionsMappingFile = 'dist/versions-mapping.json';
+
+    exec('git tag --sort -version:refname', function(error, stdout, stderr) {
+      // Let's remove the oldest 14 versions.
+      var versions = stdout.split('\n').slice(0, -14);
+      var jsContent = versions.map(function(version) {
+        return {
+          version: version,
+          url: '/bootstrap/versioned-docs/' + version
+        };
+      });
+      jsContent[0] = {
+        version: 'Current',
+        url: '/bootstrap'
+      };
+      grunt.file.write(versionsMappingFile, JSON.stringify(jsContent));
+      grunt.log.writeln('File ' + versionsMappingFile.cyan + ' created.');
+      done();
+    });
+
+  });
+
+  /**
+   * Logic from AngularJS
+   * https://github.com/angular/angular.js/blob/36831eccd1da37c089f2141a2c073a6db69f3e1d/lib/grunt/utils.js#L121-L145
+   */
+  function processCSS(state, minify, file) {
+    /* jshint quotmark: false */
+    var css = fs.readFileSync(file).toString(),
+      js;
+    state.css.push(css);
+
+    if(minify){
+      css = css
+        .replace(/\r?\n/g, '')
+        .replace(/\/\*.*?\*\//g, '')
+        .replace(/:\s+/g, ':')
+        .replace(/\s*\{\s*/g, '{')
+        .replace(/\s*\}\s*/g, '}')
+        .replace(/\s*\,\s*/g, ',')
+        .replace(/\s*\;\s*/g, ';');
+    }
+    //escape for js
+    css = css
+      .replace(/\\/g, '\\\\')
+      .replace(/'/g, "\\'")
+      .replace(/\r?\n/g, '\\n');
+    js = "!angular.$$csp() && angular.element(document).find('head').prepend('<style type=\"text/css\">" + css + "</style>');";
+    state.js.push(js);
+
+    return state;
+  }
+
+  function setVersion(type, suffix) {
+    var file = 'package.json';
+    var VERSION_REGEX = /([\'|\"]version[\'|\"][ ]*:[ ]*[\'|\"])([\d|.]*)(-\w+)*([\'|\"])/;
+    var contents = grunt.file.read(file);
+    var version;
+    contents = contents.replace(VERSION_REGEX, function(match, left, center) {
+      version = center;
+      if (type) {
+        version = require('semver').inc(version, type);
+      }
+      //semver.inc strips our suffix if it existed
+      if (suffix) {
+        version += '-' + suffix;
+      }
+      return left + version + '"';
+    });
+    grunt.log.ok('Version set to ' + version.cyan);
+    grunt.file.write(file, contents);
+    return version;
+  }
+
+  grunt.registerTask('version', 'Set version. If no arguments, it just takes off suffix', function() {
+    setVersion(this.args[0], this.args[1]);
+  });
+
+  grunt.registerMultiTask('shell', 'run shell commands', function() {
+    var self = this;
+    var sh = require('shelljs');
+    self.data.forEach(function(cmd) {
+      cmd = cmd.replace('%version%', grunt.file.readJSON('package.json').version);
+      grunt.log.ok(cmd);
+      var result = sh.exec(cmd,{silent:true});
+      if (result.code !== 0) {
+        grunt.fatal(result.output);
+      }
+    });
+  });
+
+  return grunt;
+};
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/LICENSE b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/LICENSE
new file mode 100644
index 0000000..18db5a5
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/LICENSE
@@ -0,0 +1,22 @@
+The MIT License

+

+Copyright (c) 2012-2015 the AngularUI Team, https://github.com/organizations/angular-ui/teams/291112

+

+Permission is hereby granted, free of charge, to any person obtaining a copy

+of this software and associated documentation files (the "Software"), to deal

+in the Software without restriction, including without limitation the rights

+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell

+copies of the Software, and to permit persons to whom the Software is

+furnished to do so, subject to the following conditions:

+

+The above copyright notice and this permission notice shall be included in

+all copies or substantial portions of the Software.

+

+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN

+THE SOFTWARE.

+

diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/README.md b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/README.md
new file mode 100644
index 0000000..10d868d
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/README.md
@@ -0,0 +1,136 @@
+### UI Bootstrap - [AngularJS](http://angularjs.org/) directives specific to [Bootstrap](http://getbootstrap.com)
+
+[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/angular-ui/bootstrap?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+[![Build Status](https://secure.travis-ci.org/angular-ui/bootstrap.svg)](http://travis-ci.org/angular-ui/bootstrap)
+[![devDependency Status](https://david-dm.org/angular-ui/bootstrap/dev-status.svg?branch=master)](https://david-dm.org/angular-ui/bootstrap#info=devDependencies)
+
+### Quick links
+- [Demo](#demo)
+- [Angular 2](#angular-2)
+- [Installation](#installation)
+    - [NPM](#install-with-npm)
+    - [Bower](#install-with-bower)
+    - [NuGet](#install-with-nuget)
+    - [Custom](#custom-build)
+    - [Manual](#manual-download)
+- [Support](#support)
+    - [FAQ](#faq)
+    - [Code of Conduct](#code-of-conduct)
+    - [PREFIX MIGRATION GUIDE](#prefix-migration-guide)
+    - [Supported browsers](#supported-browsers)
+    - [Need help?](#need-help)
+    - [Found a bug?](#found-a-bug)
+- [Contributing to the project](#contributing-to-the-project)
+- [Development, meeting minutes, roadmap and more.](#development-meeting-minutes-roadmap-and-more)
+
+
+# Demo
+
+Do you want to see directives in action? Visit http://angular-ui.github.io/bootstrap/!
+
+# Angular 2
+
+Are you interested in Angular 2? We are on our way! Check out [ng-bootstrap](https://github.com/ui-bootstrap/core). 
+
+# Installation
+
+Installation is easy as UI Bootstrap has minimal dependencies - only the AngularJS and Twitter Bootstrap's CSS are required.
+Note: Since version 0.13.0, UI Bootstrap depends on [ngAnimate](https://docs.angularjs.org/api/ngAnimate) for transitions and animations, such as the accordion, carousel, etc. Include `ngAnimate` in the module dependencies for your app in order to enable animation.
+
+Additionally, it is strongly recommended that for UI Bootstrap 0.13.3 and higher you use Angular 1.3.18 or higher due to animation fixes.
+
+#### Install with NPM
+
+```sh
+$ npm install angular-ui-bootstrap
+```
+
+This will install AngularJS and Bootstrap NPM packages.
+
+#### Install with Bower
+```sh
+$ bower install angular-bootstrap
+```
+
+Note: do not install 'angular-ui-bootstrap'.  A separate repository - [bootstrap-bower](https://github.com/angular-ui/bootstrap-bower) - hosts the compiled javascript file and bower.json.
+
+#### Install with NuGet
+To install AngularJS UI Bootstrap, run the following command in the Package Manager Console
+
+```sh
+PM> Install-Package Angular.UI.Bootstrap
+```
+
+#### Custom build
+
+Head over to http://angular-ui.github.io/bootstrap/ and hit the *Custom build* button to create your own custom UI Bootstrap build, just the way you like it.
+
+#### Manual download
+
+After downloading dependencies (or better yet, referencing them from your favorite CDN) you need to download build version of this project. All the files and their purposes are described here:
+https://github.com/angular-ui/bootstrap/tree/gh-pages#build-files
+Don't worry, if you are not sure which file to take, opt for `ui-bootstrap-tpls-[version].min.js`.
+
+### Adding dependency to your project
+
+When you are done downloading all the dependencies and project files the only remaining part is to add dependencies on the `ui.bootstrap` AngularJS module:
+
+```js
+angular.module('myModule', ['ui.bootstrap']);
+```
+
+If you're a Browserify or Webpack user, you can do:
+
+```js
+var uibs = require('angular-ui-bootstrap');
+
+angular.module('myModule', [uibs]);
+```
+
+# Support
+
+## FAQ
+
+https://github.com/angular-ui/bootstrap/wiki/FAQ
+
+# Code of Conduct
+
+Take a moment to read or [Code of Conduct](CODE_OF_CONDUCT.md)
+
+## PREFIX MIGRATION GUIDE
+
+If you're updating your application to use prefixes, please check the [migration guide](https://github.com/angular-ui/bootstrap/wiki/Migration-guide-for-prefixes).
+
+## Supported browsers
+
+Directives from this repository are automatically tested with the following browsers:
+* Chrome (stable and canary channel)
+* Firefox
+* IE 9 and 10
+* Opera
+* Safari
+
+Modern mobile browsers should work without problems.
+
+## Need help?
+Need help using UI Bootstrap?
+
+* Live help in the IRC (`#angularjs` channel at the `freenode` network). Use this [webchat](https://webchat.freenode.net/) or your own IRC client.
+* Ask a question in [StackOverflow](http://stackoverflow.com/) under the [angular-ui-bootstrap](http://stackoverflow.com/questions/tagged/angular-ui-bootstrap) tag.
+
+**Please do not create new issues in this repository to ask questions about using UI Bootstrap**
+
+## Found a bug?
+Please take a look at [CONTRIBUTING.md](CONTRIBUTING.md#you-think-youve-found-a-bug) and submit your issue [here](https://github.com/angular-ui/bootstrap/issues/new).
+
+
+----
+
+
+# Contributing to the project
+
+We are always looking for the quality contributions! Please check the [CONTRIBUTING.md](CONTRIBUTING.md) for the contribution guidelines.
+
+# Development, meeting minutes, roadmap and more.
+
+Head over to the [Wiki](https://github.com/angular-ui/bootstrap/wiki) for notes on development for UI Bootstrap, meeting minutes from the UI Bootstrap team, roadmap plans, project philosophy and more.
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/karma.conf.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/karma.conf.js
new file mode 100644
index 0000000..c3da490
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/karma.conf.js
@@ -0,0 +1,73 @@
+// Karma configuration
+// Generated on Sat Mar 28 2015 11:17:34 GMT-0700 (PDT)
+
+module.exports = function(config) {
+  config.set({
+
+    // base path that will be used to resolve all patterns (eg. files, exclude)
+    basePath: '',
+
+
+    // frameworks to use
+    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+    frameworks: ['jasmine'],
+
+
+    // list of files / patterns to load in the browser
+    files: [
+      'misc/test-lib/jquery-1.8.2.min.js',
+      'node_modules/angular/angular.js',
+      'node_modules/angular-mocks/angular-mocks.js',
+      'node_modules/angular-sanitize/angular-sanitize.js',
+      'misc/test-lib/helpers.js',
+      'src/**/*.js',
+      'template/**/*.js'
+    ],
+
+
+    // list of files to exclude
+    exclude: [
+      'src/**/docs/*'
+    ],
+
+
+    // preprocess matching files before serving them to the browser
+    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
+    preprocessors: {
+    },
+
+
+    // test results reporter to use
+    // possible values: 'dots', 'progress'
+    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+    reporters: ['progress'],
+
+    reportSlowerThan: 100,
+
+    // web server port
+    port: 9876,
+
+
+    // enable / disable colors in the output (reporters and logs)
+    colors: true,
+
+
+    // level of logging
+    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+    logLevel: config.LOG_INFO,
+
+
+    // enable / disable watching file and executing tests whenever any file changes
+    autoWatch: true,
+
+
+    // start these browsers
+    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+    browsers: ['Chrome'],
+
+
+    // Continuous Integration mode
+    // if true, Karma captures browsers, runs the tests and exits
+    singleRun: false
+  });
+};
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/changelog.tpl.md b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/changelog.tpl.md
new file mode 100644
index 0000000..712c490
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/changelog.tpl.md
@@ -0,0 +1,16 @@
+# <%= version%> (<%= today%>)
+<% if (_(changelog.feat).size() > 0) { %>
+## Features
+<% _(changelog.feat).keys().sort().forEach(function(scope) { %>
+- **<%= scope%>:** <% changelog.feat[scope].forEach(function(change) { %>
+  - <%= change.msg%> (<%= helpers.commitLink(change.sha1) %>)  <% }); %><% }); %> <% } %>
+<% if (_(changelog.fix).size() > 0) { %>
+## Bug Fixes
+<% _(changelog.fix).keys().sort().forEach(function(scope) { %>
+- **<%= scope%>:** <% changelog.fix[scope].forEach(function(change) { %>
+  - <%= change.msg%> (<%= helpers.commitLink(change.sha1) %>)  <% }); %><% }); %> <% } %>
+<% if (_(changelog.breaking).size() > 0) { %>
+## Breaking Changes
+<% _(changelog.breaking).keys().sort().forEach(function(scope) { %>
+- **<%= scope%>:** <% changelog.breaking[scope].forEach(function(change) { %>
+<%= change.msg%><% }); %><% }); %> <% } %>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/app.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/app.js
new file mode 100644
index 0000000..5c8580c
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/app.js
@@ -0,0 +1,315 @@
+/* global FastClick, smoothScroll */
+angular.module('ui.bootstrap.demo', ['ui.bootstrap', 'plunker', 'ngTouch', 'ngAnimate', 'ngSanitize'], function($httpProvider){
+  FastClick.attach(document.body);
+  delete $httpProvider.defaults.headers.common['X-Requested-With'];
+}).run(['$location', function($location){
+  //Allows us to navigate to the correct element on initialization
+  if ($location.path() !== '' && $location.path() !== '/') {
+    smoothScroll(document.getElementById($location.path().substring(1)), 500, function(el) {
+      location.replace('#' + el.id);
+    });
+  }
+}]).factory('buildFilesService', function ($http, $q) {
+
+  var moduleMap;
+  var rawFiles;
+
+  return {
+    getModuleMap: getModuleMap,
+    getRawFiles: getRawFiles,
+    get: function () {
+      return $q.all({
+        moduleMap: getModuleMap(),
+        rawFiles: getRawFiles()
+      });
+    }
+  };
+
+  function getModuleMap() {
+    return moduleMap ? $q.when(moduleMap) : $http.get('assets/module-mapping.json')
+      .then(function (result) {
+        moduleMap = result.data;
+        return moduleMap;
+      });
+  }
+
+  function getRawFiles() {
+    return rawFiles ? $q.when(rawFiles) : $http.get('assets/raw-files.json')
+      .then(function (result) {
+        rawFiles = result.data;
+        return rawFiles;
+      });
+  }
+
+})
+.controller('MainCtrl', MainCtrl)
+.controller('SelectModulesCtrl', SelectModulesCtrl)
+.controller('DownloadCtrl', DownloadCtrl);
+
+function MainCtrl($scope, $http, $document, $uibModal, orderByFilter) {
+  // Grab old version docs
+  $http.get('/versions-mapping.json')
+    .then(function(result) {
+      $scope.oldDocs = result.data;
+    });
+
+  $scope.showBuildModal = function() {
+    var modalInstance = $uibModal.open({
+      templateUrl: 'buildModal.html',
+      controller: 'SelectModulesCtrl',
+      resolve: {
+        modules: function(buildFilesService) {
+          return buildFilesService.getModuleMap()
+            .then(function (moduleMap) {
+              return Object.keys(moduleMap);
+            });
+        }
+      }
+    });
+  };
+
+  $scope.showDownloadModal = function() {
+    var modalInstance = $uibModal.open({
+      templateUrl: 'downloadModal.html',
+      controller: 'DownloadCtrl'
+    });
+  };
+}
+
+function SelectModulesCtrl($scope, $uibModalInstance, modules, buildFilesService) {
+  $scope.selectedModules = [];
+  $scope.modules = modules;
+
+  $scope.selectedChanged = function(module, selected) {
+    if (selected) {
+      $scope.selectedModules.push(module);
+    } else {
+      $scope.selectedModules.splice($scope.selectedModules.indexOf(module), 1);
+    }
+  };
+
+  $scope.downloadBuild = function () {
+    $uibModalInstance.close($scope.selectedModules);
+  };
+
+  $scope.cancel = function () {
+    $uibModalInstance.dismiss();
+  };
+
+  $scope.isOldBrowser = function () {
+    return isOldBrowser;
+  };
+
+  $scope.build = function (selectedModules, version) {
+    /* global JSZip, saveAs */
+    var moduleMap, rawFiles;
+
+    buildFilesService.get().then(function (buildFiles) {
+      moduleMap = buildFiles.moduleMap;
+      rawFiles = buildFiles.rawFiles;
+
+      generateBuild();
+    });
+
+    function generateBuild() {
+      var srcModuleNames = selectedModules
+      .map(function (module) {
+        return moduleMap[module];
+      })
+      .reduce(function (toBuild, module) {
+        addIfNotExists(toBuild, module.name);
+
+        module.dependencies.forEach(function (depName) {
+          addIfNotExists(toBuild, depName);
+        });
+        return toBuild;
+      }, []);
+
+      var srcModules = srcModuleNames
+      .map(function (moduleName) {
+        return moduleMap[moduleName];
+      });
+
+      var srcModuleFullNames = srcModules
+      .map(function (module) {
+        return module.moduleName;
+      });
+
+      var srcJsContent = srcModules
+      .reduce(function (buildFiles, module) {
+        return buildFiles.concat(module.srcFiles);
+      }, [])
+      .map(getFileContent)
+      .join('\n')
+      ;
+
+      var jsFile = createNoTplFile(srcModuleFullNames, srcJsContent);
+
+      var tplModuleNames = srcModules
+      .reduce(function (tplModuleNames, module) {
+        return tplModuleNames.concat(module.tplModules);
+      }, []);
+
+      var tplJsContent = srcModules
+      .reduce(function (buildFiles, module) {
+        return buildFiles.concat(module.tpljsFiles);
+      }, [])
+      .map(getFileContent)
+      .join('\n')
+      ;
+
+      var jsTplFile = createWithTplFile(srcModuleFullNames, srcJsContent, tplModuleNames, tplJsContent);
+
+      var cssContent = srcModules
+      .map(function (module) {
+        return module.css;
+      })
+      .filter(function (css) {
+        return css;
+      })
+      .join('\n')
+      ;
+
+      var cssJsContent = srcModules
+      .map(function (module) {
+        return module.cssJs;
+      })
+      .filter(function (cssJs) {
+        return cssJs;
+      })
+      .join('\n')
+      ;
+
+      var footer = cssJsContent;
+
+      var zip = new JSZip();
+      zip.file('ui-bootstrap-custom-' + version + '.js', rawFiles.banner + jsFile + footer);
+      zip.file('ui-bootstrap-custom-' + version + '.min.js', rawFiles.banner + uglify(jsFile + footer));
+      zip.file('ui-bootstrap-custom-tpls-' + version + '.js', rawFiles.banner + jsTplFile + footer);
+      zip.file('ui-bootstrap-custom-tpls-' + version + '.min.js', rawFiles.banner + uglify(jsTplFile + footer));
+      zip.file('ui-bootstrap-custom-tpls-' + version + '.min.js', rawFiles.banner + uglify(jsTplFile + footer));
+
+      if (cssContent) {
+        zip.file('ui-bootstrap-custom-' + version + '-csp.css', rawFiles.cssBanner + cssContent);
+      }
+
+      saveAs(zip.generate({type: 'blob'}), 'ui-bootstrap-custom-build.zip');
+    }
+
+    function createNoTplFile(srcModuleNames, srcJsContent) {
+      return 'angular.module("ui.bootstrap", [' + srcModuleNames.join(',') + ']);\n' +
+        srcJsContent;
+    }
+
+    function createWithTplFile(srcModuleNames, srcJsContent, tplModuleNames, tplJsContent) {
+      var depModuleNames = srcModuleNames.slice();
+      depModuleNames.unshift('"ui.bootstrap.tpls"');
+
+      return 'angular.module("ui.bootstrap", [' + depModuleNames.join(',') + ']);\n' +
+        'angular.module("ui.bootstrap.tpls", [' + tplModuleNames.join(',') + ']);\n' +
+        srcJsContent + '\n' + tplJsContent;
+
+    }
+
+    function addIfNotExists(array, element) {
+      if (array.indexOf(element) == -1) {
+        array.push(element);
+      }
+    }
+
+    function getFileContent(fileName) {
+      return rawFiles.files[fileName];
+    }
+
+    function uglify(js) {
+      /* global UglifyJS */
+
+      var ast = UglifyJS.parse(js);
+      ast.figure_out_scope();
+
+      var compressor = UglifyJS.Compressor();
+      var compressedAst = ast.transform(compressor);
+
+      compressedAst.figure_out_scope();
+      compressedAst.compute_char_frequency();
+      compressedAst.mangle_names();
+
+      var stream = UglifyJS.OutputStream();
+      compressedAst.print(stream);
+
+      return stream.toString();
+    }
+  };
+}
+
+function DownloadCtrl($scope, $uibModalInstance) {
+  $scope.options = {
+    minified: true,
+    tpls: true
+  };
+
+  $scope.download = function (version) {
+    var options = $scope.options;
+
+    var downloadUrl = ['ui-bootstrap-'];
+    if (options.tpls) {
+      downloadUrl.push('tpls-');
+    }
+    downloadUrl.push(version);
+    if (options.minified) {
+      downloadUrl.push('.min');
+    }
+    downloadUrl.push('.js');
+
+    return downloadUrl.join('');
+  };
+
+  $scope.cancel = function () {
+    $uibModalInstance.dismiss();
+  };
+}
+
+/*
+ * The following compatibility check is from:
+ *
+ * Bootstrap Customizer (http://getbootstrap.com/customize/)
+ * Copyright 2011-2014 Twitter, Inc.
+ *
+ * Licensed under the Creative Commons Attribution 3.0 Unported License. For
+ * details, see http://creativecommons.org/licenses/by/3.0/.
+ */
+var isOldBrowser;
+(function () {
+
+    var supportsFile = (window.File && window.FileReader && window.FileList && window.Blob);
+    function failback() {
+        isOldBrowser = true;
+    }
+    /**
+     * Based on:
+     *   Blob Feature Check v1.1.0
+     *   https://github.com/ssorallen/blob-feature-check/
+     *   License: Public domain (http://unlicense.org)
+     */
+    var url = window.URL;
+    var svg = new Blob(
+        ['<svg xmlns=\'http://www.w3.org/2000/svg\'></svg>'],
+        { type: 'image/svg+xml;charset=utf-8' }
+    );
+    var objectUrl = url.createObjectURL(svg);
+
+    if (/^blob:/.exec(objectUrl) === null || !supportsFile) {
+      // `URL.createObjectURL` created a URL that started with something other
+      // than "blob:", which means it has been polyfilled and is not supported by
+      // this browser.
+      failback();
+    } else {
+      angular.element('<img/>')
+          .on('load', function () {
+              isOldBrowser = false;
+          })
+          .on('error', failback)
+          .attr('src', objectUrl);
+    }
+
+  })();
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/demo.css b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/demo.css
new file mode 100644
index 0000000..d1cadcd
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/demo.css
@@ -0,0 +1,323 @@
+body {
+    opacity: 1;
+    -webkit-transition: opacity 1s ease;
+    -moz-transition: opacity 1s ease;
+    transition: opacity 1s;
+}
+
+.ng-cloak {
+    opacity: 0;
+}
+
+.ng-invalid {
+    border: 1px solid red !important;
+}
+
+section {
+    padding-top: 30px;
+}
+
+.page-header h1 > small > a {
+    color: #999;
+}
+.page-header h1 > small > a:hover {
+    text-decoration: none;
+}
+
+.footer {
+    text-align: center;
+    padding: 30px 0;
+    margin-top: 70px;
+    border-top: 1px solid #e5e5e5;
+    background-color: #f5f5f5;
+}
+
+.bs-social {
+    margin-top: 20px;
+    margin-bottom: 20px;
+    text-align: center;
+}
+
+@media (min-width: 768px) {
+
+    .bs-social {
+        text-align: left;
+    }
+
+}
+
+.nav, .pagination, .carousel, .panel-title a {
+    cursor: pointer;
+}
+
+.bs-social-buttons {
+    display: inline-block;
+    margin-bottom: 0;
+    padding-left: 0;
+    list-style: none;
+}
+.bs-social-buttons li {
+    display: inline-block;
+    padding: 5px 8px;
+    line-height: 1;
+}
+
+@media (max-width: 767px) {
+
+    .visible-xs.collapse.in {
+        display: block!important;
+    }
+    .visible-xs.collapse {
+        display: none!important;
+    }
+
+}
+
+.navbar .collapse {
+    border-top: 1px solid #e7e7e7;
+    margin-left: -15px;
+    margin-right: -15px;
+    padding-right: 15px;
+    padding-left: 15px;
+}
+
+.show-grid {
+  margin-bottom: 15px;
+}
+
+/*
+ * Container
+ *
+ * Tweak to width of container.
+ */
+
+/*@media (min-width: 1200px) {
+    .container{
+        max-width: 970px;
+    }
+}*/
+
+/*
+ * Tabs
+ *
+ * Tweaks to the Tabs.
+ */
+
+.code .nav-tabs {
+border-bottom: 1px solid #ccc;
+}
+
+.code pre, .code code {
+    border-top: none;
+    border-top-left-radius: 0;
+    border-top-right-radius: 0;
+}
+
+.code .nav-tabs>li.active>a, .code .nav-tabs>li.active>a:hover, .code .nav-tabs>li.active>a:focus {
+background-color: #f8f8f8;
+border: 1px solid #ccc;
+border-bottom-color: transparent;
+}
+
+/*
+ * Button Inverse
+ *
+ * Buttons in the masthead.
+ */
+
+.btn-outline-inverse {
+color: #fff;
+background-color: transparent;
+border-color: #cdbfe3;
+margin: 10px;
+}
+
+@media (min-width: 768px) {
+
+    .btn-outline-inverse {
+        width: auto;
+        margin: 20px 5px 20px 0;
+        padding: 18px 24px;
+        font-size: 21px;
+    }
+
+}
+
+.btn-outline-inverse:hover, .btn-outline-inverse:focus, .btn-outline-inverse:active {
+color: #563d7c;
+text-shadow: none;
+background-color: #fff;
+border-color: #fff;
+}
+
+
+/* Page headers */
+.bs-header {
+  padding: 30px 15px 40px; /* side padding builds on .container 15px, so 30px */
+  font-size: 16px;
+  text-align: center;
+  text-shadow: 0 1px 0 rgba(0,0,0,.15);
+  color: #cdbfe3;
+  background-color: #563d7c;
+  background-image: url(header.png);
+}
+.bs-header a {
+  color: #fff;
+  font-weight: normal;
+}
+.bs-header h1 {
+  color: #fff;
+}
+.bs-header p {
+  font-weight: 200;
+  line-height: 1.4;
+}
+.bs-header .container {
+  position: relative;
+}
+
+@media (min-width: 768px) {
+  .bs-header {
+    font-size: 30px;
+    text-align: left;
+  }
+  .bs-header h1 {
+    font-size: 100px;
+    line-height: 1;
+  }
+}
+
+@media (min-width: 992px) {
+  .bs-header p {
+    margin-right: 25%;
+  }
+}
+
+.navbar-inner {
+    -webkit-box-shadow: 0 3px 3px rgba(0,0,0,0.175);
+    box-shadow: 0 3px 3px rgba(0,0,0,0.175);
+}
+
+/*
+ * Side navigation
+ *
+ * Scrollspy and affixed enhanced navigation to highlight sections and secondary
+ * sections of docs content.
+ */
+
+/* By default it's not affixed in mobile views, so undo that */
+.bs-sidebar.affix {
+  position: static;
+}
+
+/* First level of nav */
+.bs-sidenav {
+  margin-top: 30px;
+  margin-bottom: 30px;
+  padding-top:    10px;
+  padding-bottom: 10px;
+  text-shadow: 0 1px 0 #fff;
+  background-color: #f7f5fa;
+  border-radius: 5px;
+}
+
+/* All levels of nav */
+.bs-sidebar .nav > li > a {
+  display: block;
+  color: #716b7a;
+  padding: 5px 20px;
+}
+.bs-sidebar .nav > li > a:hover,
+.bs-sidebar .nav > li > a:focus {
+  text-decoration: none;
+  background-color: #e5e3e9;
+  border-right: 1px solid #dbd8e0;
+}
+.bs-sidebar .nav > .active > a,
+.bs-sidebar .nav > .active:hover > a,
+.bs-sidebar .nav > .active:focus > a {
+  font-weight: bold;
+  color: #563d7c;
+  background-color: transparent;
+  border-right: 1px solid #563d7c;
+}
+
+/* Nav: second level (shown on .active) */
+.bs-sidebar .nav .nav {
+  display: none; /* Hide by default, but at >768px, show it */
+  margin-bottom: 8px;
+}
+.bs-sidebar .nav .nav > li > a {
+  padding-top:    3px;
+  padding-bottom: 3px;
+  padding-left: 30px;
+  font-size: 90%;
+}
+
+/* Show and affix the side nav when space allows it */
+@media (min-width: 992px) {
+  .bs-sidebar .nav > .active > ul {
+    display: block;
+  }
+  /* Widen the fixed sidebar */
+  .bs-sidebar.affix,
+  .bs-sidebar.affix-bottom {
+    width: 213px;
+  }
+  .bs-sidebar.affix {
+    position: fixed; /* Undo the static from mobile first approach */
+    top: 80px;
+  }
+  .bs-sidebar.affix-bottom {
+    position: absolute; /* Undo the static from mobile first approach */
+  }
+  .bs-sidebar.affix-bottom .bs-sidenav,
+  .bs-sidebar.affix .bs-sidenav {
+    margin-top: 0;
+    margin-bottom: 0;
+  }
+}
+@media (min-width: 1200px) {
+  /* Widen the fixed sidebar again */
+  .bs-sidebar.affix-bottom,
+  .bs-sidebar.affix {
+    width: 263px;
+  }
+}
+
+
+/* Not enough room on mobile for markup tab, js tab, and plunk btn.
+  And no one cares about plunk button on a phone anyway */
+@media only screen and (max-device-width: 480px) {
+    #plunk-btn {
+        display: none;
+    }
+}
+
+.navbar-nav .dropdown .navbar-brand {
+    max-width: 100%;
+    margin-right: inherit;
+    margin-left: inherit;
+}
+
+.header-placeholder {
+    height: 50px;
+}
+
+@media screen and (min-width: 768px) {
+
+    .dropdown.open > .navbar-brand + .dropdown-menu {
+        left: 10px;
+    }
+
+    .header-placeholder {
+        height: 50px;
+    }
+
+    .navbar-nav .dropdown .navbar-brand {
+        max-width: 200px;
+        margin-right: 5px;
+        margin-left: 10px;
+    }
+
+}
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/favicon.ico b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/favicon.ico
new file mode 100644
index 0000000..3d3f000
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/favicon.ico
Binary files differ
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/github-16px.png b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/github-16px.png
new file mode 100644
index 0000000..c99ab23
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/github-16px.png
Binary files differ
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/header.png b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/header.png
new file mode 100644
index 0000000..785dd10
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/header.png
Binary files differ
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/img/glyphicons-halflings-white.png b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/img/glyphicons-halflings-white.png
new file mode 100644
index 0000000..3bf6484
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/img/glyphicons-halflings-white.png
Binary files differ
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/img/glyphicons-halflings.png b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/img/glyphicons-halflings.png
new file mode 100644
index 0000000..a996999
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/img/glyphicons-halflings.png
Binary files differ
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/plunker.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/plunker.js
new file mode 100644
index 0000000..1e58610
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/plunker.js
@@ -0,0 +1,59 @@
+angular.module('plunker', [])
+
+  .factory('plunkGenerator', function ($document) {
+
+    return function (ngVersion, bsVersion, version, module, content) {
+
+      var form = angular.element('<form style="display: none;" method="post" action="http://plnkr.co/edit/?p=preview" target="_blank"></form>');
+      var addField = function (name, value) {
+        var input = angular.element('<input type="hidden" name="' + name + '">');
+        input.attr('value', value);
+        form.append(input);
+      };
+
+      var indexContent = function (content, version) {
+        return '<!doctype html>\n' +
+          '<html ng-app="ui.bootstrap.demo">\n' +
+          '  <head>\n' +
+          '    <script src="//ajax.googleapis.com/ajax/libs/angularjs/'+ngVersion+'/angular.js"></script>\n' +
+          '    <script src="//ajax.googleapis.com/ajax/libs/angularjs/'+ngVersion+'/angular-animate.js"></script>\n' +
+          '    <script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-'+version+'.js"></script>\n' +
+          '    <script src="example.js"></script>\n' +
+          '    <link href="//netdna.bootstrapcdn.com/bootstrap/'+bsVersion+'/css/bootstrap.min.css" rel="stylesheet">\n' +
+          '  </head>\n' +
+          '  <body>\n\n' +
+          content + '\n' +
+          '  </body>\n' +
+          '</html>\n';
+      };
+
+      var scriptContent = function(content) {
+        return "angular.module('ui.bootstrap.demo', ['ngAnimate', 'ui.bootstrap']);" + "\n" + content;
+      };
+
+      addField('description', 'http://angular-ui.github.io/bootstrap/');
+      addField('files[index.html]', indexContent(content.markup, version));
+      addField('files[example.js]', scriptContent(content.javascript));
+
+      $document.find('body').append(form);
+      form[0].submit();
+      form.remove();
+    };
+  })
+
+  .controller('PlunkerCtrl', function ($scope, plunkGenerator) {
+
+    $scope.content = {};
+
+    $scope.edit = function (ngVersion, bsVersion, version, module) {
+      plunkGenerator(ngVersion, bsVersion, version, module, $scope.content);
+    };
+  })
+
+  .directive('plunkerContent', function () {
+    return {
+      link:function (scope, element, attrs) {
+        scope.content[attrs.plunkerContent] = element.text().trim();
+      }
+    }
+  });
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/rainbow-generic.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/rainbow-generic.js
new file mode 100644
index 0000000..bfc5477
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/rainbow-generic.js
@@ -0,0 +1,59 @@
+/**
+ * Generic language patterns
+ *
+ * @author Craig Campbell
+ * @version 1.0.9
+ */
+Rainbow.extend([
+    {
+        'matches': {
+            1: {
+                'name': 'keyword.operator',
+                'pattern': /\=/g
+            },
+            2: {
+                'name': 'string',
+                'matches': {
+                    'name': 'constant.character.escape',
+                    'pattern': /\\('|"){1}/g
+                }
+            }
+        },
+        'pattern': /(\(|\s|\[|\=|:)(('|")([^\\\1]|\\.)*?(\3))/gm
+    },
+    {
+        'name': 'comment',
+        'pattern': /\/\*[\s\S]*?\*\/|(\/\/|\#)[\s\S]*?$/gm
+    },
+    {
+        'name': 'constant.numeric',
+        'pattern': /\b(\d+(\.\d+)?(e(\+|\-)?\d+)?(f|d)?|0x[\da-f]+)\b/gi
+    },
+    {
+        'matches': {
+            1: 'keyword'
+        },
+        'pattern': /\b(and|array|as|bool(ean)?|c(atch|har|lass|onst)|d(ef|elete|o(uble)?)|e(cho|lse(if)?|xit|xtends|xcept)|f(inally|loat|or(each)?|unction)|global|if|import|int(eger)?|long|new|object|or|pr(int|ivate|otected)|public|return|self|st(ring|ruct|atic)|switch|th(en|is|row)|try|(un)?signed|var|void|while)(?=\(|\b)/gi
+    },
+    {
+        'name': 'constant.language',
+        'pattern': /true|false|null/g
+    },
+    {
+        'name': 'keyword.operator',
+        'pattern': /\+|\!|\-|&(gt|lt|amp);|\||\*|\=/g
+    },
+    {
+        'matches': {
+            1: 'function.call'
+        },
+        'pattern': /(\w+?)(?=\()/g
+    },
+    {
+        'matches': {
+            1: 'storage.function',
+            2: 'entity.name.function'
+        },
+        'pattern': /(function)\s(.*?)(?=\()/g
+    }
+]);
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/rainbow-html.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/rainbow-html.js
new file mode 100644
index 0000000..f221888
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/rainbow-html.js
@@ -0,0 +1,83 @@
+/**
+ * HTML patterns
+ *
+ * @author Craig Campbell
+ * @version 1.0.7
+ */
+Rainbow.extend('html', [
+    {
+        'name': 'source.php.embedded',
+        'matches': {
+            2: {
+                'language': 'php'
+            }
+        },
+        'pattern': /&lt;\?=?(?!xml)(php)?([\s\S]*?)(\?&gt;)/gm
+    },
+    {
+        'name': 'source.css.embedded',
+        'matches': {
+            0: {
+                'language': 'css'
+            }
+        },
+        'pattern': /&lt;style(.*?)&gt;([\s\S]*?)&lt;\/style&gt;/gm
+    },
+    {
+        'name': 'source.js.embedded',
+        'matches': {
+            0: {
+                'language': 'javascript'
+            }
+        },
+        'pattern': /&lt;script(?! src)(.*?)&gt;([\s\S]*?)&lt;\/script&gt;/gm
+    },
+    {
+        'name': 'comment.html',
+        'pattern': /&lt;\!--[\S\s]*?--&gt;/g
+    },
+    {
+        'matches': {
+            1: 'support.tag.open',
+            2: 'support.tag.cclose'
+        },
+        'pattern': /(&lt;)|(\/?\??&gt;)/g
+    },
+    {
+        'name': 'support.tag',
+        'matches': {
+            1: 'support.tag',
+            2: 'support.tag.special',
+            3: 'support.tag-name'
+        },
+        'pattern': /(&lt;\??)(\/|\!?)(\w+)/g
+    },
+    {
+        'matches': {
+            1: 'support.attribute'
+        },
+        'pattern': /([a-z-]+)(?=\=)/gi
+    },
+    {
+        'matches': {
+            1: 'support.operator',
+            2: 'string.quote',
+            3: 'string.value',
+            4: 'string.quote'
+        },
+        'pattern': /(=)('|")(.*?)(\2)/g
+    },
+    {
+        'matches': {
+            1: 'support.operator',
+            2: 'support.value'
+        },
+        'pattern': /(=)([a-zA-Z\-0-9]*)\b/g
+    },
+    {
+        'matches': {
+            1: 'support.attribute'
+        },
+        'pattern': /\s(\w+)(?=\s|&gt;)(?![\s\S]*&lt;)/g
+    }
+], true);
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/rainbow-javascript.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/rainbow-javascript.js
new file mode 100644
index 0000000..83b0d46
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/rainbow-javascript.js
@@ -0,0 +1,110 @@
+/**
+ * Javascript patterns
+ *
+ * @author Craig Campbell
+ * @version 1.0.7
+ */
+Rainbow.extend('javascript', [
+
+    /**
+     * matches $. or $(
+     */
+    {
+        'name': 'selector',
+        'pattern': /(\s|^)\$(?=\.|\()/g
+    },
+    {
+        'name': 'support',
+        'pattern': /\b(window|document)\b/g
+    },
+    {
+        'matches': {
+            1: 'support.property'
+        },
+        'pattern': /\.(length|node(Name|Value))\b/g
+    },
+    {
+        'matches': {
+            1: 'support.function'
+        },
+        'pattern': /(setTimeout|setInterval)(?=\()/g
+
+    },
+    {
+        'matches': {
+            1: 'support.method'
+        },
+        'pattern': /\.(getAttribute|push|getElementById|getElementsByClassName|log|setTimeout|setInterval)(?=\()/g
+    },
+    {
+        'matches': {
+            1: 'support.tag.script',
+            2: [
+                {
+                    'name': 'string',
+                    'pattern': /('|")(.*?)(\1)/g
+                },
+                {
+                    'name': 'entity.tag.script',
+                    'pattern': /(\w+)/g
+                }
+            ],
+            3: 'support.tag.script'
+        },
+        'pattern': /(&lt;\/?)(script.*?)(&gt;)/g
+    },
+
+    /**
+     * matches any escaped characters inside of a js regex pattern
+     *
+     * @see https://github.com/ccampbell/rainbow/issues/22
+     *
+     * this was causing single line comments to fail so it now makes sure
+     * the opening / is not directly followed by a *
+     *
+     * @todo check that there is valid regex in match group 1
+     */
+    {
+        'name': 'string.regexp',
+        'matches': {
+            1: 'string.regexp.open',
+            2: {
+                'name': 'constant.regexp.escape',
+                'pattern': /\\(.){1}/g
+            },
+            3: 'string.regexp.cclose',
+            4: 'string.regexp.modifier'
+        },
+        'pattern': /(\/)(?!\*)(.+)(\/)([igm]{0,3})/g
+    },
+
+    /**
+     * matches runtime function declarations
+     */
+    {
+        'matches': {
+            1: 'storage',
+            3: 'entity.function'
+        },
+        'pattern': /(var)?(\s|^)(.*)(?=\s?=\s?function\()/g
+    },
+
+    /**
+     * matches constructor call
+     */
+    {
+        'matches': {
+            1: 'keyword',
+            2: 'entity.function'
+        },
+        'pattern': /(new)\s+(.*)(?=\()/g
+    },
+
+    /**
+     * matches any function call in the style functionName: function()
+     */
+    {
+        'name': 'entity.function',
+        'pattern': /(\w+)(?=:\s{0,}function)/g
+    }
+]);
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/rainbow.css b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/rainbow.css
new file mode 100644
index 0000000..0a82f65
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/rainbow.css
@@ -0,0 +1,88 @@
+/**
+ * GitHub theme
+ *
+ * @author Craig Campbell
+ * @version 1.0.4
+ */
+pre {
+    border: 1px solid #ccc;
+    word-wrap: break-word;
+    padding: 6px 10px;
+    line-height: 19px;
+    margin-bottom: 20px;
+}
+
+code {
+    border: 1px solid #eaeaea;
+    margin: 0 2px;
+    padding: 0 5px;
+    font-size: 12px;
+}
+
+pre code {
+    border: 0;
+    padding: 0;
+    margin: 0;
+    -moz-border-radius: 0;
+    -webkit-border-radius: 0;
+    border-radius: 0;
+}
+
+pre, code {
+    font-family: Consolas, 'Liberation Mono', Courier, monospace;
+    color: #333;
+    background: #f8f8f8;
+    -moz-border-radius: 3px;
+    -webkit-border-radius: 3px;
+    border-radius: 3px;
+}
+
+pre, pre code {
+    font-size: 13px;
+}
+
+pre .comment {
+    color: #998;
+}
+
+pre .support {
+    color: #0086B3;
+}
+
+pre .tag, pre .tag-name {
+    color: navy;
+}
+
+pre .keyword, pre .css-property, pre .vendor-prefix, pre .sass, pre .class, pre .id, pre .css-value, pre .entity.function, pre .storage.function {
+    font-weight: bold;
+}
+
+pre .css-property, pre .css-value, pre .vendor-prefix, pre .support.namespace {
+    color: #333;
+}
+
+pre .constant.numeric, pre .keyword.unit, pre .hex-color {
+    font-weight: normal;
+    color: #099;
+}
+
+pre .entity.class {
+    color: #458;
+}
+
+pre .entity.id, pre .entity.function {
+    color: #900;
+}
+
+pre .attribute, pre .variable {
+    color: teal;
+}
+
+pre .string, pre .support.value  {
+    font-weight: normal;
+    color: #d14;
+}
+
+pre .regexp {
+    color: #009926;
+}
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/rainbow.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/rainbow.js
new file mode 100644
index 0000000..ed8894d
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/rainbow.js
@@ -0,0 +1,773 @@
+/**
+ * Copyright 2012 Craig Campbell
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Rainbow is a simple code syntax highlighter
+ *
+ * @preserve @version 1.1.8
+ * @url rainbowco.de
+ */
+window['Rainbow'] = (function() {
+
+    /**
+     * array of replacements to process at the end
+     *
+     * @type {Object}
+     */
+    var replacements = {},
+
+        /**
+         * an array of start and end positions of blocks to be replaced
+         *
+         * @type {Object}
+         */
+        replacement_positions = {},
+
+        /**
+         * an array of the language patterns specified for each language
+         *
+         * @type {Object}
+         */
+        language_patterns = {},
+
+        /**
+         * an array of languages and whether they should bypass the default patterns
+         *
+         * @type {Object}
+         */
+        bypass_defaults = {},
+
+        /**
+         * processing level
+         *
+         * replacements are stored at this level so if there is a sub block of code
+         * (for example php inside of html) it runs at a different level
+         *
+         * @type {number}
+         */
+        CURRENT_LEVEL = 0,
+
+        /**
+         * constant used to refer to the default language
+         *
+         * @type {number}
+         */
+        DEFAULT_LANGUAGE = 0,
+
+        /**
+         * used as counters so we can selectively call setTimeout
+         * after processing a certain number of matches/replacements
+         *
+         * @type {number}
+         */
+        match_counter = 0,
+
+        /**
+         * @type {number}
+         */
+        replacement_counter = 0,
+
+        /**
+         * @type {null|string}
+         */
+        global_class,
+
+        /**
+         * @type {null|Function}
+         */
+        onHighlight;
+
+    /**
+     * cross browser get attribute for an element
+     *
+     * @see http://stackoverflow.com/questions/3755227/cross-browser-javascript-getattribute-method
+     *
+     * @param {Node} el
+     * @param {string} attr     attribute you are trying to get
+     * @returns {string|number}
+     */
+    function _attr(el, attr, attrs, i) {
+        var result = (el.getAttribute && el.getAttribute(attr)) || 0;
+
+        if (!result) {
+            attrs = el.attributes;
+
+            for (i = 0; i < attrs.length; ++i) {
+                if (attrs[i].nodeName === attr) {
+                    return attrs[i].nodeValue;
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * adds a class to a given code block
+     *
+     * @param {Element} el
+     * @param {string} class_name   class name to add
+     * @returns void
+     */
+    function _addClass(el, class_name) {
+        el.className += el.className ? ' ' + class_name : class_name;
+    }
+
+    /**
+     * checks if a block has a given class
+     *
+     * @param {Element} el
+     * @param {string} class_name   class name to check for
+     * @returns {boolean}
+     */
+    function _hasClass(el, class_name) {
+        return (' ' + el.className + ' ').indexOf(' ' + class_name + ' ') > -1;
+    }
+
+    /**
+     * gets the language for this block of code
+     *
+     * @param {Element} block
+     * @returns {string|null}
+     */
+    function _getLanguageForBlock(block) {
+
+        // if this doesn't have a language but the parent does then use that
+        // this means if for example you have: <pre data-language="php">
+        // with a bunch of <code> blocks inside then you do not have
+        // to specify the language for each block
+        var language = _attr(block, 'data-language') || _attr(block.parentNode, 'data-language');
+
+        // this adds support for specifying language via a css class
+        // you can use the Google Code Prettify style: <pre class="lang-php">
+        // or the HTML5 style: <pre><code class="language-php">
+        if (!language) {
+            var pattern = /\blang(?:uage)?-(\w+)/,
+                match = block.className.match(pattern) || block.parentNode.className.match(pattern);
+
+            if (match) {
+                language = match[1];
+            }
+        }
+
+        return language;
+    }
+
+    /**
+     * makes sure html entities are always used for tags
+     *
+     * @param {string} code
+     * @returns {string}
+     */
+    function _htmlEntities(code) {
+        return code.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/&(?![\w\#]+;)/g, '&amp;');
+    }
+
+    /**
+     * determines if a new match intersects with an existing one
+     *
+     * @param {number} start1    start position of existing match
+     * @param {number} end1      end position of existing match
+     * @param {number} start2    start position of new match
+     * @param {number} end2      end position of new match
+     * @returns {boolean}
+     */
+    function _intersects(start1, end1, start2, end2) {
+        if (start2 >= start1 && start2 < end1) {
+            return true;
+        }
+
+        return end2 > start1 && end2 < end1;
+    }
+
+    /**
+     * determines if two different matches have complete overlap with each other
+     *
+     * @param {number} start1   start position of existing match
+     * @param {number} end1     end position of existing match
+     * @param {number} start2   start position of new match
+     * @param {number} end2     end position of new match
+     * @returns {boolean}
+     */
+    function _hasCompleteOverlap(start1, end1, start2, end2) {
+
+        // if the starting and end positions are exactly the same
+        // then the first one should stay and this one should be ignored
+        if (start2 == start1 && end2 == end1) {
+            return false;
+        }
+
+        return start2 <= start1 && end2 >= end1;
+    }
+
+    /**
+     * determines if the match passed in falls inside of an existing match
+     * this prevents a regex pattern from matching inside of a bigger pattern
+     *
+     * @param {number} start - start position of new match
+     * @param {number} end - end position of new match
+     * @returns {boolean}
+     */
+    function _matchIsInsideOtherMatch(start, end) {
+        for (var key in replacement_positions[CURRENT_LEVEL]) {
+            key = parseInt(key, 10);
+
+            // if this block completely overlaps with another block
+            // then we should remove the other block and return false
+            if (_hasCompleteOverlap(key, replacement_positions[CURRENT_LEVEL][key], start, end)) {
+                delete replacement_positions[CURRENT_LEVEL][key];
+                delete replacements[CURRENT_LEVEL][key];
+            }
+
+            if (_intersects(key, replacement_positions[CURRENT_LEVEL][key], start, end)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * takes a string of code and wraps it in a span tag based on the name
+     *
+     * @param {string} name     name of the pattern (ie keyword.regex)
+     * @param {string} code     block of code to wrap
+     * @returns {string}
+     */
+    function _wrapCodeInSpan(name, code) {
+        return '<span class="' + name.replace(/\./g, ' ') + (global_class ? ' ' + global_class : '') + '">' + code + '</span>';
+    }
+
+    /**
+     * finds out the position of group match for a regular expression
+     *
+     * @see http://stackoverflow.com/questions/1985594/how-to-find-index-of-groups-in-match
+     *
+     * @param {Object} match
+     * @param {number} group_number
+     * @returns {number}
+     */
+    function _indexOfGroup(match, group_number) {
+        var index = 0,
+            i;
+
+        for (i = 1; i < group_number; ++i) {
+            if (match[i]) {
+                index += match[i].length;
+            }
+        }
+
+        return index;
+    }
+
+    /**
+     * matches a regex pattern against a block of code
+     * finds all matches that should be processed and stores the positions
+     * of where they should be replaced within the string
+     *
+     * this is where pretty much all the work is done but it should not
+     * be called directly
+     *
+     * @param {RegExp} pattern
+     * @param {string} code
+     * @returns void
+     */
+    function _processPattern(regex, pattern, code, callback)
+    {
+        var match = regex.exec(code);
+
+        if (!match) {
+            return callback();
+        }
+
+        ++match_counter;
+
+        // treat match 0 the same way as name
+        if (!pattern['name'] && typeof pattern['matches'][0] == 'string') {
+            pattern['name'] = pattern['matches'][0];
+            delete pattern['matches'][0];
+        }
+
+        var replacement = match[0],
+            start_pos = match.index,
+            end_pos = match[0].length + start_pos,
+
+            /**
+             * callback to process the next match of this pattern
+             */
+            processNext = function() {
+                var nextCall = function() {
+                    _processPattern(regex, pattern, code, callback);
+                };
+
+                // every 100 items we process let's call set timeout
+                // to let the ui breathe a little
+                return match_counter % 100 > 0 ? nextCall() : setTimeout(nextCall, 0);
+            };
+
+        // if this is not a child match and it falls inside of another
+        // match that already happened we should skip it and continue processing
+        if (_matchIsInsideOtherMatch(start_pos, end_pos)) {
+            return processNext();
+        }
+
+        /**
+         * callback for when a match was successfully processed
+         *
+         * @param {string} replacement
+         * @returns void
+         */
+        var onMatchSuccess = function(replacement) {
+                // if this match has a name then wrap it in a span tag
+                if (pattern['name']) {
+                    replacement = _wrapCodeInSpan(pattern['name'], replacement);
+                }
+
+                // console.log('LEVEL', CURRENT_LEVEL, 'replace', match[0], 'with', replacement, 'at position', start_pos, 'to', end_pos);
+
+                // store what needs to be replaced with what at this position
+                if (!replacements[CURRENT_LEVEL]) {
+                    replacements[CURRENT_LEVEL] = {};
+                    replacement_positions[CURRENT_LEVEL] = {};
+                }
+
+                replacements[CURRENT_LEVEL][start_pos] = {
+                    'replace': match[0],
+                    'with': replacement
+                };
+
+                // store the range of this match so we can use it for comparisons
+                // with other matches later
+                replacement_positions[CURRENT_LEVEL][start_pos] = end_pos;
+
+                // process the next match
+                processNext();
+            },
+
+            // if this pattern has sub matches for different groups in the regex
+            // then we should process them one at a time by rerunning them through
+            // this function to generate the new replacement
+            //
+            // we run through them backwards because the match position of earlier
+            // matches will not change depending on what gets replaced in later
+            // matches
+            group_keys = keys(pattern['matches']),
+
+            /**
+             * callback for processing a sub group
+             *
+             * @param {number} i
+             * @param {Array} group_keys
+             * @param {Function} callback
+             */
+            processGroup = function(i, group_keys, callback) {
+                if (i >= group_keys.length) {
+                    return callback(replacement);
+                }
+
+                var processNextGroup = function() {
+                        processGroup(++i, group_keys, callback);
+                    },
+                    block = match[group_keys[i]];
+
+                // if there is no match here then move on
+                if (!block) {
+                    return processNextGroup();
+                }
+
+                var group = pattern['matches'][group_keys[i]],
+                    language = group['language'],
+
+                    /**
+                     * process group is what group we should use to actually process
+                     * this match group
+                     *
+                     * for example if the subgroup pattern looks like this
+                     * 2: {
+                     *     'name': 'keyword',
+                     *     'pattern': /true/g
+                     * }
+                     *
+                     * then we use that as is, but if it looks like this
+                     *
+                     * 2: {
+                     *     'name': 'keyword',
+                     *     'matches': {
+                     *          'name': 'special',
+                     *          'pattern': /whatever/g
+                     *      }
+                     * }
+                     *
+                     * we treat the 'matches' part as the pattern and keep
+                     * the name around to wrap it with later
+                     */
+                    process_group = group['name'] && group['matches'] ? group['matches'] : group,
+
+                    /**
+                     * takes the code block matched at this group, replaces it
+                     * with the highlighted block, and optionally wraps it with
+                     * a span with a name
+                     *
+                     * @param {string} block
+                     * @param {string} replace_block
+                     * @param {string|null} match_name
+                     */
+                    _replaceAndContinue = function(block, replace_block, match_name) {
+                        replacement = _replaceAtPosition(_indexOfGroup(match, group_keys[i]), block, match_name ? _wrapCodeInSpan(match_name, replace_block) : replace_block, replacement);
+                        processNextGroup();
+                    };
+
+                // if this is a sublanguage go and process the block using that language
+                if (language) {
+                    return _highlightBlockForLanguage(block, language, function(code) {
+                        _replaceAndContinue(block, code);
+                    });
+                }
+
+                // if this is a string then this match is directly mapped to selector
+                // so all we have to do is wrap it in a span and continue
+                if (typeof group === 'string') {
+                    return _replaceAndContinue(block, block, group);
+                }
+
+                // the process group can be a single pattern or an array of patterns
+                // _processCodeWithPatterns always expects an array so we convert it here
+                _processCodeWithPatterns(block, process_group.length ? process_group : [process_group], function(code) {
+                    _replaceAndContinue(block, code, group['matches'] ? group['name'] : 0);
+                });
+            };
+
+        processGroup(0, group_keys, onMatchSuccess);
+    }
+
+    /**
+     * should a language bypass the default patterns?
+     *
+     * if you call Rainbow.extend() and pass true as the third argument
+     * it will bypass the defaults
+     */
+    function _bypassDefaultPatterns(language)
+    {
+        return bypass_defaults[language];
+    }
+
+    /**
+     * returns a list of regex patterns for this language
+     *
+     * @param {string} language
+     * @returns {Array}
+     */
+    function _getPatternsForLanguage(language) {
+        var patterns = language_patterns[language] || [],
+            default_patterns = language_patterns[DEFAULT_LANGUAGE] || [];
+
+        return _bypassDefaultPatterns(language) ? patterns : patterns.concat(default_patterns);
+    }
+
+    /**
+     * substring replace call to replace part of a string at a certain position
+     *
+     * @param {number} position         the position where the replacement should happen
+     * @param {string} replace          the text we want to replace
+     * @param {string} replace_with     the text we want to replace it with
+     * @param {string} code             the code we are doing the replacing in
+     * @returns {string}
+     */
+    function _replaceAtPosition(position, replace, replace_with, code) {
+        var sub_string = code.substr(position);
+        return code.substr(0, position) + sub_string.replace(replace, replace_with);
+    }
+
+   /**
+     * sorts an object by index descending
+     *
+     * @param {Object} object
+     * @return {Array}
+     */
+    function keys(object) {
+        var locations = [],
+            replacement,
+            pos;
+
+        for(var location in object) {
+            if (object.hasOwnProperty(location)) {
+                locations.push(location);
+            }
+        }
+
+        // numeric descending
+        return locations.sort(function(a, b) {
+            return b - a;
+        });
+    }
+
+    /**
+     * processes a block of code using specified patterns
+     *
+     * @param {string} code
+     * @param {Array} patterns
+     * @returns void
+     */
+    function _processCodeWithPatterns(code, patterns, callback)
+    {
+        // we have to increase the level here so that the
+        // replacements will not conflict with each other when
+        // processing sub blocks of code
+        ++CURRENT_LEVEL;
+
+        // patterns are processed one at a time through this function
+        function _workOnPatterns(patterns, i)
+        {
+            // still have patterns to process, keep going
+            if (i < patterns.length) {
+                return _processPattern(patterns[i]['pattern'], patterns[i], code, function() {
+                    _workOnPatterns(patterns, ++i);
+                });
+            }
+
+            // we are done processing the patterns
+            // process the replacements and update the DOM
+            _processReplacements(code, function(code) {
+
+                // when we are done processing replacements
+                // we are done at this level so we can go back down
+                delete replacements[CURRENT_LEVEL];
+                delete replacement_positions[CURRENT_LEVEL];
+                --CURRENT_LEVEL;
+                callback(code);
+            });
+        }
+
+        _workOnPatterns(patterns, 0);
+    }
+
+    /**
+     * process replacements in the string of code to actually update the markup
+     *
+     * @param {string} code         the code to process replacements in
+     * @param {Function} onComplete   what to do when we are done processing
+     * @returns void
+     */
+    function _processReplacements(code, onComplete) {
+
+        /**
+         * processes a single replacement
+         *
+         * @param {string} code
+         * @param {Array} positions
+         * @param {number} i
+         * @param {Function} onComplete
+         * @returns void
+         */
+        function _processReplacement(code, positions, i, onComplete) {
+            if (i < positions.length) {
+                ++replacement_counter;
+                var pos = positions[i],
+                    replacement = replacements[CURRENT_LEVEL][pos];
+                code = _replaceAtPosition(pos, replacement['replace'], replacement['with'], code);
+
+                // process next function
+                var next = function() {
+                    _processReplacement(code, positions, ++i, onComplete);
+                };
+
+                // use a timeout every 250 to not freeze up the UI
+                return replacement_counter % 250 > 0 ? next() : setTimeout(next, 0);
+            }
+
+            onComplete(code);
+        }
+
+        var string_positions = keys(replacements[CURRENT_LEVEL]);
+        _processReplacement(code, string_positions, 0, onComplete);
+    }
+
+    /**
+     * takes a string of code and highlights it according to the language specified
+     *
+     * @param {string} code
+     * @param {string} language
+     * @param {Function} onComplete
+     * @returns void
+     */
+    function _highlightBlockForLanguage(code, language, onComplete) {
+        var patterns = _getPatternsForLanguage(language);
+        _processCodeWithPatterns(_htmlEntities(code), patterns, onComplete);
+    }
+
+    /**
+     * highlight an individual code block
+     *
+     * @param {Array} code_blocks
+     * @param {number} i
+     * @returns void
+     */
+    function _highlightCodeBlock(code_blocks, i, onComplete) {
+        if (i < code_blocks.length) {
+            var block = code_blocks[i],
+                language = _getLanguageForBlock(block);
+
+            if (!_hasClass(block, 'rainbow') && language) {
+                language = language.toLowerCase();
+
+                _addClass(block, 'rainbow');
+
+                return _highlightBlockForLanguage(block.innerHTML, language, function(code) {
+                    block.innerHTML = code;
+
+                    // reset the replacement arrays
+                    replacements = {};
+                    replacement_positions = {};
+
+                    // if you have a listener attached tell it that this block is now highlighted
+                    if (onHighlight) {
+                        onHighlight(block, language);
+                    }
+
+                    // process the next block
+                    setTimeout(function() {
+                        _highlightCodeBlock(code_blocks, ++i, onComplete);
+                    }, 0);
+                });
+            }
+            return _highlightCodeBlock(code_blocks, ++i, onComplete);
+        }
+
+        if (onComplete) {
+            onComplete();
+        }
+    }
+
+    /**
+     * start highlighting all the code blocks
+     *
+     * @returns void
+     */
+    function _highlight(node, onComplete) {
+
+        // the first argument can be an Event or a DOM Element
+        // I was originally checking instanceof Event but that makes it break
+        // when using mootools
+        //
+        // @see https://github.com/ccampbell/rainbow/issues/32
+        //
+        node = node && typeof node.getElementsByTagName == 'function' ? node : document;
+
+        var pre_blocks = node.getElementsByTagName('pre'),
+            code_blocks = node.getElementsByTagName('code'),
+            i,
+            final_blocks = [];
+
+        // @see http://stackoverflow.com/questions/2735067/how-to-convert-a-dom-node-list-to-an-array-in-javascript
+        // we are going to process all <code> blocks
+        for (i = 0; i < code_blocks.length; ++i) {
+            final_blocks.push(code_blocks[i]);
+        }
+
+        // loop through the pre blocks to see which ones we should add
+        for (i = 0; i < pre_blocks.length; ++i) {
+
+            // if the pre block has no code blocks then process it directly
+            if (!pre_blocks[i].getElementsByTagName('code').length) {
+                final_blocks.push(pre_blocks[i]);
+            }
+        }
+
+        _highlightCodeBlock(final_blocks, 0, onComplete);
+    }
+
+    /**
+     * public methods
+     */
+    return {
+
+        /**
+         * extends the language pattern matches
+         *
+         * @param {*} language     name of language
+         * @param {*} patterns      array of patterns to add on
+         * @param {boolean|null} bypass      if true this will bypass the default language patterns
+         */
+        extend: function(language, patterns, bypass) {
+
+            // if there is only one argument then we assume that we want to
+            // extend the default language rules
+            if (arguments.length == 1) {
+                patterns = language;
+                language = DEFAULT_LANGUAGE;
+            }
+
+            bypass_defaults[language] = bypass;
+            language_patterns[language] = patterns.concat(language_patterns[language] || []);
+        },
+
+        /**
+         * call back to let you do stuff in your app after a piece of code has been highlighted
+         *
+         * @param {Function} callback
+         */
+        onHighlight: function(callback) {
+            onHighlight = callback;
+        },
+
+        /**
+         * method to set a global class that will be applied to all spans
+         *
+         * @param {string} class_name
+         */
+        addClass: function(class_name) {
+            global_class = class_name;
+        },
+
+        /**
+         * starts the magic rainbow
+         *
+         * @returns void
+         */
+        color: function() {
+
+            // if you want to straight up highlight a string you can pass the string of code,
+            // the language, and a callback function
+            if (typeof arguments[0] == 'string') {
+                return _highlightBlockForLanguage(arguments[0], arguments[1], arguments[2]);
+            }
+
+            // if you pass a callback function then we rerun the color function
+            // on all the code and call the callback function on complete
+            if (typeof arguments[0] == 'function') {
+                return _highlight(0, arguments[0]);
+            }
+
+            // otherwise we use whatever node you passed in with an optional
+            // callback function as the second parameter
+            _highlight(arguments[0], arguments[1]);
+        }
+    };
+}) ();
+
+/**
+ * adds event listener to start highlighting
+ */
+(function() {
+    if (window.addEventListener) {
+        return window.addEventListener('load', Rainbow.color, false);
+    }
+    window.attachEvent('onload', Rainbow.color);
+}) ();
+
+// When using Google closure compiler in advanced mode some methods
+// get renamed.  This keeps a public reference to these methods so they can
+// still be referenced from outside this library.
+Rainbow["onHighlight"] = Rainbow.onHighlight;
+Rainbow["addClass"] = Rainbow.addClass;
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/smoothscroll-angular-custom.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/smoothscroll-angular-custom.js
new file mode 100644
index 0000000..e33586f
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/smoothscroll-angular-custom.js
@@ -0,0 +1,97 @@
+/*
+ * https://github.com/alicelieutier/smoothScroll/
+ * A teeny tiny, standard compliant, smooth scroll script with ease-in-out effect and no jQuery (or any other dependancy, FWIW).
+ * MIT License
+ */
+window.smoothScroll = (function(){
+// We do not want this script to be applied in browsers that do not support those
+// That means no smoothscroll on IE9 and below.
+if(document.querySelectorAll === void 0 || window.pageYOffset === void 0 || history.pushState === void 0) { return; }
+
+// Get the top position of an element in the document
+var getTop = function(element) {
+    // return value of html.getBoundingClientRect().top ... IE : 0, other browsers : -pageYOffset
+    if(element.nodeName === 'HTML') return -window.pageYOffset
+    return element.getBoundingClientRect().top + window.pageYOffset;
+}
+// ease in out function thanks to:
+// http://blog.greweb.fr/2012/02/bezier-curve-based-easing-functions-from-concept-to-implementation/
+var easeInOutCubic = function (t) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 }
+
+// calculate the scroll position we should be in
+// given the start and end point of the scroll
+// the time elapsed from the beginning of the scroll
+// and the total duration of the scroll (default 500ms)
+var position = function(start, end, elapsed, duration) {
+    if (elapsed > duration) return end;
+    return start + (end - start) * easeInOutCubic(elapsed / duration); // <-- you can change the easing funtion there
+    // return start + (end - start) * (elapsed / duration); // <-- this would give a linear scroll
+}
+
+// we use requestAnimationFrame to be called by the browser before every repaint
+// if the first argument is an element then scroll to the top of this element
+// if the first argument is numeric then scroll to this location
+// if the callback exist, it is called when the scrolling is finished
+var smoothScroll = function(el, duration, callback){
+    duration = duration || 500;
+    var start = window.pageYOffset;
+
+    if (typeof el === 'number') {
+      var end = parseInt(el);
+    } else {
+      var end = getTop(el);
+    }
+
+    var clock = Date.now();
+    var requestAnimationFrame = window.requestAnimationFrame ||
+        window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame ||
+        function(fn){window.setTimeout(fn, 15);};
+
+    var step = function(){
+        var elapsed = Date.now() - clock;
+        window.scroll(0, position(start, end, elapsed, duration));
+        if (elapsed > duration) {
+            if (typeof callback === 'function') {
+                callback(el);
+            }
+        } else {
+            requestAnimationFrame(step);
+        }
+    }
+    step();
+}
+
+var linkHandler = function(ev) {
+    ev.preventDefault();
+
+    if (location.hash !== this.hash) {
+      //NOTE(@ajoslin): Changed this line to stop $digest errors
+      //window.history.pushState(null, null, this.hash)
+      angular.element(document).injector().get('$location').hash(this.hash);
+    }
+    // using the history api to solve issue #1 - back doesn't work
+    // most browser don't update :target when the history api is used:
+    // THIS IS A BUG FROM THE BROWSERS.
+    // change the scrolling duration in this call
+    var targetEl = document.getElementById(this.hash.substring(1));
+    if (targetEl) {
+      smoothScroll(document.getElementById(this.hash.substring(1)), 500, function(el) {
+        location.replace('#' + el.id)
+        // this will cause the :target to be activated.
+      });
+    }
+}
+
+// We look for all the internal links in the documents and attach the smoothscroll function
+document.addEventListener("DOMContentLoaded", function () {
+    var internal = document.querySelectorAll('a[href^="#"]'), a;
+    for(var i=internal.length; a=internal[--i];){
+        a.addEventListener("click", linkHandler, false);
+    }
+});
+
+// return smoothscroll API
+return smoothScroll;
+
+})();
+
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/uglifyjs.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/uglifyjs.js
new file mode 100644
index 0000000..2b09c57
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/assets/uglifyjs.js
@@ -0,0 +1,4 @@
+!function(n,e){"use strict";function t(n){for(var e=Object.create(null),t=0;t<n.length;++t)e[n[t]]=!0;return e}function r(n,e){return Array.prototype.slice.call(n,e||0)}function i(n){return n.split("")}function o(n,e){for(var t=e.length;--t>=0;)if(e[t]==n)return!0;return!1}function a(n,e){for(var t=0,r=e.length;r>t;++t)if(n(e[t]))return e[t]}function u(n,e){if(0>=e)return"";if(1==e)return n;var t=u(n,e>>1);return t+=t,1&e&&(t+=n),t}function s(n,e){Error.call(this,n),this.msg=n,this.defs=e}function c(n,e,t){n===!0&&(n={});var r=n||{};if(t)for(var i in r)r.hasOwnProperty(i)&&!e.hasOwnProperty(i)&&s.croak("`"+i+"` is not a supported option",e);for(var i in e)e.hasOwnProperty(i)&&(r[i]=n&&n.hasOwnProperty(i)?n[i]:e[i]);return r}function f(n,e){for(var t in e)e.hasOwnProperty(t)&&(n[t]=e[t]);return n}function l(){}function p(n,e){n.indexOf(e)<0&&n.push(e)}function d(n,e){return n.replace(/\{(.+?)\}/g,function(n,t){return e[t]})}function h(n,e){for(var t=n.length;--t>=0;)n[t]===e&&n.splice(t,1)}function _(n,e){function t(n,t){for(var r=[],i=0,o=0,a=0;i<n.length&&o<t.length;)r[a++]=e(n[i],t[o])<=0?n[i++]:t[o++];return i<n.length&&r.push.apply(r,n.slice(i)),o<t.length&&r.push.apply(r,t.slice(o)),r}function r(n){if(n.length<=1)return n;var e=Math.floor(n.length/2),i=n.slice(0,e),o=n.slice(e);return i=r(i),o=r(o),t(i,o)}return n.length<2?n.slice():r(n)}function m(n,e){return n.filter(function(n){return e.indexOf(n)<0})}function v(n,e){return n.filter(function(n){return e.indexOf(n)>=0})}function g(n){function e(n){if(1==n.length)return t+="return str === "+JSON.stringify(n[0])+";";t+="switch(str){";for(var e=0;e<n.length;++e)t+="case "+JSON.stringify(n[e])+":";t+="return true}return false;"}n instanceof Array||(n=n.split(" "));var t="",r=[];n:for(var i=0;i<n.length;++i){for(var o=0;o<r.length;++o)if(r[o][0].length==n[i].length){r[o].push(n[i]);continue n}r.push([n[i]])}if(r.length>3){r.sort(function(n,e){return e.length-n.length}),t+="switch(str.length){";for(var i=0;i<r.length;++i){var a=r[i];t+="case "+a[0].length+":",e(a)}t+="}"}else e(n);return new Function("str",t)}function b(n,e){for(var t=n.length;--t>=0;)if(!e(n[t]))return!1;return!0}function y(){this._values=Object.create(null),this._size=0}function A(n,e,t,r){arguments.length<4&&(r=W),e=e?e.split(/\s+/):[];var i=e;r&&r.PROPS&&(e=e.concat(r.PROPS));for(var o="return function AST_"+n+"(props){ if (props) { ",a=e.length;--a>=0;)o+="this."+e[a]+" = props."+e[a]+";";var u=r&&new r;(u&&u.initialize||t&&t.initialize)&&(o+="this.initialize();"),o+="}}";var s=new Function(o)();if(u&&(s.prototype=u,s.BASE=r),r&&r.SUBCLASSES.push(s),s.prototype.CTOR=s,s.PROPS=e||null,s.SELF_PROPS=i,s.SUBCLASSES=[],n&&(s.prototype.TYPE=s.TYPE=n),t)for(a in t)t.hasOwnProperty(a)&&(/^\$/.test(a)?s[a.substr(1)]=t[a]:s.prototype[a]=t[a]);return s.DEFMETHOD=function(n,e){this.prototype[n]=e},s}function w(n,e){n.body instanceof Y?n.body._walk(e):n.body.forEach(function(n){n._walk(e)})}function E(n){this.visit=n,this.stack=[]}function D(n){return n>=97&&122>=n||n>=65&&90>=n||n>=170&&qt.letter.test(String.fromCharCode(n))}function F(n){return n>=48&&57>=n}function S(n){return F(n)||D(n)}function C(n){return qt.non_spacing_mark.test(n)||qt.space_combining_mark.test(n)}function k(n){return qt.connector_punctuation.test(n)}function x(n){return!St(n)&&/^[a-z_$][a-z0-9_$]*$/i.test(n)}function B(n){return 36==n||95==n||D(n)}function T(n){var e=n.charCodeAt(0);return B(e)||F(e)||8204==e||8205==e||C(n)||k(n)}function $(n){return/^[a-z_$][a-z0-9_$]*$/i.test(n)}function O(n){return xt.test(n)?parseInt(n.substr(2),16):Bt.test(n)?parseInt(n.substr(1),8):Tt.test(n)?parseFloat(n):void 0}function M(n,e,t,r){this.message=n,this.line=e,this.col=t,this.pos=r,this.stack=(new Error).stack}function N(n,e,t,r,i){throw new M(n,t,r,i)}function R(n,e,t){return n.type==e&&(null==t||n.value==t)}function q(n,e,t){function r(){return D.text.charAt(D.pos)}function i(n,e){var t=D.text.charAt(D.pos++);if(n&&!t)throw Ht;return"\n"==t?(D.newline_before=D.newline_before||!e,++D.line,D.col=0):++D.col,t}function o(n){for(;n-->0;)i()}function a(n){return D.text.substr(D.pos,n.length)==n}function u(n,e){var t=D.text.indexOf(n,D.pos);if(e&&-1==t)throw Ht;return t}function s(){D.tokline=D.line,D.tokcol=D.col,D.tokpos=D.pos}function c(n,t,r){D.regex_allowed="operator"==n&&!Pt(t)||"keyword"==n&&Ct(t)||"punc"==n&&Mt(t),C="punc"==n&&"."==t;var i={type:n,value:t,line:D.tokline,col:D.tokcol,pos:D.tokpos,endpos:D.pos,nlb:D.newline_before,file:e};if(!r){i.comments_before=D.comments_before,D.comments_before=[];for(var o=0,a=i.comments_before.length;a>o;o++)i.nlb=i.nlb||i.comments_before[o].nlb}return D.newline_before=!1,new L(i)}function f(){for(;Ot(r());)i()}function l(n){for(var e,t="",o=0;(e=r())&&n(e,o++);)t+=i();return t}function p(n){N(n,e,D.tokline,D.tokcol,D.tokpos)}function d(n){var e=!1,t=!1,r=!1,i="."==n,o=l(function(o,a){var u=o.charCodeAt(0);switch(u){case 120:case 88:return r?!1:r=!0;case 101:case 69:return r?!0:e?!1:e=t=!0;case 45:return t||0==a&&!n;case 43:return t;case t=!1,46:return i||r||e?!1:i=!0}return S(u)});n&&(o=n+o);var a=O(o);return isNaN(a)?void p("Invalid syntax: "+o):c("num",a)}function h(n){var e=i(!0,n);switch(e.charCodeAt(0)){case 110:return"\n";case 114:return"\r";case 116:return"	";case 98:return"\b";case 118:return"";case 102:return"\f";case 48:return"\x00";case 120:return String.fromCharCode(_(2));case 117:return String.fromCharCode(_(4));case 10:return"";default:return e}}function _(n){for(var e=0;n>0;--n){var t=parseInt(i(!0),16);isNaN(t)&&p("Invalid hex-character pattern in string"),e=e<<4|t}return e}function m(n){var e,t=D.regex_allowed,r=u("\n");return-1==r?(e=D.text.substr(D.pos),D.pos=D.text.length):(e=D.text.substring(D.pos,r),D.pos=r),D.comments_before.push(c(n,e,!0)),D.regex_allowed=t,E()}function v(){for(var n,e,t=!1,o="",a=!1;null!=(n=r());)if(t)"u"!=n&&p("Expecting UnicodeEscapeSequence -- uXXXX"),n=h(),T(n)||p("Unicode char: "+n.charCodeAt(0)+" is not valid in identifier"),o+=n,t=!1;else if("\\"==n)a=t=!0,i();else{if(!T(n))break;o+=i()}return Dt(o)&&a&&(e=o.charCodeAt(0).toString(16).toUpperCase(),o="\\u"+"0000".substr(e.length)+e+o.slice(1)),o}function g(n){function e(n){if(!r())return n;var t=n+r();return $t(t)?(i(),e(t)):n}return c("operator",e(n||i()))}function b(){switch(i(),r()){case"/":return i(),m("comment1");case"*":return i(),x()}return D.regex_allowed?$(""):g("/")}function y(){return i(),F(r().charCodeAt(0))?d("."):c("punc",".")}function A(){var n=v();return C?c("name",n):Ft(n)?c("atom",n):Dt(n)?$t(n)?c("operator",n):c("keyword",n):c("name",n)}function w(n,e){return function(t){try{return e(t)}catch(r){if(r!==Ht)throw r;p(n)}}}function E(n){if(null!=n)return $(n);if(f(),s(),t){if(a("<!--"))return o(4),m("comment3");if(a("-->")&&D.newline_before)return o(3),m("comment4")}var e=r();if(!e)return c("eof");var u=e.charCodeAt(0);switch(u){case 34:case 39:return k();case 46:return y();case 47:return b()}return F(u)?d():Nt(e)?c("punc",i()):kt(e)?g():92==u||B(u)?A():void p("Unexpected character '"+e+"'")}var D={text:n.replace(/\r\n?|[\n\u2028\u2029]/g,"\n").replace(/\uFEFF/g,""),filename:e,pos:0,tokpos:0,line:1,tokline:0,col:0,tokcol:0,newline_before:!1,regex_allowed:!1,comments_before:[]},C=!1,k=w("Unterminated string constant",function(){for(var n=i(),e="";;){var t=i(!0);if("\\"==t){var r=0,o=null;t=l(function(n){if(n>="0"&&"7">=n){if(!o)return o=n,++r;if("3">=o&&2>=r)return++r;if(o>="4"&&1>=r)return++r}return!1}),t=r>0?String.fromCharCode(parseInt(t,8)):h(!0)}else if(t==n)break;e+=t}return c("string",e)}),x=w("Unterminated multiline comment",function(){var n=D.regex_allowed,e=u("*/",!0),t=D.text.substring(D.pos,e),r=t.split("\n"),i=r.length;D.pos=e+2,D.line+=i-1,i>1?D.col=r[i-1].length:D.col+=r[i-1].length,D.col+=2;var o=D.newline_before=D.newline_before||t.indexOf("\n")>=0;return D.comments_before.push(c("comment2",t,!0)),D.regex_allowed=n,D.newline_before=o,E()}),$=w("Unterminated regular expression",function(n){for(var e,t=!1,r=!1;e=i(!0);)if(t)n+="\\"+e,t=!1;else if("["==e)r=!0,n+=e;else if("]"==e&&r)r=!1,n+=e;else{if("/"==e&&!r)break;"\\"==e?t=!0:n+=e}var o=v();return c("regexp",new RegExp(n,o))});return E.context=function(n){return n&&(D=n),D},E}function H(n,e){function t(n,e){return R(I.token,n,e)}function r(){return I.peeked||(I.peeked=I.input())}function i(){return I.prev=I.token,I.peeked?(I.token=I.peeked,I.peeked=null):I.token=I.input(),I.in_directives=I.in_directives&&("string"==I.token.type||t("punc",";")),I.token}function o(){return I.prev}function u(n,e,t,r){var i=I.input.context();N(n,i.filename,null!=e?e:i.tokline,null!=t?t:i.tokcol,null!=r?r:i.tokpos)}function s(n,e){u(e,n.line,n.col)}function f(n){null==n&&(n=I.token),s(n,"Unexpected token: "+n.type+" ("+n.value+")")}function l(n,e){return t(n,e)?i():void s(I.token,"Unexpected token "+I.token.type+" «"+I.token.value+"», expected "+n+" «"+e+"»")}function p(n){return l("punc",n)}function d(){return!e.strict&&(I.token.nlb||t("eof")||t("punc","}"))}function h(){t("punc",";")?i():d()||f()}function _(){p("(");var n=De(!0);return p(")"),n}function m(n){return function(){var e=I.token,t=n(),r=o();return t.start=e,t.end=r,t}}function v(){(t("operator","/")||t("operator","/="))&&(I.peeked=null,I.token=I.input(I.token.value.substr(1)))}function g(){var n=M(ut);a(function(e){return e.name==n.name},I.labels)&&u("Label "+n.name+" defined twice"),p(":"),I.labels.push(n);var e=U();return I.labels.pop(),e instanceof te||n.references.forEach(function(e){e instanceof Ae&&(e=e.label.start,u("Continue label `"+n.name+"` refers to non-IterationStatement.",e.line,e.col,e.pos))}),new ee({body:e,label:n})}function b(n){return new K({body:(n=De(!0),h(),n)})}function y(n){var e,t=null;d()||(t=M(ct,!0)),null!=t?(e=a(function(n){return n.name==t.name},I.labels),e||u("Undefined label "+t.name),t.thedef=e):0==I.in_loop&&u(n.TYPE+" not inside a loop or switch"),h();var r=new n({label:t});return e&&e.references.push(r),r}function A(){p("(");var n=null;return!t("punc",";")&&(n=t("keyword","var")?(i(),L(!0)):De(!0,!0),t("operator","in"))?(n instanceof Te&&n.definitions.length>1&&u("Only one variable declaration allowed in for..in loop"),i(),E(n)):w(n)}function w(n){p(";");var e=t("punc",";")?null:De(!0);p(";");var r=t("punc",")")?null:De(!0);return p(")"),new ae({init:n,condition:e,step:r,body:j(U)})}function E(n){var e=n instanceof Te?n.definitions[0].name:null,t=De(!0);return p(")"),new ue({init:n,name:e,object:t,body:j(U)})}function D(){var n=_(),e=U(),r=null;return t("keyword","else")&&(i(),r=U()),new we({condition:n,body:e,alternative:r})}function F(){p("{");for(var n=[];!t("punc","}");)t("eof")&&f(),n.push(U());return i(),n}function S(){p("{");for(var n,e=[],r=null,a=null;!t("punc","}");)t("eof")&&f(),t("keyword","case")?(a&&(a.end=o()),r=[],a=new Se({start:(n=I.token,i(),n),expression:De(!0),body:r}),e.push(a),p(":")):t("keyword","default")?(a&&(a.end=o()),r=[],a=new Fe({start:(n=I.token,i(),p(":"),n),body:r}),e.push(a)):(r||f(),r.push(U()));return a&&(a.end=o()),i(),e}function C(){var n=F(),e=null,r=null;if(t("keyword","catch")){var a=I.token;i(),p("(");var s=M(at);p(")"),e=new ke({start:a,argname:s,body:F(),end:o()})}if(t("keyword","finally")){var a=I.token;i(),r=new xe({start:a,body:F(),end:o()})}return e||r||u("Missing catch/finally blocks"),new Ce({body:n,bcatch:e,bfinally:r})}function k(n,e){for(var r=[];r.push(new Oe({start:I.token,name:M(e?tt:et),value:t("operator","=")?(i(),De(!1,n)):null,end:o()})),t("punc",",");)i();return r}function x(){var n,e=I.token;switch(e.type){case"name":case"keyword":n=O(st);break;case"num":n=new dt({start:e,end:e,value:e.value});break;case"string":n=new pt({start:e,end:e,value:e.value});break;case"regexp":n=new ht({start:e,end:e,value:e.value});break;case"atom":switch(e.value){case"false":n=new wt({start:e,end:e});break;case"true":n=new Et({start:e,end:e});break;case"null":n=new mt({start:e,end:e})}}return i(),n}function B(n,e,r){for(var o=!0,a=[];!t("punc",n)&&(o?o=!1:p(","),!e||!t("punc",n));)a.push(t("punc",",")&&r?new bt({start:I.token,end:I.token}):De(!1));return i(),a}function T(){var n=I.token;switch(i(),n.type){case"num":case"string":case"name":case"operator":case"keyword":case"atom":return n.value;default:f()}}function $(){var n=I.token;switch(i(),n.type){case"name":case"operator":case"keyword":case"atom":return n.value;default:f()}}function O(n){var e=I.token.value;return new("this"==e?ft:n)({name:String(e),start:I.token,end:I.token})}function M(n,e){if(!t("name"))return e||u("Name expected"),null;var r=O(n);return i(),r}function H(n,e,t){return"++"!=e&&"--"!=e||P(t)||u("Invalid use of "+e+" operator"),new n({operator:e,expression:t})}function z(n){return _e(le(!0),0,n)}function P(n){return e.strict?n instanceof ft?!1:n instanceof qe||n instanceof Ze:!0}function j(n){++I.in_loop;var e=n();return--I.in_loop,e}e=c(e,{strict:!1,filename:null,toplevel:null,expression:!1,html5_comments:!0});var I={input:"string"==typeof n?q(n,e.filename,e.html5_comments):n,token:null,prev:null,peeked:null,in_function:0,in_directives:!0,in_loop:0,labels:[]};I.token=i();var U=m(function(){var n;switch(v(),I.token.type){case"string":var e=I.in_directives,a=b();return e&&a.body instanceof pt&&!t("punc",",")?new G({value:a.body.value}):a;case"num":case"regexp":case"operator":case"atom":return b();case"name":return R(r(),"punc",":")?g():b();case"punc":switch(I.token.value){case"{":return new Z({start:I.token,body:F(),end:o()});case"[":case"(":return b();case";":return i(),new Q;default:f()}case"keyword":switch(n=I.token.value,i(),n){case"break":return y(ye);case"continue":return y(Ae);case"debugger":return h(),new X;case"do":return new ie({body:j(U),condition:(l("keyword","while"),n=_(),h(),n)});case"while":return new oe({condition:_(),body:j(U)});case"for":return A();case"function":return V(he);case"if":return D();case"return":return 0==I.in_function&&u("'return' outside of function"),new ve({value:t("punc",";")?(i(),null):d()?null:(n=De(!0),h(),n)});case"switch":return new Ee({expression:_(),body:j(S)});case"throw":return I.token.nlb&&u("Illegal newline after 'throw'"),new ge({value:(n=De(!0),h(),n)});case"try":return C();case"var":return n=L(),h(),n;case"const":return n=W(),h(),n;case"with":return new se({expression:_(),body:U()});default:f()}}}),V=function(n){var e=n===he,r=t("name")?M(e?it:ot):null;return e&&!r&&f(),p("("),new n({name:r,argnames:function(n,e){for(;!t("punc",")");)n?n=!1:p(","),e.push(M(rt));return i(),e}(!0,[]),body:function(n,e){++I.in_function,I.in_directives=!0,I.in_loop=0,I.labels=[];var t=F();return--I.in_function,I.in_loop=n,I.labels=e,t}(I.in_loop,I.labels)})},L=function(n){return new Te({start:o(),definitions:k(n,!1),end:o()})},W=function(){return new $e({start:o(),definitions:k(!1,!0),end:o()})},Y=function(){var n=I.token;l("operator","new");var e,r=J(!1);return t("punc","(")?(i(),e=B(")")):e=[],ce(new Ne({start:n,expression:r,args:e,end:o()}),!0)},J=function(n){if(t("operator","new"))return Y();var e=I.token;if(t("punc")){switch(e.value){case"(":i();var r=De(!0);return r.start=e,r.end=I.token,p(")"),ce(r,n);case"[":return ce(ne(),n);case"{":return ce(re(),n)}f()}if(t("keyword","function")){i();var a=V(de);return a.start=e,a.end=o(),ce(a,n)}return Vt[I.token.type]?ce(x(),n):void f()},ne=m(function(){return p("["),new We({elements:B("]",!e.strict,!0)})}),re=m(function(){p("{");for(var n=!0,r=[];!t("punc","}")&&(n?n=!1:p(","),e.strict||!t("punc","}"));){var a=I.token,u=a.type,s=T();if("name"==u&&!t("punc",":")){if("get"==s){r.push(new Je({start:a,key:x(),value:V(pe),end:o()}));continue}if("set"==s){r.push(new Ke({start:a,key:x(),value:V(pe),end:o()}));continue}}p(":"),r.push(new Ge({start:a,key:s,value:De(!1),end:o()}))}return i(),new Ye({properties:r})}),ce=function(n,e){var r=n.start;if(t("punc","."))return i(),ce(new He({start:r,expression:n,property:$(),end:o()}),e);if(t("punc","[")){i();var a=De(!0);return p("]"),ce(new ze({start:r,expression:n,property:a,end:o()}),e)}return e&&t("punc","(")?(i(),ce(new Me({start:r,expression:n,args:B(")"),end:o()}),!0)):n},le=function(n){var e=I.token;if(t("operator")&&zt(e.value)){i(),v();var r=H(je,e.value,le(n));return r.start=e,r.end=o(),r}for(var a=J(n);t("operator")&&Pt(I.token.value)&&!I.token.nlb;)a=H(Ie,I.token.value,a),a.start=e,a.end=I.token,i();return a},_e=function(n,e,r){var o=t("operator")?I.token.value:null;"in"==o&&r&&(o=null);var a=null!=o?It[o]:null;if(null!=a&&a>e){i();var u=_e(le(!0),a,r);return _e(new Ue({start:n.start,left:n,operator:o,right:u,end:u.end}),e,r)}return n},me=function(n){var e=I.token,r=z(n);if(t("operator","?")){i();var a=De(!1);return p(":"),new Ve({start:e,condition:r,consequent:a,alternative:De(!1,n),end:o()})}return r},be=function(n){var e=I.token,r=me(n),a=I.token.value;if(t("operator")&&jt(a)){if(P(r))return i(),new Le({start:e,left:r,operator:a,right:be(n),end:o()});u("Invalid assignment")}return r},De=function(n,e){var o=I.token,a=be(e);return n&&t("punc",",")?(i(),new Re({start:o,car:a,cdr:De(!0,e),end:r()})):a};return e.expression?De(!0):function(){for(var n=I.token,r=[];!t("eof");)r.push(U());var i=o(),a=e.toplevel;return a?(a.body=a.body.concat(r),a.end=i):a=new fe({start:n,body:r,end:i}),a}()}function z(n,e){E.call(this),this.before=n,this.after=e}function P(n,e,t){this.name=t.name,this.orig=[t],this.scope=n,this.references=[],this.global=!1,this.mangled_name=null,this.undeclared=!1,this.constant=!1,this.index=e}function j(n){function e(n,e){return n.replace(/[\u0080-\uffff]/g,function(n){var t=n.charCodeAt(0).toString(16);if(t.length<=2&&!e){for(;t.length<2;)t="0"+t;return"\\x"+t}for(;t.length<4;)t="0"+t;return"\\u"+t})}function t(t){var r=0,i=0;return t=t.replace(/[\\\b\f\n\r\t\x22\x27\u2028\u2029\0]/g,function(n){switch(n){case"\\":return"\\\\";case"\b":return"\\b";case"\f":return"\\f";case"\n":return"\\n";case"\r":return"\\r";case"\u2028":return"\\u2028";case"\u2029":return"\\u2029";case'"':return++r,'"';case"'":return++i,"'";case"\x00":return"\\x00"}return n}),n.ascii_only&&(t=e(t)),r>i?"'"+t.replace(/\x27/g,"\\'")+"'":'"'+t.replace(/\x22/g,'\\"')+'"'}function r(e){var r=t(e);return n.inline_script&&(r=r.replace(/<\x2fscript([>\/\t\n\f\r ])/gi,"<\\/script$1")),r}function i(t){return t=t.toString(),n.ascii_only&&(t=e(t,!0)),t}function o(e){return u(" ",n.indent_start+A-e*n.indent_level)}function a(){return k.charAt(k.length-1)}function s(){n.max_line_len&&w>n.max_line_len&&f("\n")}function f(e){e=String(e);var t=e.charAt(0);if(C&&(t&&!(";}".indexOf(t)<0)||/[;]$/.test(k)||(n.semicolons||x(t)?(F+=";",w++,D++):(F+="\n",D++,E++,w=0),n.beautify||(S=!1)),C=!1,s()),!n.beautify&&n.preserve_line&&q[q.length-1])for(var r=q[q.length-1].start.line;r>E;)F+="\n",D++,E++,w=0,S=!1;if(S){var i=a();(T(i)&&(T(t)||"\\"==t)||/^[\+\-\/]$/.test(t)&&t==i)&&(F+=" ",w++,D++),S=!1}var o=e.split(/\r?\n/),u=o.length-1;E+=u,0==u?w+=o[u].length:w=o[u].length,D+=e.length,k=e,F+=e}function p(){C=!1,f(";")}function d(){return A+n.indent_level}function h(n){var e;return f("{"),M(),O(d(),function(){e=n()}),$(),f("}"),e}function _(n){f("(");var e=n();return f(")"),e}function m(n){f("[");var e=n();return f("]"),e}function v(){f(","),B()}function b(){f(":"),n.space_colon&&B()}function y(){return F}n=c(n,{indent_start:0,indent_level:4,quote_keys:!1,space_colon:!0,ascii_only:!1,unescape_regexps:!1,inline_script:!1,width:80,max_line_len:32e3,beautify:!1,source_map:null,bracketize:!1,semicolons:!0,comments:!1,preserve_line:!1,screw_ie8:!1,preamble:null},!0);var A=0,w=0,E=1,D=0,F="",S=!1,C=!1,k=null,x=g("( [ + * / - , ."),B=n.beautify?function(){f(" ")}:function(){S=!0},$=n.beautify?function(e){n.beautify&&f(o(e?.5:0))}:l,O=n.beautify?function(n,e){n===!0&&(n=d());var t=A;A=n;var r=e();return A=t,r}:function(n,e){return e()},M=n.beautify?function(){f("\n")}:l,N=n.beautify?function(){f(";")}:function(){C=!0},R=n.source_map?function(e,t){try{e&&n.source_map.add(e.file||"?",E,w,e.line,e.col,t||"name"!=e.type?t:e.value)}catch(r){W.warn("Couldn't figure out mapping for {file}:{line},{col} → {cline},{ccol} [{name}]",{file:e.file,line:e.line,col:e.col,cline:E,ccol:w,name:t||""})}}:l;n.preamble&&f(n.preamble.replace(/\r\n?|[\n\u2028\u2029]|\s*$/g,"\n"));var q=[];return{get:y,toString:y,indent:$,indentation:function(){return A},current_width:function(){return w-A},should_break:function(){return n.width&&this.current_width()>=n.width},newline:M,print:f,space:B,comma:v,colon:b,last:function(){return k},semicolon:N,force_semicolon:p,to_ascii:e,print_name:function(n){f(i(n))},print_string:function(n){f(r(n))},next_indent:d,with_indent:O,with_block:h,with_parens:_,with_square:m,add_mapping:R,option:function(e){return n[e]},line:function(){return E},col:function(){return w},pos:function(){return D},push_node:function(n){q.push(n)},pop_node:function(){return q.pop()},stack:function(){return q},parent:function(n){return q[q.length-2-(n||0)]}}}function I(n,e){return this instanceof I?(z.call(this,this.before,this.after),void(this.options=c(n,{sequences:!e,properties:!e,dead_code:!e,drop_debugger:!e,unsafe:!1,unsafe_comps:!1,conditionals:!e,comparisons:!e,evaluate:!e,booleans:!e,loops:!e,unused:!e,hoist_funs:!e,keep_fargs:!1,hoist_vars:!1,if_return:!e,join_vars:!e,cascade:!e,side_effects:!e,pure_getters:!1,pure_funcs:null,negate_iife:!e,screw_ie8:!1,drop_console:!1,angular:!1,warnings:!0,global_defs:{}},!0))):new I(n,e)}function U(n){function e(e,i,o,a,u,s){if(r){var c=r.originalPositionFor({line:a,column:u});if(null===c.source)return;e=c.source,a=c.line,u=c.column,s=c.name}t.addMapping({generated:{line:i+n.dest_line_diff,column:o},original:{line:a+n.orig_line_diff,column:u},source:e,name:s})}n=c(n,{file:null,root:null,orig:null,orig_line_diff:0,dest_line_diff:0});var t=new MOZ_SourceMap.SourceMapGenerator({file:n.file,sourceRoot:n.root}),r=n.orig&&new MOZ_SourceMap.SourceMapConsumer(n.orig);return{add:e,get:function(){return t},toString:function(){return t.toString()}}}e.UglifyJS=n,s.prototype=Object.create(Error.prototype),s.prototype.constructor=s,s.croak=function(n,e){throw new s(n,e)};var V=function(){function n(n,o,a){function u(){var u=o(n[s],s),l=u instanceof r;return l&&(u=u.v),u instanceof e?(u=u.v,u instanceof t?f.push.apply(f,a?u.v.slice().reverse():u.v):f.push(u)):u!==i&&(u instanceof t?c.push.apply(c,a?u.v.slice().reverse():u.v):c.push(u)),l}var s,c=[],f=[];if(n instanceof Array)if(a){for(s=n.length;--s>=0&&!u(););c.reverse(),f.reverse()}else for(s=0;s<n.length&&!u();++s);else for(s in n)if(n.hasOwnProperty(s)&&u())break;return f.concat(c)}function e(n){this.v=n}function t(n){this.v=n}function r(n){this.v=n}n.at_top=function(n){return new e(n)},n.splice=function(n){return new t(n)},n.last=function(n){return new r(n)};var i=n.skip={};return n}();y.prototype={set:function(n,e){return this.has(n)||++this._size,this._values["$"+n]=e,this},add:function(n,e){return this.has(n)?this.get(n).push(e):this.set(n,[e]),this},get:function(n){return this._values["$"+n]},del:function(n){return this.has(n)&&(--this._size,delete this._values["$"+n]),this},has:function(n){return"$"+n in this._values},each:function(n){for(var e in this._values)n(this._values[e],e.substr(1))},size:function(){return this._size},map:function(n){var e=[];for(var t in this._values)e.push(n(this._values[t],t.substr(1)));return e}};var L=A("Token","type value line col pos endpos nlb comments_before file",{},null),W=A("Node","start end",{clone:function(){return new this.CTOR(this)},$documentation:"Base class of all AST nodes",$propdoc:{start:"[AST_Token] The first token of this node",end:"[AST_Token] The last token of this node"},_walk:function(n){return n._visit(this)},walk:function(n){return this._walk(n)}},null);W.warn_function=null,W.warn=function(n,e){W.warn_function&&W.warn_function(d(n,e))};var Y=A("Statement",null,{$documentation:"Base class of all statements"}),X=A("Debugger",null,{$documentation:"Represents a debugger statement"},Y),G=A("Directive","value scope",{$documentation:'Represents a directive, like "use strict";',$propdoc:{value:"[string] The value of this directive as a plain string (it's not an AST_String!)",scope:"[AST_Scope/S] The scope that this directive affects"}},Y),K=A("SimpleStatement","body",{$documentation:"A statement consisting of an expression, i.e. a = 1 + 2",$propdoc:{body:"[AST_Node] an expression node (should not be instanceof AST_Statement)"},_walk:function(n){return n._visit(this,function(){this.body._walk(n)})}},Y),J=A("Block","body",{$documentation:"A body of statements (usually bracketed)",$propdoc:{body:"[AST_Statement*] an array of statements"},_walk:function(n){return n._visit(this,function(){w(this,n)})}},Y),Z=A("BlockStatement",null,{$documentation:"A block statement"},J),Q=A("EmptyStatement",null,{$documentation:"The empty statement (empty block or simply a semicolon)",_walk:function(n){return n._visit(this)}},Y),ne=A("StatementWithBody","body",{$documentation:"Base class for all statements that contain one nested body: `For`, `ForIn`, `Do`, `While`, `With`",$propdoc:{body:"[AST_Statement] the body; this should always be present, even if it's an AST_EmptyStatement"},_walk:function(n){return n._visit(this,function(){this.body._walk(n)})}},Y),ee=A("LabeledStatement","label",{$documentation:"Statement with a label",$propdoc:{label:"[AST_Label] a label definition"},_walk:function(n){return n._visit(this,function(){this.label._walk(n),this.body._walk(n)})}},ne),te=A("IterationStatement",null,{$documentation:"Internal class.  All loops inherit from it."},ne),re=A("DWLoop","condition",{$documentation:"Base class for do/while statements",$propdoc:{condition:"[AST_Node] the loop condition.  Should not be instanceof AST_Statement"},_walk:function(n){return n._visit(this,function(){this.condition._walk(n),this.body._walk(n)})}},te),ie=A("Do",null,{$documentation:"A `do` statement"},re),oe=A("While",null,{$documentation:"A `while` statement"},re),ae=A("For","init condition step",{$documentation:"A `for` statement",$propdoc:{init:"[AST_Node?] the `for` initialization code, or null if empty",condition:"[AST_Node?] the `for` termination clause, or null if empty",step:"[AST_Node?] the `for` update clause, or null if empty"},_walk:function(n){return n._visit(this,function(){this.init&&this.init._walk(n),this.condition&&this.condition._walk(n),this.step&&this.step._walk(n),this.body._walk(n)})}},te),ue=A("ForIn","init name object",{$documentation:"A `for ... in` statement",$propdoc:{init:"[AST_Node] the `for/in` initialization code",name:"[AST_SymbolRef?] the loop variable, only if `init` is AST_Var",object:"[AST_Node] the object that we're looping through"},_walk:function(n){return n._visit(this,function(){this.init._walk(n),this.object._walk(n),this.body._walk(n)})}},te),se=A("With","expression",{$documentation:"A `with` statement",$propdoc:{expression:"[AST_Node] the `with` expression"},_walk:function(n){return n._visit(this,function(){this.expression._walk(n),this.body._walk(n)})}},ne),ce=A("Scope","directives variables functions uses_with uses_eval parent_scope enclosed cname",{$documentation:"Base class for all statements introducing a lexical scope",$propdoc:{directives:"[string*/S] an array of directives declared in this scope",variables:"[Object/S] a map of name -> SymbolDef for all variables/functions defined in this scope",functions:"[Object/S] like `variables`, but only lists function declarations",uses_with:"[boolean/S] tells whether this scope uses the `with` statement",uses_eval:"[boolean/S] tells whether this scope contains a direct call to the global `eval`",parent_scope:"[AST_Scope?/S] link to the parent scope",enclosed:"[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes",cname:"[integer/S] current index for mangling variables (used internally by the mangler)"}},J),fe=A("Toplevel","globals",{$documentation:"The toplevel scope",$propdoc:{globals:"[Object/S] a map of name -> SymbolDef for all undeclared names"},wrap_enclose:function(n){var e=this,t=[],r=[];n.forEach(function(n){var e=n.lastIndexOf(":");t.push(n.substr(0,e)),r.push(n.substr(e+1))});var i="(function("+r.join(",")+"){ '$ORIG'; })("+t.join(",")+")";return i=H(i),i=i.transform(new z(function(n){return n instanceof G&&"$ORIG"==n.value?V.splice(e.body):void 0}))},wrap_commonjs:function(n,e){var t=this,r=[];e&&(t.figure_out_scope(),t.walk(new E(function(n){n instanceof nt&&n.definition().global&&(a(function(e){return e.name==n.name},r)||r.push(n))})));var i="(function(exports, global){ global['"+n+"'] = exports; '$ORIG'; '$EXPORTS'; }({}, (function(){return this}())))";return i=H(i),i=i.transform(new z(function(n){if(n instanceof K&&(n=n.body,n instanceof pt))switch(n.getValue()){case"$ORIG":return V.splice(t.body);case"$EXPORTS":var e=[];return r.forEach(function(n){e.push(new K({body:new Le({left:new ze({expression:new st({name:"exports"}),property:new pt({value:n.name})}),operator:"=",right:new st(n)})}))}),V.splice(e)}}))}},ce),le=A("Lambda","name argnames uses_arguments",{$documentation:"Base class for functions",$propdoc:{name:"[AST_SymbolDeclaration?] the name of this function",argnames:"[AST_SymbolFunarg*] array of function arguments",uses_arguments:"[boolean/S] tells whether this function accesses the arguments array"},_walk:function(n){return n._visit(this,function(){this.name&&this.name._walk(n),this.argnames.forEach(function(e){e._walk(n)}),w(this,n)})}},ce),pe=A("Accessor",null,{$documentation:"A setter/getter function.  The `name` property is always null."},le),de=A("Function",null,{$documentation:"A function expression"},le),he=A("Defun",null,{$documentation:"A function definition"},le),_e=A("Jump",null,{$documentation:"Base class for “jumps” (for now that's `return`, `throw`, `break` and `continue`)"},Y),me=A("Exit","value",{$documentation:"Base class for “exits” (`return` and `throw`)",$propdoc:{value:"[AST_Node?] the value returned or thrown by this statement; could be null for AST_Return"},_walk:function(n){return n._visit(this,this.value&&function(){this.value._walk(n)})}},_e),ve=A("Return",null,{$documentation:"A `return` statement"},me),ge=A("Throw",null,{$documentation:"A `throw` statement"},me),be=A("LoopControl","label",{$documentation:"Base class for loop control statements (`break` and `continue`)",$propdoc:{label:"[AST_LabelRef?] the label, or null if none"},_walk:function(n){return n._visit(this,this.label&&function(){this.label._walk(n)})}},_e),ye=A("Break",null,{$documentation:"A `break` statement"},be),Ae=A("Continue",null,{$documentation:"A `continue` statement"},be),we=A("If","condition alternative",{$documentation:"A `if` statement",$propdoc:{condition:"[AST_Node] the `if` condition",alternative:"[AST_Statement?] the `else` part, or null if not present"},_walk:function(n){return n._visit(this,function(){this.condition._walk(n),this.body._walk(n),this.alternative&&this.alternative._walk(n)})}},ne),Ee=A("Switch","expression",{$documentation:"A `switch` statement",$propdoc:{expression:"[AST_Node] the `switch` “discriminant”"},_walk:function(n){return n._visit(this,function(){this.expression._walk(n),w(this,n)})}},J),De=A("SwitchBranch",null,{$documentation:"Base class for `switch` branches"},J),Fe=A("Default",null,{$documentation:"A `default` switch branch"},De),Se=A("Case","expression",{$documentation:"A `case` switch branch",$propdoc:{expression:"[AST_Node] the `case` expression"},_walk:function(n){return n._visit(this,function(){this.expression._walk(n),w(this,n)})}},De),Ce=A("Try","bcatch bfinally",{$documentation:"A `try` statement",$propdoc:{bcatch:"[AST_Catch?] the catch block, or null if not present",bfinally:"[AST_Finally?] the finally block, or null if not present"},_walk:function(n){return n._visit(this,function(){w(this,n),this.bcatch&&this.bcatch._walk(n),this.bfinally&&this.bfinally._walk(n)})}},J),ke=A("Catch","argname",{$documentation:"A `catch` node; only makes sense as part of a `try` statement",$propdoc:{argname:"[AST_SymbolCatch] symbol for the exception"},_walk:function(n){return n._visit(this,function(){this.argname._walk(n),w(this,n)
+})}},J),xe=A("Finally",null,{$documentation:"A `finally` node; only makes sense as part of a `try` statement"},J),Be=A("Definitions","definitions",{$documentation:"Base class for `var` or `const` nodes (variable declarations/initializations)",$propdoc:{definitions:"[AST_VarDef*] array of variable definitions"},_walk:function(n){return n._visit(this,function(){this.definitions.forEach(function(e){e._walk(n)})})}},Y),Te=A("Var",null,{$documentation:"A `var` statement"},Be),$e=A("Const",null,{$documentation:"A `const` statement"},Be),Oe=A("VarDef","name value",{$documentation:"A variable declaration; only appears in a AST_Definitions node",$propdoc:{name:"[AST_SymbolVar|AST_SymbolConst] name of the variable",value:"[AST_Node?] initializer, or null of there's no initializer"},_walk:function(n){return n._visit(this,function(){this.name._walk(n),this.value&&this.value._walk(n)})}}),Me=A("Call","expression args",{$documentation:"A function call expression",$propdoc:{expression:"[AST_Node] expression to invoke as function",args:"[AST_Node*] array of arguments"},_walk:function(n){return n._visit(this,function(){this.expression._walk(n),this.args.forEach(function(e){e._walk(n)})})}}),Ne=A("New",null,{$documentation:"An object instantiation.  Derives from a function call since it has exactly the same properties"},Me),Re=A("Seq","car cdr",{$documentation:"A sequence expression (two comma-separated expressions)",$propdoc:{car:"[AST_Node] first element in sequence",cdr:"[AST_Node] second element in sequence"},$cons:function(n,e){var t=new Re(n);return t.car=n,t.cdr=e,t},$from_array:function(n){if(0==n.length)return null;if(1==n.length)return n[0].clone();for(var e=null,t=n.length;--t>=0;)e=Re.cons(n[t],e);for(var r=e;r;){if(r.cdr&&!r.cdr.cdr){r.cdr=r.cdr.car;break}r=r.cdr}return e},to_array:function(){for(var n=this,e=[];n;){if(e.push(n.car),n.cdr&&!(n.cdr instanceof Re)){e.push(n.cdr);break}n=n.cdr}return e},add:function(n){for(var e=this;e;){if(!(e.cdr instanceof Re)){var t=Re.cons(e.cdr,n);return e.cdr=t}e=e.cdr}},_walk:function(n){return n._visit(this,function(){this.car._walk(n),this.cdr&&this.cdr._walk(n)})}}),qe=A("PropAccess","expression property",{$documentation:'Base class for property access expressions, i.e. `a.foo` or `a["foo"]`',$propdoc:{expression:"[AST_Node] the “container” expression",property:"[AST_Node|string] the property to access.  For AST_Dot this is always a plain string, while for AST_Sub it's an arbitrary AST_Node"}}),He=A("Dot",null,{$documentation:"A dotted property access expression",_walk:function(n){return n._visit(this,function(){this.expression._walk(n)})}},qe),ze=A("Sub",null,{$documentation:'Index-style property access, i.e. `a["foo"]`',_walk:function(n){return n._visit(this,function(){this.expression._walk(n),this.property._walk(n)})}},qe),Pe=A("Unary","operator expression",{$documentation:"Base class for unary expressions",$propdoc:{operator:"[string] the operator",expression:"[AST_Node] expression that this unary operator applies to"},_walk:function(n){return n._visit(this,function(){this.expression._walk(n)})}}),je=A("UnaryPrefix",null,{$documentation:"Unary prefix expression, i.e. `typeof i` or `++i`"},Pe),Ie=A("UnaryPostfix",null,{$documentation:"Unary postfix expression, i.e. `i++`"},Pe),Ue=A("Binary","left operator right",{$documentation:"Binary expression, i.e. `a + b`",$propdoc:{left:"[AST_Node] left-hand side expression",operator:"[string] the operator",right:"[AST_Node] right-hand side expression"},_walk:function(n){return n._visit(this,function(){this.left._walk(n),this.right._walk(n)})}}),Ve=A("Conditional","condition consequent alternative",{$documentation:"Conditional expression using the ternary operator, i.e. `a ? b : c`",$propdoc:{condition:"[AST_Node]",consequent:"[AST_Node]",alternative:"[AST_Node]"},_walk:function(n){return n._visit(this,function(){this.condition._walk(n),this.consequent._walk(n),this.alternative._walk(n)})}}),Le=A("Assign",null,{$documentation:"An assignment expression — `a = b + 5`"},Ue),We=A("Array","elements",{$documentation:"An array literal",$propdoc:{elements:"[AST_Node*] array of elements"},_walk:function(n){return n._visit(this,function(){this.elements.forEach(function(e){e._walk(n)})})}}),Ye=A("Object","properties",{$documentation:"An object literal",$propdoc:{properties:"[AST_ObjectProperty*] array of properties"},_walk:function(n){return n._visit(this,function(){this.properties.forEach(function(e){e._walk(n)})})}}),Xe=A("ObjectProperty","key value",{$documentation:"Base class for literal object properties",$propdoc:{key:"[string] the property name converted to a string for ObjectKeyVal.  For setters and getters this is an arbitrary AST_Node.",value:"[AST_Node] property value.  For setters and getters this is an AST_Function."},_walk:function(n){return n._visit(this,function(){this.value._walk(n)})}}),Ge=A("ObjectKeyVal",null,{$documentation:"A key: value object property"},Xe),Ke=A("ObjectSetter",null,{$documentation:"An object setter property"},Xe),Je=A("ObjectGetter",null,{$documentation:"An object getter property"},Xe),Ze=A("Symbol","scope name thedef",{$propdoc:{name:"[string] name of this symbol",scope:"[AST_Scope/S] the current scope (not necessarily the definition scope)",thedef:"[SymbolDef/S] the definition of this symbol"},$documentation:"Base class for all symbols"}),Qe=A("SymbolAccessor",null,{$documentation:"The name of a property accessor (setter/getter function)"},Ze),nt=A("SymbolDeclaration","init",{$documentation:"A declaration symbol (symbol in var/const, function name or argument, symbol in catch)",$propdoc:{init:"[AST_Node*/S] array of initializers for this declaration."}},Ze),et=A("SymbolVar",null,{$documentation:"Symbol defining a variable"},nt),tt=A("SymbolConst",null,{$documentation:"A constant declaration"},nt),rt=A("SymbolFunarg",null,{$documentation:"Symbol naming a function argument"},et),it=A("SymbolDefun",null,{$documentation:"Symbol defining a function"},nt),ot=A("SymbolLambda",null,{$documentation:"Symbol naming a function expression"},nt),at=A("SymbolCatch",null,{$documentation:"Symbol naming the exception in catch"},nt),ut=A("Label","references",{$documentation:"Symbol naming a label (declaration)",$propdoc:{references:"[AST_LoopControl*] a list of nodes referring to this label"},initialize:function(){this.references=[],this.thedef=this}},Ze),st=A("SymbolRef",null,{$documentation:"Reference to some symbol (not definition/declaration)"},Ze),ct=A("LabelRef",null,{$documentation:"Reference to a label symbol"},Ze),ft=A("This",null,{$documentation:"The `this` symbol"},Ze),lt=A("Constant",null,{$documentation:"Base class for all constants",getValue:function(){return this.value}}),pt=A("String","value",{$documentation:"A string literal",$propdoc:{value:"[string] the contents of this string"}},lt),dt=A("Number","value",{$documentation:"A number literal",$propdoc:{value:"[number] the numeric value"}},lt),ht=A("RegExp","value",{$documentation:"A regexp literal",$propdoc:{value:"[RegExp] the actual regexp"}},lt),_t=A("Atom",null,{$documentation:"Base class for atoms"},lt),mt=A("Null",null,{$documentation:"The `null` atom",value:null},_t),vt=A("NaN",null,{$documentation:"The impossible value",value:0/0},_t),gt=A("Undefined",null,{$documentation:"The `undefined` value",value:void 0},_t),bt=A("Hole",null,{$documentation:"A hole in an array",value:void 0},_t),yt=A("Infinity",null,{$documentation:"The `Infinity` value",value:1/0},_t),At=A("Boolean",null,{$documentation:"Base class for booleans"},_t),wt=A("False",null,{$documentation:"The `false` atom",value:!1},At),Et=A("True",null,{$documentation:"The `true` atom",value:!0},At);E.prototype={_visit:function(n,e){this.stack.push(n);var t=this.visit(n,e?function(){e.call(n)}:l);return!t&&e&&e.call(n),this.stack.pop(),t},parent:function(n){return this.stack[this.stack.length-2-(n||0)]},push:function(n){this.stack.push(n)},pop:function(){return this.stack.pop()},self:function(){return this.stack[this.stack.length-1]},find_parent:function(n){for(var e=this.stack,t=e.length;--t>=0;){var r=e[t];if(r instanceof n)return r}},has_directive:function(n){return this.find_parent(ce).has_directive(n)},in_boolean_context:function(){for(var n=this.stack,e=n.length,t=n[--e];e>0;){var r=n[--e];if(r instanceof we&&r.condition===t||r instanceof Ve&&r.condition===t||r instanceof re&&r.condition===t||r instanceof ae&&r.condition===t||r instanceof je&&"!"==r.operator&&r.expression===t)return!0;if(!(r instanceof Ue)||"&&"!=r.operator&&"||"!=r.operator)return!1;t=r}},loopcontrol_target:function(n){var e=this.stack;if(n)for(var t=e.length;--t>=0;){var r=e[t];if(r instanceof ee&&r.label.name==n.name)return r.body}else for(var t=e.length;--t>=0;){var r=e[t];if(r instanceof Ee||r instanceof te)return r}}};var Dt="break case catch const continue debugger default delete do else finally for function if in instanceof new return switch throw try typeof var void while with",Ft="false null true",St="abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized this throws transient volatile yield "+Ft+" "+Dt,Ct="return new delete throw else case";Dt=g(Dt),St=g(St),Ct=g(Ct),Ft=g(Ft);var kt=g(i("+-*&%=<>!?|~^")),xt=/^0x[0-9a-f]+$/i,Bt=/^0[0-7]+$/,Tt=/^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i,$t=g(["in","instanceof","typeof","new","void","delete","++","--","+","-","!","~","&","|","^","*","/","%",">>","<<",">>>","<",">","<=",">=","==","===","!=","!==","?","=","+=","-=","/=","*=","%=",">>=","<<=",">>>=","|=","^=","&=","&&","||"]),Ot=g(i("  \n\r	\f​᠎              ")),Mt=g(i("[{(,.;:")),Nt=g(i("[]{}(),;:")),Rt=g(i("gmsiy")),qt={letter:new RegExp("[\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u0523\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0621-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971\\u0972\\u097B-\\u097F\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D\\u0C58\\u0C59\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D28\\u0D2A-\\u0D39\\u0D3D\\u0D60\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC\\u0EDD\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8B\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10D0-\\u10FA\\u10FC\\u1100-\\u1159\\u115F-\\u11A2\\u11A8-\\u11F9\\u1200-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u1676\\u1681-\\u169A\\u16A0-\\u16EA\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u1900-\\u191C\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19A9\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u2094\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183\\u2184\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2C6F\\u2C71-\\u2C7D\\u2C80-\\u2CE4\\u2D00-\\u2D25\\u2D30-\\u2D65\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005\\u3006\\u3031-\\u3035\\u303B\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31B7\\u31F0-\\u31FF\\u3400\\u4DB5\\u4E00\\u9FC3\\uA000-\\uA48C\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA65F\\uA662-\\uA66E\\uA67F-\\uA697\\uA717-\\uA71F\\uA722-\\uA788\\uA78B\\uA78C\\uA7FB-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA90A-\\uA925\\uA930-\\uA946\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAC00\\uD7A3\\uF900-\\uFA2D\\uFA30-\\uFA6A\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC]"),non_spacing_mark:new RegExp("[\\u0300-\\u036F\\u0483-\\u0487\\u0591-\\u05BD\\u05BF\\u05C1\\u05C2\\u05C4\\u05C5\\u05C7\\u0610-\\u061A\\u064B-\\u065E\\u0670\\u06D6-\\u06DC\\u06DF-\\u06E4\\u06E7\\u06E8\\u06EA-\\u06ED\\u0711\\u0730-\\u074A\\u07A6-\\u07B0\\u07EB-\\u07F3\\u0816-\\u0819\\u081B-\\u0823\\u0825-\\u0827\\u0829-\\u082D\\u0900-\\u0902\\u093C\\u0941-\\u0948\\u094D\\u0951-\\u0955\\u0962\\u0963\\u0981\\u09BC\\u09C1-\\u09C4\\u09CD\\u09E2\\u09E3\\u0A01\\u0A02\\u0A3C\\u0A41\\u0A42\\u0A47\\u0A48\\u0A4B-\\u0A4D\\u0A51\\u0A70\\u0A71\\u0A75\\u0A81\\u0A82\\u0ABC\\u0AC1-\\u0AC5\\u0AC7\\u0AC8\\u0ACD\\u0AE2\\u0AE3\\u0B01\\u0B3C\\u0B3F\\u0B41-\\u0B44\\u0B4D\\u0B56\\u0B62\\u0B63\\u0B82\\u0BC0\\u0BCD\\u0C3E-\\u0C40\\u0C46-\\u0C48\\u0C4A-\\u0C4D\\u0C55\\u0C56\\u0C62\\u0C63\\u0CBC\\u0CBF\\u0CC6\\u0CCC\\u0CCD\\u0CE2\\u0CE3\\u0D41-\\u0D44\\u0D4D\\u0D62\\u0D63\\u0DCA\\u0DD2-\\u0DD4\\u0DD6\\u0E31\\u0E34-\\u0E3A\\u0E47-\\u0E4E\\u0EB1\\u0EB4-\\u0EB9\\u0EBB\\u0EBC\\u0EC8-\\u0ECD\\u0F18\\u0F19\\u0F35\\u0F37\\u0F39\\u0F71-\\u0F7E\\u0F80-\\u0F84\\u0F86\\u0F87\\u0F90-\\u0F97\\u0F99-\\u0FBC\\u0FC6\\u102D-\\u1030\\u1032-\\u1037\\u1039\\u103A\\u103D\\u103E\\u1058\\u1059\\u105E-\\u1060\\u1071-\\u1074\\u1082\\u1085\\u1086\\u108D\\u109D\\u135F\\u1712-\\u1714\\u1732-\\u1734\\u1752\\u1753\\u1772\\u1773\\u17B7-\\u17BD\\u17C6\\u17C9-\\u17D3\\u17DD\\u180B-\\u180D\\u18A9\\u1920-\\u1922\\u1927\\u1928\\u1932\\u1939-\\u193B\\u1A17\\u1A18\\u1A56\\u1A58-\\u1A5E\\u1A60\\u1A62\\u1A65-\\u1A6C\\u1A73-\\u1A7C\\u1A7F\\u1B00-\\u1B03\\u1B34\\u1B36-\\u1B3A\\u1B3C\\u1B42\\u1B6B-\\u1B73\\u1B80\\u1B81\\u1BA2-\\u1BA5\\u1BA8\\u1BA9\\u1C2C-\\u1C33\\u1C36\\u1C37\\u1CD0-\\u1CD2\\u1CD4-\\u1CE0\\u1CE2-\\u1CE8\\u1CED\\u1DC0-\\u1DE6\\u1DFD-\\u1DFF\\u20D0-\\u20DC\\u20E1\\u20E5-\\u20F0\\u2CEF-\\u2CF1\\u2DE0-\\u2DFF\\u302A-\\u302F\\u3099\\u309A\\uA66F\\uA67C\\uA67D\\uA6F0\\uA6F1\\uA802\\uA806\\uA80B\\uA825\\uA826\\uA8C4\\uA8E0-\\uA8F1\\uA926-\\uA92D\\uA947-\\uA951\\uA980-\\uA982\\uA9B3\\uA9B6-\\uA9B9\\uA9BC\\uAA29-\\uAA2E\\uAA31\\uAA32\\uAA35\\uAA36\\uAA43\\uAA4C\\uAAB0\\uAAB2-\\uAAB4\\uAAB7\\uAAB8\\uAABE\\uAABF\\uAAC1\\uABE5\\uABE8\\uABED\\uFB1E\\uFE00-\\uFE0F\\uFE20-\\uFE26]"),space_combining_mark:new RegExp("[\\u0903\\u093E-\\u0940\\u0949-\\u094C\\u094E\\u0982\\u0983\\u09BE-\\u09C0\\u09C7\\u09C8\\u09CB\\u09CC\\u09D7\\u0A03\\u0A3E-\\u0A40\\u0A83\\u0ABE-\\u0AC0\\u0AC9\\u0ACB\\u0ACC\\u0B02\\u0B03\\u0B3E\\u0B40\\u0B47\\u0B48\\u0B4B\\u0B4C\\u0B57\\u0BBE\\u0BBF\\u0BC1\\u0BC2\\u0BC6-\\u0BC8\\u0BCA-\\u0BCC\\u0BD7\\u0C01-\\u0C03\\u0C41-\\u0C44\\u0C82\\u0C83\\u0CBE\\u0CC0-\\u0CC4\\u0CC7\\u0CC8\\u0CCA\\u0CCB\\u0CD5\\u0CD6\\u0D02\\u0D03\\u0D3E-\\u0D40\\u0D46-\\u0D48\\u0D4A-\\u0D4C\\u0D57\\u0D82\\u0D83\\u0DCF-\\u0DD1\\u0DD8-\\u0DDF\\u0DF2\\u0DF3\\u0F3E\\u0F3F\\u0F7F\\u102B\\u102C\\u1031\\u1038\\u103B\\u103C\\u1056\\u1057\\u1062-\\u1064\\u1067-\\u106D\\u1083\\u1084\\u1087-\\u108C\\u108F\\u109A-\\u109C\\u17B6\\u17BE-\\u17C5\\u17C7\\u17C8\\u1923-\\u1926\\u1929-\\u192B\\u1930\\u1931\\u1933-\\u1938\\u19B0-\\u19C0\\u19C8\\u19C9\\u1A19-\\u1A1B\\u1A55\\u1A57\\u1A61\\u1A63\\u1A64\\u1A6D-\\u1A72\\u1B04\\u1B35\\u1B3B\\u1B3D-\\u1B41\\u1B43\\u1B44\\u1B82\\u1BA1\\u1BA6\\u1BA7\\u1BAA\\u1C24-\\u1C2B\\u1C34\\u1C35\\u1CE1\\u1CF2\\uA823\\uA824\\uA827\\uA880\\uA881\\uA8B4-\\uA8C3\\uA952\\uA953\\uA983\\uA9B4\\uA9B5\\uA9BA\\uA9BB\\uA9BD-\\uA9C0\\uAA2F\\uAA30\\uAA33\\uAA34\\uAA4D\\uAA7B\\uABE3\\uABE4\\uABE6\\uABE7\\uABE9\\uABEA\\uABEC]"),connector_punctuation:new RegExp("[\\u005F\\u203F\\u2040\\u2054\\uFE33\\uFE34\\uFE4D-\\uFE4F\\uFF3F]")};M.prototype.toString=function(){return this.message+" (line: "+this.line+", col: "+this.col+", pos: "+this.pos+")\n\n"+this.stack};var Ht={},zt=g(["typeof","void","delete","--","++","!","~","-","+"]),Pt=g(["--","++"]),jt=g(["=","+=","-=","/=","*=","%=",">>=","<<=",">>>=","|=","^=","&="]),It=function(n,e){for(var t=0;t<n.length;++t)for(var r=n[t],i=0;i<r.length;++i)e[r[i]]=t+1;return e}([["||"],["&&"],["|"],["^"],["&"],["==","===","!=","!=="],["<",">","<=",">=","in","instanceof"],[">>","<<",">>>"],["+","-"],["*","/","%"]],{}),Ut=t(["for","do","while","switch"]),Vt=t(["atom","num","string","regexp","name"]);z.prototype=new E,function(n){function e(e,t){e.DEFMETHOD("transform",function(e,r){var i,o;return e.push(this),e.before&&(i=e.before(this,t,r)),i===n&&(e.after?(e.stack[e.stack.length-1]=i=this.clone(),t(i,e),o=e.after(i,r),o!==n&&(i=o)):(i=this,t(i,e))),e.pop(),i})}function t(n,e){return V(n,function(n){return n.transform(e,!0)})}e(W,l),e(ee,function(n,e){n.label=n.label.transform(e),n.body=n.body.transform(e)}),e(K,function(n,e){n.body=n.body.transform(e)}),e(J,function(n,e){n.body=t(n.body,e)}),e(re,function(n,e){n.condition=n.condition.transform(e),n.body=n.body.transform(e)}),e(ae,function(n,e){n.init&&(n.init=n.init.transform(e)),n.condition&&(n.condition=n.condition.transform(e)),n.step&&(n.step=n.step.transform(e)),n.body=n.body.transform(e)}),e(ue,function(n,e){n.init=n.init.transform(e),n.object=n.object.transform(e),n.body=n.body.transform(e)}),e(se,function(n,e){n.expression=n.expression.transform(e),n.body=n.body.transform(e)}),e(me,function(n,e){n.value&&(n.value=n.value.transform(e))}),e(be,function(n,e){n.label&&(n.label=n.label.transform(e))}),e(we,function(n,e){n.condition=n.condition.transform(e),n.body=n.body.transform(e),n.alternative&&(n.alternative=n.alternative.transform(e))}),e(Ee,function(n,e){n.expression=n.expression.transform(e),n.body=t(n.body,e)}),e(Se,function(n,e){n.expression=n.expression.transform(e),n.body=t(n.body,e)}),e(Ce,function(n,e){n.body=t(n.body,e),n.bcatch&&(n.bcatch=n.bcatch.transform(e)),n.bfinally&&(n.bfinally=n.bfinally.transform(e))}),e(ke,function(n,e){n.argname=n.argname.transform(e),n.body=t(n.body,e)}),e(Be,function(n,e){n.definitions=t(n.definitions,e)}),e(Oe,function(n,e){n.name=n.name.transform(e),n.value&&(n.value=n.value.transform(e))}),e(le,function(n,e){n.name&&(n.name=n.name.transform(e)),n.argnames=t(n.argnames,e),n.body=t(n.body,e)}),e(Me,function(n,e){n.expression=n.expression.transform(e),n.args=t(n.args,e)}),e(Re,function(n,e){n.car=n.car.transform(e),n.cdr=n.cdr.transform(e)}),e(He,function(n,e){n.expression=n.expression.transform(e)}),e(ze,function(n,e){n.expression=n.expression.transform(e),n.property=n.property.transform(e)}),e(Pe,function(n,e){n.expression=n.expression.transform(e)}),e(Ue,function(n,e){n.left=n.left.transform(e),n.right=n.right.transform(e)}),e(Ve,function(n,e){n.condition=n.condition.transform(e),n.consequent=n.consequent.transform(e),n.alternative=n.alternative.transform(e)}),e(We,function(n,e){n.elements=t(n.elements,e)}),e(Ye,function(n,e){n.properties=t(n.properties,e)}),e(Xe,function(n,e){n.value=n.value.transform(e)})}(),P.prototype={unmangleable:function(n){return this.global&&!(n&&n.toplevel)||this.undeclared||!(n&&n.eval)&&(this.scope.uses_eval||this.scope.uses_with)},mangle:function(n){if(!this.mangled_name&&!this.unmangleable(n)){var e=this.scope;!n.screw_ie8&&this.orig[0]instanceof ot&&(e=e.parent_scope),this.mangled_name=e.next_mangled(n,this)}}},fe.DEFMETHOD("figure_out_scope",function(n){n=c(n,{screw_ie8:!1});var e=this,t=e.parent_scope=null,r=null,i=0,o=new E(function(e,a){if(n.screw_ie8&&e instanceof ke){var u=t;return t=new ce(e),t.init_scope_vars(i),t.parent_scope=u,a(),t=u,!0}if(e instanceof ce){e.init_scope_vars(i);var u=e.parent_scope=t,s=r;return r=t=e,++i,a(),--i,t=u,r=s,!0}if(e instanceof G)return e.scope=t,p(t.directives,e.value),!0;if(e instanceof se)for(var c=t;c;c=c.parent_scope)c.uses_with=!0;else if(e instanceof Ze&&(e.scope=t),e instanceof ot)r.def_function(e);else if(e instanceof it)(e.scope=r.parent_scope).def_function(e);else if(e instanceof et||e instanceof tt){var f=r.def_variable(e);f.constant=e instanceof tt,f.init=o.parent().value}else e instanceof at&&(n.screw_ie8?t:r).def_variable(e)});e.walk(o);var a=null,u=e.globals=new y,o=new E(function(n,t){if(n instanceof le){var r=a;return a=n,t(),a=r,!0}if(n instanceof st){var i=n.name,s=n.scope.find_variable(i);if(s)n.thedef=s;else{var c;if(u.has(i)?c=u.get(i):(c=new P(e,u.size(),n),c.undeclared=!0,c.global=!0,u.set(i,c)),n.thedef=c,"eval"==i&&o.parent()instanceof Me)for(var f=n.scope;f&&!f.uses_eval;f=f.parent_scope)f.uses_eval=!0;a&&"arguments"==i&&(a.uses_arguments=!0)}return n.reference(),!0}});e.walk(o)}),ce.DEFMETHOD("init_scope_vars",function(n){this.directives=[],this.variables=new y,this.functions=new y,this.uses_with=!1,this.uses_eval=!1,this.parent_scope=null,this.enclosed=[],this.cname=-1,this.nesting=n}),ce.DEFMETHOD("strict",function(){return this.has_directive("use strict")}),le.DEFMETHOD("init_scope_vars",function(){ce.prototype.init_scope_vars.apply(this,arguments),this.uses_arguments=!1}),st.DEFMETHOD("reference",function(){var n=this.definition();n.references.push(this);for(var e=this.scope;e&&(p(e.enclosed,n),e!==n.scope);)e=e.parent_scope;this.frame=this.scope.nesting-n.scope.nesting}),ce.DEFMETHOD("find_variable",function(n){return n instanceof Ze&&(n=n.name),this.variables.get(n)||this.parent_scope&&this.parent_scope.find_variable(n)}),ce.DEFMETHOD("has_directive",function(n){return this.parent_scope&&this.parent_scope.has_directive(n)||(this.directives.indexOf(n)>=0?this:null)}),ce.DEFMETHOD("def_function",function(n){this.functions.set(n.name,this.def_variable(n))}),ce.DEFMETHOD("def_variable",function(n){var e;return this.variables.has(n.name)?(e=this.variables.get(n.name),e.orig.push(n)):(e=new P(this,this.variables.size(),n),this.variables.set(n.name,e),e.global=!this.parent_scope),n.thedef=e}),ce.DEFMETHOD("next_mangled",function(n){var e=this.enclosed;n:for(;;){var t=Lt(++this.cname);if(x(t)&&!(n.except.indexOf(t)>=0)){for(var r=e.length;--r>=0;){var i=e[r],o=i.mangled_name||i.unmangleable(n)&&i.name;if(t==o)continue n}return t}}}),de.DEFMETHOD("next_mangled",function(n,e){for(var t=e.orig[0]instanceof rt&&this.name&&this.name.definition();;){var r=le.prototype.next_mangled.call(this,n,e);if(!t||t.mangled_name!=r)return r}}),ce.DEFMETHOD("references",function(n){return n instanceof Ze&&(n=n.definition()),this.enclosed.indexOf(n)<0?null:n}),Ze.DEFMETHOD("unmangleable",function(n){return this.definition().unmangleable(n)}),Qe.DEFMETHOD("unmangleable",function(){return!0}),ut.DEFMETHOD("unmangleable",function(){return!1}),Ze.DEFMETHOD("unreferenced",function(){return 0==this.definition().references.length&&!(this.scope.uses_eval||this.scope.uses_with)}),Ze.DEFMETHOD("undeclared",function(){return this.definition().undeclared}),ct.DEFMETHOD("undeclared",function(){return!1}),ut.DEFMETHOD("undeclared",function(){return!1}),Ze.DEFMETHOD("definition",function(){return this.thedef}),Ze.DEFMETHOD("global",function(){return this.definition().global}),fe.DEFMETHOD("_default_mangler_options",function(n){return c(n,{except:[],eval:!1,sort:!1,toplevel:!1,screw_ie8:!1})}),fe.DEFMETHOD("mangle_names",function(n){n=this._default_mangler_options(n);var e=-1,t=[],r=new E(function(i,o){if(i instanceof ee){var a=e;return o(),e=a,!0}if(i instanceof ce){var u=(r.parent(),[]);return i.variables.each(function(e){n.except.indexOf(e.name)<0&&u.push(e)}),n.sort&&u.sort(function(n,e){return e.references.length-n.references.length}),void t.push.apply(t,u)}if(i instanceof ut){var s;do s=Lt(++e);while(!x(s));return i.mangled_name=s,!0}return n.screw_ie8&&i instanceof at?void t.push(i.definition()):void 0});this.walk(r),t.forEach(function(e){e.mangle(n)})}),fe.DEFMETHOD("compute_char_frequency",function(n){n=this._default_mangler_options(n);var e=new E(function(e){e instanceof lt?Lt.consider(e.print_to_string()):e instanceof ve?Lt.consider("return"):e instanceof ge?Lt.consider("throw"):e instanceof Ae?Lt.consider("continue"):e instanceof ye?Lt.consider("break"):e instanceof X?Lt.consider("debugger"):e instanceof G?Lt.consider(e.value):e instanceof oe?Lt.consider("while"):e instanceof ie?Lt.consider("do while"):e instanceof we?(Lt.consider("if"),e.alternative&&Lt.consider("else")):e instanceof Te?Lt.consider("var"):e instanceof $e?Lt.consider("const"):e instanceof le?Lt.consider("function"):e instanceof ae?Lt.consider("for"):e instanceof ue?Lt.consider("for in"):e instanceof Ee?Lt.consider("switch"):e instanceof Se?Lt.consider("case"):e instanceof Fe?Lt.consider("default"):e instanceof se?Lt.consider("with"):e instanceof Ke?Lt.consider("set"+e.key):e instanceof Je?Lt.consider("get"+e.key):e instanceof Ge?Lt.consider(e.key):e instanceof Ne?Lt.consider("new"):e instanceof ft?Lt.consider("this"):e instanceof Ce?Lt.consider("try"):e instanceof ke?Lt.consider("catch"):e instanceof xe?Lt.consider("finally"):e instanceof Ze&&e.unmangleable(n)?Lt.consider(e.name):e instanceof Pe||e instanceof Ue?Lt.consider(e.operator):e instanceof He&&Lt.consider(e.property)});this.walk(e),Lt.sort()});var Lt=function(){function n(){r=Object.create(null),t=i.split("").map(function(n){return n.charCodeAt(0)}),t.forEach(function(n){r[n]=0})}function e(n){var e="",r=54;do e+=String.fromCharCode(t[n%r]),n=Math.floor(n/r),r=64;while(n>0);return e}var t,r,i="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789";return e.consider=function(n){for(var e=n.length;--e>=0;){var t=n.charCodeAt(e);t in r&&++r[t]}},e.sort=function(){t=_(t,function(n,e){return F(n)&&!F(e)?1:F(e)&&!F(n)?-1:r[e]-r[n]})},e.reset=n,n(),e.get=function(){return t},e.freq=function(){return r},e}();fe.DEFMETHOD("scope_warnings",function(n){n=c(n,{undeclared:!1,unreferenced:!0,assign_to_global:!0,func_arguments:!0,nested_defuns:!0,eval:!0});var e=new E(function(t){if(n.undeclared&&t instanceof st&&t.undeclared()&&W.warn("Undeclared symbol: {name} [{file}:{line},{col}]",{name:t.name,file:t.start.file,line:t.start.line,col:t.start.col}),n.assign_to_global){var r=null;t instanceof Le&&t.left instanceof st?r=t.left:t instanceof ue&&t.init instanceof st&&(r=t.init),r&&(r.undeclared()||r.global()&&r.scope!==r.definition().scope)&&W.warn("{msg}: {name} [{file}:{line},{col}]",{msg:r.undeclared()?"Accidental global?":"Assignment to global",name:r.name,file:r.start.file,line:r.start.line,col:r.start.col})}n.eval&&t instanceof st&&t.undeclared()&&"eval"==t.name&&W.warn("Eval is used [{file}:{line},{col}]",t.start),n.unreferenced&&(t instanceof nt||t instanceof ut)&&t.unreferenced()&&W.warn("{type} {name} is declared but not referenced [{file}:{line},{col}]",{type:t instanceof ut?"Label":"Symbol",name:t.name,file:t.start.file,line:t.start.line,col:t.start.col}),n.func_arguments&&t instanceof le&&t.uses_arguments&&W.warn("arguments used in function {name} [{file}:{line},{col}]",{name:t.name?t.name.name:"anonymous",file:t.start.file,line:t.start.line,col:t.start.col}),n.nested_defuns&&t instanceof he&&!(e.parent()instanceof ce)&&W.warn('Function {name} declared in nested statement "{type}" [{file}:{line},{col}]',{name:t.name.name,type:e.parent().TYPE,file:t.start.file,line:t.start.line,col:t.start.col})});this.walk(e)}),function(){function n(n,e){n.DEFMETHOD("_codegen",e)}function e(n,e){n.DEFMETHOD("needs_parens",e)}function t(n){var e=n.parent();return e instanceof Pe?!0:e instanceof Ue&&!(e instanceof Le)?!0:e instanceof Me&&e.expression===this?!0:e instanceof Ve&&e.condition===this?!0:e instanceof qe&&e.expression===this?!0:void 0}function r(n,e,t){var r=n.length-1;n.forEach(function(n,i){n instanceof Q||(t.indent(),n.print(t),i==r&&e||(t.newline(),e&&t.newline()))})}function i(n,e){n.length>0?e.with_block(function(){r(n,!1,e)}):e.print("{}")}function o(n,e){if(e.option("bracketize"))return void h(n.body,e);if(!n.body)return e.force_semicolon();if(n.body instanceof ie&&!e.option("screw_ie8"))return void h(n.body,e);for(var t=n.body;;)if(t instanceof we){if(!t.alternative)return void h(n.body,e);t=t.alternative}else{if(!(t instanceof ne))break;t=t.body}s(n.body,e)}function a(n,e,t){if(t)try{n.walk(new E(function(n){if(n instanceof Ue&&"in"==n.operator)throw e})),n.print(e)}catch(r){if(r!==e)throw r;n.print(e,!0)}else n.print(e)}function u(n){return[92,47,46,43,42,63,40,41,91,93,123,125,36,94,58,124,33,10,13,0,65279,8232,8233].indexOf(n)<0}function s(n,e){e.option("bracketize")?!n||n instanceof Q?e.print("{}"):n instanceof Z?n.print(e):e.with_block(function(){e.indent(),n.print(e),e.newline()}):!n||n instanceof Q?e.force_semicolon():n.print(e)}function c(n){for(var e=n.stack(),t=e.length,r=e[--t],i=e[--t];t>0;){if(i instanceof Y&&i.body===r)return!0;if(!(i instanceof Re&&i.car===r||i instanceof Me&&i.expression===r&&!(i instanceof Ne)||i instanceof He&&i.expression===r||i instanceof ze&&i.expression===r||i instanceof Ve&&i.condition===r||i instanceof Ue&&i.left===r||i instanceof Ie&&i.expression===r))return!1;r=i,i=e[--t]}}function f(n,e){return 0==n.args.length&&!e.option("beautify")}function p(n){for(var e=n[0],t=e.length,r=1;r<n.length;++r)n[r].length<t&&(e=n[r],t=e.length);return e}function d(n){var e,t=n.toString(10),r=[t.replace(/^0\./,".").replace("e+","e")];return Math.floor(n)===n?(n>=0?r.push("0x"+n.toString(16).toLowerCase(),"0"+n.toString(8)):r.push("-0x"+(-n).toString(16).toLowerCase(),"-0"+(-n).toString(8)),(e=/^(.*?)(0+)$/.exec(n))&&r.push(e[1]+"e"+e[2].length)):(e=/^0?\.(0+)(.*)$/.exec(n))&&r.push(e[2]+"e-"+(e[1].length+e[2].length),t.substr(t.indexOf("."))),p(r)}function h(n,e){return n instanceof Z?void n.print(e):void e.with_block(function(){e.indent(),n.print(e),e.newline()})}function _(n,e){n.DEFMETHOD("add_source_map",function(n){e(this,n)})}function m(n,e){e.add_mapping(n.start)}W.DEFMETHOD("print",function(n,e){function t(){r.add_comments(n),r.add_source_map(n),i(r,n)}var r=this,i=r._codegen;n.push_node(r),e||r.needs_parens(n)?n.with_parens(t):t(),n.pop_node()}),W.DEFMETHOD("print_to_string",function(n){var e=j(n);return this.print(e),e.get()}),W.DEFMETHOD("add_comments",function(n){var e=n.option("comments"),t=this;if(e){var r=t.start;if(r&&!r._comments_dumped){r._comments_dumped=!0;var i=r.comments_before||[];t instanceof me&&t.value&&t.value.walk(new E(function(n){return n.start&&n.start.comments_before&&(i=i.concat(n.start.comments_before),n.start.comments_before=[]),n instanceof de||n instanceof We||n instanceof Ye?!0:void 0
+})),e.test?i=i.filter(function(n){return e.test(n.value)}):"function"==typeof e&&(i=i.filter(function(n){return e(t,n)})),i.forEach(function(e){/comment[134]/.test(e.type)?(n.print("//"+e.value+"\n"),n.indent()):"comment2"==e.type&&(n.print("/*"+e.value+"*/"),r.nlb?(n.print("\n"),n.indent()):n.space())})}}}),e(W,function(){return!1}),e(de,function(n){return c(n)}),e(Ye,function(n){return c(n)}),e(Pe,function(n){var e=n.parent();return e instanceof qe&&e.expression===this}),e(Re,function(n){var e=n.parent();return e instanceof Me||e instanceof Pe||e instanceof Ue||e instanceof Oe||e instanceof qe||e instanceof We||e instanceof Xe||e instanceof Ve}),e(Ue,function(n){var e=n.parent();if(e instanceof Me&&e.expression===this)return!0;if(e instanceof Pe)return!0;if(e instanceof qe&&e.expression===this)return!0;if(e instanceof Ue){var t=e.operator,r=It[t],i=this.operator,o=It[i];if(r>o||r==o&&this===e.right)return!0}}),e(qe,function(n){var e=n.parent();if(e instanceof Ne&&e.expression===this)try{this.walk(new E(function(n){if(n instanceof Me)throw e}))}catch(t){if(t!==e)throw t;return!0}}),e(Me,function(n){var e,t=n.parent();return t instanceof Ne&&t.expression===this?!0:this.expression instanceof de&&t instanceof qe&&t.expression===this&&(e=n.parent(1))instanceof Le&&e.left===t}),e(Ne,function(n){var e=n.parent();return f(this,n)&&(e instanceof qe||e instanceof Me&&e.expression===this)?!0:void 0}),e(dt,function(n){var e=n.parent();return this.getValue()<0&&e instanceof qe&&e.expression===this?!0:void 0}),e(vt,function(n){var e=n.parent();return e instanceof qe&&e.expression===this?!0:void 0}),e(Le,t),e(Ve,t),n(G,function(n,e){e.print_string(n.value),e.semicolon()}),n(X,function(n,e){e.print("debugger"),e.semicolon()}),ne.DEFMETHOD("_do_print_body",function(n){s(this.body,n)}),n(Y,function(n,e){n.body.print(e),e.semicolon()}),n(fe,function(n,e){r(n.body,!0,e),e.print("")}),n(ee,function(n,e){n.label.print(e),e.colon(),n.body.print(e)}),n(K,function(n,e){n.body.print(e),e.semicolon()}),n(Z,function(n,e){i(n.body,e)}),n(Q,function(n,e){e.semicolon()}),n(ie,function(n,e){e.print("do"),e.space(),n._do_print_body(e),e.space(),e.print("while"),e.space(),e.with_parens(function(){n.condition.print(e)}),e.semicolon()}),n(oe,function(n,e){e.print("while"),e.space(),e.with_parens(function(){n.condition.print(e)}),e.space(),n._do_print_body(e)}),n(ae,function(n,e){e.print("for"),e.space(),e.with_parens(function(){!n.init||n.init instanceof Q?e.print(";"):(n.init instanceof Be?n.init.print(e):a(n.init,e,!0),e.print(";"),e.space()),n.condition?(n.condition.print(e),e.print(";"),e.space()):e.print(";"),n.step&&n.step.print(e)}),e.space(),n._do_print_body(e)}),n(ue,function(n,e){e.print("for"),e.space(),e.with_parens(function(){n.init.print(e),e.space(),e.print("in"),e.space(),n.object.print(e)}),e.space(),n._do_print_body(e)}),n(se,function(n,e){e.print("with"),e.space(),e.with_parens(function(){n.expression.print(e)}),e.space(),n._do_print_body(e)}),le.DEFMETHOD("_do_print",function(n,e){var t=this;e||n.print("function"),t.name&&(n.space(),t.name.print(n)),n.with_parens(function(){t.argnames.forEach(function(e,t){t&&n.comma(),e.print(n)})}),n.space(),i(t.body,n)}),n(le,function(n,e){n._do_print(e)}),me.DEFMETHOD("_do_print",function(n,e){n.print(e),this.value&&(n.space(),this.value.print(n)),n.semicolon()}),n(ve,function(n,e){n._do_print(e,"return")}),n(ge,function(n,e){n._do_print(e,"throw")}),be.DEFMETHOD("_do_print",function(n,e){n.print(e),this.label&&(n.space(),this.label.print(n)),n.semicolon()}),n(ye,function(n,e){n._do_print(e,"break")}),n(Ae,function(n,e){n._do_print(e,"continue")}),n(we,function(n,e){e.print("if"),e.space(),e.with_parens(function(){n.condition.print(e)}),e.space(),n.alternative?(o(n,e),e.space(),e.print("else"),e.space(),s(n.alternative,e)):n._do_print_body(e)}),n(Ee,function(n,e){e.print("switch"),e.space(),e.with_parens(function(){n.expression.print(e)}),e.space(),n.body.length>0?e.with_block(function(){n.body.forEach(function(n,t){t&&e.newline(),e.indent(!0),n.print(e)})}):e.print("{}")}),De.DEFMETHOD("_do_print_body",function(n){this.body.length>0&&(n.newline(),this.body.forEach(function(e){n.indent(),e.print(n),n.newline()}))}),n(Fe,function(n,e){e.print("default:"),n._do_print_body(e)}),n(Se,function(n,e){e.print("case"),e.space(),n.expression.print(e),e.print(":"),n._do_print_body(e)}),n(Ce,function(n,e){e.print("try"),e.space(),i(n.body,e),n.bcatch&&(e.space(),n.bcatch.print(e)),n.bfinally&&(e.space(),n.bfinally.print(e))}),n(ke,function(n,e){e.print("catch"),e.space(),e.with_parens(function(){n.argname.print(e)}),e.space(),i(n.body,e)}),n(xe,function(n,e){e.print("finally"),e.space(),i(n.body,e)}),Be.DEFMETHOD("_do_print",function(n,e){n.print(e),n.space(),this.definitions.forEach(function(e,t){t&&n.comma(),e.print(n)});var t=n.parent(),r=t instanceof ae||t instanceof ue,i=r&&t.init===this;i||n.semicolon()}),n(Te,function(n,e){n._do_print(e,"var")}),n($e,function(n,e){n._do_print(e,"const")}),n(Oe,function(n,e){if(n.name.print(e),n.value){e.space(),e.print("="),e.space();var t=e.parent(1),r=t instanceof ae||t instanceof ue;a(n.value,e,r)}}),n(Me,function(n,e){n.expression.print(e),n instanceof Ne&&f(n,e)||e.with_parens(function(){n.args.forEach(function(n,t){t&&e.comma(),n.print(e)})})}),n(Ne,function(n,e){e.print("new"),e.space(),Me.prototype._codegen(n,e)}),Re.DEFMETHOD("_do_print",function(n){this.car.print(n),this.cdr&&(n.comma(),n.should_break()&&(n.newline(),n.indent()),this.cdr.print(n))}),n(Re,function(n,e){n._do_print(e)}),n(He,function(n,e){var t=n.expression;t.print(e),t instanceof dt&&t.getValue()>=0&&(/[xa-f.]/i.test(e.last())||e.print(".")),e.print("."),e.add_mapping(n.end),e.print_name(n.property)}),n(ze,function(n,e){n.expression.print(e),e.print("["),n.property.print(e),e.print("]")}),n(je,function(n,e){var t=n.operator;e.print(t),(/^[a-z]/i.test(t)||/[+-]$/.test(t)&&n.expression instanceof je&&/^[+-]/.test(n.expression.operator))&&e.space(),n.expression.print(e)}),n(Ie,function(n,e){n.expression.print(e),e.print(n.operator)}),n(Ue,function(n,e){n.left.print(e),e.space(),e.print(n.operator),"<"==n.operator&&n.right instanceof je&&"!"==n.right.operator&&n.right.expression instanceof je&&"--"==n.right.expression.operator?e.print(" "):e.space(),n.right.print(e)}),n(Ve,function(n,e){n.condition.print(e),e.space(),e.print("?"),e.space(),n.consequent.print(e),e.space(),e.colon(),n.alternative.print(e)}),n(We,function(n,e){e.with_square(function(){var t=n.elements,r=t.length;r>0&&e.space(),t.forEach(function(n,t){t&&e.comma(),n.print(e),t===r-1&&n instanceof bt&&e.comma()}),r>0&&e.space()})}),n(Ye,function(n,e){n.properties.length>0?e.with_block(function(){n.properties.forEach(function(n,t){t&&(e.print(","),e.newline()),e.indent(),n.print(e)}),e.newline()}):e.print("{}")}),n(Ge,function(n,e){var t=n.key;e.option("quote_keys")?e.print_string(t+""):("number"==typeof t||!e.option("beautify")&&+t+""==t)&&parseFloat(t)>=0?e.print(d(t)):(St(t)?e.option("screw_ie8"):$(t))?e.print_name(t):e.print_string(t),e.colon(),n.value.print(e)}),n(Ke,function(n,e){e.print("set"),e.space(),n.key.print(e),n.value._do_print(e,!0)}),n(Je,function(n,e){e.print("get"),e.space(),n.key.print(e),n.value._do_print(e,!0)}),n(Ze,function(n,e){var t=n.definition();e.print_name(t?t.mangled_name||t.name:n.name)}),n(gt,function(n,e){e.print("void 0")}),n(bt,l),n(yt,function(n,e){e.print("1/0")}),n(vt,function(n,e){e.print("0/0")}),n(ft,function(n,e){e.print("this")}),n(lt,function(n,e){e.print(n.getValue())}),n(pt,function(n,e){e.print_string(n.getValue())}),n(dt,function(n,e){e.print(d(n.getValue()))}),n(ht,function(n,e){var t=n.getValue().toString();e.option("ascii_only")?t=e.to_ascii(t):e.option("unescape_regexps")&&(t=t.split("\\\\").map(function(n){return n.replace(/\\u[0-9a-fA-F]{4}|\\x[0-9a-fA-F]{2}/g,function(n){var e=parseInt(n.substr(2),16);return u(e)?String.fromCharCode(e):n})}).join("\\\\")),e.print(t);var r=e.parent();r instanceof Ue&&/^in/.test(r.operator)&&r.left===n&&e.print(" ")}),_(W,l),_(G,m),_(X,m),_(Ze,m),_(_e,m),_(ne,m),_(ee,l),_(le,m),_(Ee,m),_(De,m),_(Z,m),_(fe,l),_(Ne,m),_(Ce,m),_(ke,m),_(xe,m),_(Be,m),_(lt,m),_(Xe,function(n,e){e.add_mapping(n.start,n.key)})}(),I.prototype=new z,f(I.prototype,{option:function(n){return this.options[n]},warn:function(){this.options.warnings&&W.warn.apply(W,arguments)},before:function(n,e){if(n._squeezed)return n;var t=!1;return n instanceof ce&&(n=n.hoist_declarations(this),t=!0),e(n,this),n=n.optimize(this),t&&n instanceof ce&&(n.drop_unused(this),e(n,this)),n._squeezed=!0,n}}),function(){function n(n,e){n.DEFMETHOD("optimize",function(n){var t=this;if(t._optimized)return t;var r=e(t,n);return r._optimized=!0,r===t?r:r.transform(n)})}function e(n,e,t){return t||(t={}),e&&(t.start||(t.start=e.start),t.end||(t.end=e.end)),new n(t)}function t(n,t,r){if(t instanceof W)return t.transform(n);switch(typeof t){case"string":return e(pt,r,{value:t}).optimize(n);case"number":return e(isNaN(t)?vt:dt,r,{value:t}).optimize(n);case"boolean":return e(t?Et:wt,r).optimize(n);case"undefined":return e(gt,r).optimize(n);default:if(null===t)return e(mt,r).optimize(n);if(t instanceof RegExp)return e(ht,r).optimize(n);throw new Error(d("Can't handle constant of type: {type}",{type:typeof t}))}}function r(n){if(null===n)return[];if(n instanceof Z)return n.body;if(n instanceof Q)return[];if(n instanceof Y)return[n];throw new Error("Can't convert thing to statement array")}function i(n){return null===n?!0:n instanceof Q?!0:n instanceof Z?0==n.body.length:!1}function u(n){return n instanceof Ee?n:(n instanceof ae||n instanceof ue||n instanceof re)&&n.body instanceof Z?n.body:n}function s(n,t){function i(n){function r(n,t){return e(K,n,{body:e(Le,n,{operator:"=",left:e(He,t,{expression:e(st,t,t),property:"$inject"}),right:e(We,n,{elements:n.argnames.map(function(n){return e(pt,n,{value:n.name})})})})})}return n.reduce(function(n,e){n.push(e);var i=e.start,o=i.comments_before;if(o&&o.length>0){var a=o.pop();/@ngInject/.test(a.value)&&(e instanceof he?n.push(r(e,e.name)):e instanceof Be?e.definitions.forEach(function(e){e.value&&e.value instanceof le&&n.push(r(e.value,e.name))}):t.warn("Unknown statement marked with @ngInject [{file}:{line},{col}]",i))}return n},[])}function o(n){var e=[];return n.reduce(function(n,t){return t instanceof Z?(_=!0,n.push.apply(n,o(t.body))):t instanceof Q?_=!0:t instanceof G?e.indexOf(t.value)<0?(n.push(t),e.push(t.value)):_=!0:n.push(t),n},[])}function a(n,t){var i=t.self(),o=i instanceof le,a=[];n:for(var s=n.length;--s>=0;){var c=n[s];switch(!0){case o&&c instanceof ve&&!c.value&&0==a.length:_=!0;continue n;case c instanceof we:if(c.body instanceof ve){if((o&&0==a.length||a[0]instanceof ve&&!a[0].value)&&!c.body.value&&!c.alternative){_=!0;var f=e(K,c.condition,{body:c.condition});a.unshift(f);continue n}if(a[0]instanceof ve&&c.body.value&&a[0].value&&!c.alternative){_=!0,c=c.clone(),c.alternative=a[0],a[0]=c.transform(t);continue n}if((0==a.length||a[0]instanceof ve)&&c.body.value&&!c.alternative&&o){_=!0,c=c.clone(),c.alternative=a[0]||e(ve,c,{value:e(gt,c)}),a[0]=c.transform(t);continue n}if(!c.body.value&&o){_=!0,c=c.clone(),c.condition=c.condition.negate(t),c.body=e(Z,c,{body:r(c.alternative).concat(a)}),c.alternative=null,a=[c.transform(t)];continue n}if(1==a.length&&o&&a[0]instanceof K&&(!c.alternative||c.alternative instanceof K)){_=!0,a.push(e(ve,a[0],{value:e(gt,a[0])}).transform(t)),a=r(c.alternative).concat(a),a.unshift(c);continue n}}var l=m(c.body),p=l instanceof be?t.loopcontrol_target(l.label):null;if(l&&(l instanceof ve&&!l.value&&o||l instanceof Ae&&i===u(p)||l instanceof ye&&p instanceof Z&&i===p)){l.label&&h(l.label.thedef.references,l),_=!0;var d=r(c.body).slice(0,-1);c=c.clone(),c.condition=c.condition.negate(t),c.body=e(Z,c,{body:r(c.alternative).concat(a)}),c.alternative=e(Z,c,{body:d}),a=[c.transform(t)];continue n}var l=m(c.alternative),p=l instanceof be?t.loopcontrol_target(l.label):null;if(l&&(l instanceof ve&&!l.value&&o||l instanceof Ae&&i===u(p)||l instanceof ye&&p instanceof Z&&i===p)){l.label&&h(l.label.thedef.references,l),_=!0,c=c.clone(),c.body=e(Z,c.body,{body:r(c.body).concat(a)}),c.alternative=e(Z,c.alternative,{body:r(c.alternative).slice(0,-1)}),a=[c.transform(t)];continue n}a.unshift(c);break;default:a.unshift(c)}}return a}function s(n,e){var t=!1,r=n.length,i=e.self();return n=n.reduce(function(n,r){if(t)c(e,r,n);else{if(r instanceof be){var o=e.loopcontrol_target(r.label);r instanceof ye&&o instanceof Z&&u(o)===i||r instanceof Ae&&u(o)===i?r.label&&h(r.label.thedef.references,r):n.push(r)}else n.push(r);m(r)&&(t=!0)}return n},[]),_=n.length!=r,n}function f(n,t){function r(){i=Re.from_array(i),i&&o.push(e(K,i,{body:i})),i=[]}if(n.length<2)return n;var i=[],o=[];return n.forEach(function(n){n instanceof K?i.push(n.body):(r(),o.push(n))}),r(),o=l(o,t),_=o.length!=n.length,o}function l(n,t){function r(n){i.pop();var e=o.body;return e instanceof Re?e.add(n):e=Re.cons(e,n),e.transform(t)}var i=[],o=null;return n.forEach(function(n){if(o)if(n instanceof ae){var t={};try{o.body.walk(new E(function(n){if(n instanceof Ue&&"in"==n.operator)throw t})),!n.init||n.init instanceof Be?n.init||(n.init=o.body,i.pop()):n.init=r(n.init)}catch(a){if(a!==t)throw a}}else n instanceof we?n.condition=r(n.condition):n instanceof se?n.expression=r(n.expression):n instanceof me&&n.value?n.value=r(n.value):n instanceof me?n.value=r(e(gt,n)):n instanceof Ee&&(n.expression=r(n.expression));i.push(n),o=n instanceof K?n:null}),i}function p(n){var e=null;return n.reduce(function(n,t){return t instanceof Be&&e&&e.TYPE==t.TYPE?(e.definitions=e.definitions.concat(t.definitions),_=!0):t instanceof ae&&e instanceof Be&&(!t.init||t.init.TYPE==e.TYPE)?(_=!0,n.pop(),t.init?t.init.definitions=e.definitions.concat(t.init.definitions):t.init=e,n.push(t),e=t):(e=t,n.push(t)),n},[])}function d(n){n.forEach(function(n){n instanceof K&&(n.body=function t(n){return n.transform(new z(function(n){if(n instanceof Me&&n.expression instanceof de)return e(je,n,{operator:"!",expression:n});if(n instanceof Me)n.expression=t(n.expression);else if(n instanceof Re)n.car=t(n.car);else if(n instanceof Ve){var r=t(n.condition);if(r!==n.condition){n.condition=r;var i=n.consequent;n.consequent=n.alternative,n.alternative=i}}return n}))}(n.body))})}var _;do _=!1,t.option("angular")&&(n=i(n)),n=o(n),t.option("dead_code")&&(n=s(n,t)),t.option("if_return")&&(n=a(n,t)),t.option("sequences")&&(n=f(n,t)),t.option("join_vars")&&(n=p(n,t));while(_);return t.option("negate_iife")&&d(n,t),n}function c(n,e,t){n.warn("Dropping unreachable code [{file}:{line},{col}]",e.start),e.walk(new E(function(e){return e instanceof Be?(n.warn("Declarations in unreachable code! [{file}:{line},{col}]",e.start),e.remove_initializers(),t.push(e),!0):e instanceof he?(t.push(e),!0):e instanceof ce?!0:void 0}))}function f(n,e){return n.print_to_string().length>e.print_to_string().length?e:n}function m(n){return n&&n.aborts()}function v(n,t){function i(i){i=r(i),n.body instanceof Z?(n.body=n.body.clone(),n.body.body=i.concat(n.body.body.slice(1)),n.body=n.body.transform(t)):n.body=e(Z,n.body,{body:i}).transform(t),v(n,t)}var o=n.body instanceof Z?n.body.body[0]:n.body;o instanceof we&&(o.body instanceof ye&&t.loopcontrol_target(o.body.label)===n?(n.condition=n.condition?e(Ue,n.condition,{left:n.condition,operator:"&&",right:o.condition.negate(t)}):o.condition.negate(t),i(o.alternative)):o.alternative instanceof ye&&t.loopcontrol_target(o.alternative.label)===n&&(n.condition=n.condition?e(Ue,n.condition,{left:n.condition,operator:"&&",right:o.condition}):o.condition,i(o.body)))}function A(n,e){var t=e.option("pure_getters");e.options.pure_getters=!1;var r=n.has_side_effects(e);return e.options.pure_getters=t,r}function w(n,t){return t.option("booleans")&&t.in_boolean_context()?e(Et,n):n}n(W,function(n){return n}),W.DEFMETHOD("equivalent_to",function(n){return this.print_to_string()==n.print_to_string()}),function(n){var e=["!","delete"],t=["in","instanceof","==","!=","===","!==","<","<=",">=",">"];n(W,function(){return!1}),n(je,function(){return o(this.operator,e)}),n(Ue,function(){return o(this.operator,t)||("&&"==this.operator||"||"==this.operator)&&this.left.is_boolean()&&this.right.is_boolean()}),n(Ve,function(){return this.consequent.is_boolean()&&this.alternative.is_boolean()}),n(Le,function(){return"="==this.operator&&this.right.is_boolean()}),n(Re,function(){return this.cdr.is_boolean()}),n(Et,function(){return!0}),n(wt,function(){return!0})}(function(n,e){n.DEFMETHOD("is_boolean",e)}),function(n){n(W,function(){return!1}),n(pt,function(){return!0}),n(je,function(){return"typeof"==this.operator}),n(Ue,function(n){return"+"==this.operator&&(this.left.is_string(n)||this.right.is_string(n))}),n(Le,function(n){return("="==this.operator||"+="==this.operator)&&this.right.is_string(n)}),n(Re,function(n){return this.cdr.is_string(n)}),n(Ve,function(n){return this.consequent.is_string(n)&&this.alternative.is_string(n)}),n(Me,function(n){return n.option("unsafe")&&this.expression instanceof st&&"String"==this.expression.name&&this.expression.undeclared()})}(function(n,e){n.DEFMETHOD("is_string",e)}),function(n){function e(n,e){if(!e)throw new Error("Compressor must be passed");return n._eval(e)}W.DEFMETHOD("evaluate",function(e){if(!e.option("evaluate"))return[this];try{var r=this._eval(e);return[f(t(e,r,this),this),r]}catch(i){if(i!==n)throw i;return[this]}}),n(Y,function(){throw new Error(d("Cannot evaluate a statement [{file}:{line},{col}]",this.start))}),n(de,function(){throw n}),n(W,function(){throw n}),n(lt,function(){return this.getValue()}),n(je,function(t){var r=this.expression;switch(this.operator){case"!":return!e(r,t);case"typeof":if(r instanceof de)return"function";if(r=e(r,t),r instanceof RegExp)throw n;return typeof r;case"void":return void e(r,t);case"~":return~e(r,t);case"-":if(r=e(r,t),0===r)throw n;return-r;case"+":return+e(r,t)}throw n}),n(Ue,function(t){var r=this.left,i=this.right;switch(this.operator){case"&&":return e(r,t)&&e(i,t);case"||":return e(r,t)||e(i,t);case"|":return e(r,t)|e(i,t);case"&":return e(r,t)&e(i,t);case"^":return e(r,t)^e(i,t);case"+":return e(r,t)+e(i,t);case"*":return e(r,t)*e(i,t);case"/":return e(r,t)/e(i,t);case"%":return e(r,t)%e(i,t);case"-":return e(r,t)-e(i,t);case"<<":return e(r,t)<<e(i,t);case">>":return e(r,t)>>e(i,t);case">>>":return e(r,t)>>>e(i,t);case"==":return e(r,t)==e(i,t);case"===":return e(r,t)===e(i,t);case"!=":return e(r,t)!=e(i,t);case"!==":return e(r,t)!==e(i,t);case"<":return e(r,t)<e(i,t);case"<=":return e(r,t)<=e(i,t);case">":return e(r,t)>e(i,t);case">=":return e(r,t)>=e(i,t);case"in":return e(r,t)in e(i,t);case"instanceof":return e(r,t)instanceof e(i,t)}throw n}),n(Ve,function(n){return e(this.condition,n)?e(this.consequent,n):e(this.alternative,n)}),n(st,function(t){var r=this.definition();if(r&&r.constant&&r.init)return e(r.init,t);throw n}),n(He,function(t){if(t.option("unsafe")&&"length"==this.property){var r=e(this.expression,t);if("string"==typeof r)return r.length}throw n})}(function(n,e){n.DEFMETHOD("_eval",e)}),function(n){function t(n){return e(je,n,{operator:"!",expression:n})}n(W,function(){return t(this)}),n(Y,function(){throw new Error("Cannot negate a statement")}),n(de,function(){return t(this)}),n(je,function(){return"!"==this.operator?this.expression:t(this)}),n(Re,function(n){var e=this.clone();return e.cdr=e.cdr.negate(n),e}),n(Ve,function(n){var e=this.clone();return e.consequent=e.consequent.negate(n),e.alternative=e.alternative.negate(n),f(t(this),e)}),n(Ue,function(n){var e=this.clone(),r=this.operator;if(n.option("unsafe_comps"))switch(r){case"<=":return e.operator=">",e;case"<":return e.operator=">=",e;case">=":return e.operator="<",e;case">":return e.operator="<=",e}switch(r){case"==":return e.operator="!=",e;case"!=":return e.operator="==",e;case"===":return e.operator="!==",e;case"!==":return e.operator="===",e;case"&&":return e.operator="||",e.left=e.left.negate(n),e.right=e.right.negate(n),f(t(this),e);case"||":return e.operator="&&",e.left=e.left.negate(n),e.right=e.right.negate(n),f(t(this),e)}return t(this)})}(function(n,e){n.DEFMETHOD("negate",function(n){return e.call(this,n)})}),function(n){n(W,function(){return!0}),n(Q,function(){return!1}),n(lt,function(){return!1}),n(ft,function(){return!1}),n(Me,function(n){var e=n.option("pure_funcs");return e?e.indexOf(this.expression.print_to_string())<0:!0}),n(J,function(n){for(var e=this.body.length;--e>=0;)if(this.body[e].has_side_effects(n))return!0;return!1}),n(K,function(n){return this.body.has_side_effects(n)}),n(he,function(){return!0}),n(de,function(){return!1}),n(Ue,function(n){return this.left.has_side_effects(n)||this.right.has_side_effects(n)}),n(Le,function(){return!0}),n(Ve,function(n){return this.condition.has_side_effects(n)||this.consequent.has_side_effects(n)||this.alternative.has_side_effects(n)}),n(Pe,function(n){return"delete"==this.operator||"++"==this.operator||"--"==this.operator||this.expression.has_side_effects(n)}),n(st,function(){return!1}),n(Ye,function(n){for(var e=this.properties.length;--e>=0;)if(this.properties[e].has_side_effects(n))return!0;return!1}),n(Xe,function(n){return this.value.has_side_effects(n)}),n(We,function(n){for(var e=this.elements.length;--e>=0;)if(this.elements[e].has_side_effects(n))return!0;return!1}),n(He,function(n){return n.option("pure_getters")?this.expression.has_side_effects(n):!0}),n(ze,function(n){return n.option("pure_getters")?this.expression.has_side_effects(n)||this.property.has_side_effects(n):!0}),n(qe,function(n){return!n.option("pure_getters")}),n(Re,function(n){return this.car.has_side_effects(n)||this.cdr.has_side_effects(n)})}(function(n,e){n.DEFMETHOD("has_side_effects",e)}),function(n){function e(){var n=this.body.length;return n>0&&m(this.body[n-1])}n(Y,function(){return null}),n(_e,function(){return this}),n(Z,e),n(De,e),n(we,function(){return this.alternative&&m(this.body)&&m(this.alternative)})}(function(n,e){n.DEFMETHOD("aborts",e)}),n(G,function(n){return n.scope.has_directive(n.value)!==n.scope?e(Q,n):n}),n(X,function(n,t){return t.option("drop_debugger")?e(Q,n):n}),n(ee,function(n,t){return n.body instanceof ye&&t.loopcontrol_target(n.body.label)===n.body?e(Q,n):0==n.label.references.length?n.body:n}),n(J,function(n,e){return n.body=s(n.body,e),n}),n(Z,function(n,t){switch(n.body=s(n.body,t),n.body.length){case 1:return n.body[0];case 0:return e(Q,n)}return n}),ce.DEFMETHOD("drop_unused",function(n){var t=this;if(n.option("unused")&&!(t instanceof fe)&&!t.uses_eval){var r=[],i=new y,a=this,u=new E(function(e,o){if(e!==t){if(e instanceof he)return i.add(e.name.name,e),!0;if(e instanceof Be&&a===t)return e.definitions.forEach(function(e){e.value&&(i.add(e.name.name,e.value),e.value.has_side_effects(n)&&e.value.walk(u))}),!0;if(e instanceof st)return p(r,e.definition()),!0;if(e instanceof ce){var s=a;return a=e,o(),a=s,!0}}});t.walk(u);for(var s=0;s<r.length;++s)r[s].orig.forEach(function(n){var e=i.get(n.name);e&&e.forEach(function(n){var e=new E(function(n){n instanceof st&&p(r,n.definition())});n.walk(e)})});var c=new z(function(i,a,u){if(i instanceof le&&!(i instanceof pe)&&!n.option("keep_fargs"))for(var s=i.argnames,f=s.length;--f>=0;){var l=s[f];if(!l.unreferenced())break;s.pop(),n.warn("Dropping unused function argument {name} [{file}:{line},{col}]",{name:l.name,file:l.start.file,line:l.start.line,col:l.start.col})}if(i instanceof he&&i!==t)return o(i.name.definition(),r)?i:(n.warn("Dropping unused function {name} [{file}:{line},{col}]",{name:i.name.name,file:i.name.start.file,line:i.name.start.line,col:i.name.start.col}),e(Q,i));if(i instanceof Be&&!(c.parent()instanceof ue)){var p=i.definitions.filter(function(e){if(o(e.name.definition(),r))return!0;var t={name:e.name.name,file:e.name.start.file,line:e.name.start.line,col:e.name.start.col};return e.value&&e.value.has_side_effects(n)?(e._unused_side_effects=!0,n.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]",t),!0):(n.warn("Dropping unused variable {name} [{file}:{line},{col}]",t),!1)});p=_(p,function(n,e){return!n.value&&e.value?-1:!e.value&&n.value?1:0});for(var d=[],f=0;f<p.length;){var h=p[f];h._unused_side_effects?(d.push(h.value),p.splice(f,1)):(d.length>0&&(d.push(h.value),h.value=Re.from_array(d),d=[]),++f)}return d=d.length>0?e(Z,i,{body:[e(K,i,{body:Re.from_array(d)})]}):null,0!=p.length||d?0==p.length?d:(i.definitions=p,d&&(d.body.unshift(i),i=d),i):e(Q,i)}if(i instanceof ae&&(a(i,this),i.init instanceof Z)){var m=i.init.body.slice(0,-1);return i.init=i.init.body.slice(-1)[0].body,m.push(i),u?V.splice(m):e(Z,i,{body:m})}return i instanceof ce&&i!==t?i:void 0});t.transform(c)}}),ce.DEFMETHOD("hoist_declarations",function(n){var t=n.option("hoist_funs"),r=n.option("hoist_vars"),i=this;if(t||r){var o=[],u=[],s=new y,c=0,f=0;i.walk(new E(function(n){return n instanceof ce&&n!==i?!0:n instanceof Te?(++f,!0):void 0})),r=r&&f>1;var l=new z(function(n){if(n!==i){if(n instanceof G)return o.push(n),e(Q,n);if(n instanceof he&&t)return u.push(n),e(Q,n);if(n instanceof Te&&r){n.definitions.forEach(function(n){s.set(n.name.name,n),++c});var a=n.to_assignments(),f=l.parent();return f instanceof ue&&f.init===n?null==a?n.definitions[0].name:a:f instanceof ae&&f.init===n?a:a?e(K,n,{body:a}):e(Q,n)}if(n instanceof ce)return n}});if(i=i.transform(l),c>0){var p=[];if(s.each(function(n,e){i instanceof le&&a(function(e){return e.name==n.name.name},i.argnames)?s.del(e):(n=n.clone(),n.value=null,p.push(n),s.set(e,n))}),p.length>0){for(var d=0;d<i.body.length;){if(i.body[d]instanceof K){var _,m,v=i.body[d].body;if(v instanceof Le&&"="==v.operator&&(_=v.left)instanceof Ze&&s.has(_.name)){var g=s.get(_.name);if(g.value)break;g.value=v.right,h(p,g),p.push(g),i.body.splice(d,1);continue}if(v instanceof Re&&(m=v.car)instanceof Le&&"="==m.operator&&(_=m.left)instanceof Ze&&s.has(_.name)){var g=s.get(_.name);if(g.value)break;g.value=m.right,h(p,g),p.push(g),i.body[d].body=v.cdr;continue}}if(i.body[d]instanceof Q)i.body.splice(d,1);else{if(!(i.body[d]instanceof Z))break;var b=[d,1].concat(i.body[d].body);i.body.splice.apply(i.body,b)}}p=e(Te,i,{definitions:p}),u.push(p)}}i.body=o.concat(u,i.body)}return i}),n(K,function(n,t){return t.option("side_effects")&&!n.body.has_side_effects(t)?(t.warn("Dropping side-effect-free statement [{file}:{line},{col}]",n.start),e(Q,n)):n}),n(re,function(n,t){var r=n.condition.evaluate(t);if(n.condition=r[0],!t.option("loops"))return n;if(r.length>1){if(r[1])return e(ae,n,{body:n.body});if(n instanceof oe&&t.option("dead_code")){var i=[];return c(t,n.body,i),e(Z,n,{body:i})}}return n}),n(oe,function(n,t){return t.option("loops")?(n=re.prototype.optimize.call(n,t),n instanceof oe&&(v(n,t),n=e(ae,n,n).transform(t)),n):n}),n(ae,function(n,t){var r=n.condition;if(r&&(r=r.evaluate(t),n.condition=r[0]),!t.option("loops"))return n;if(r&&r.length>1&&!r[1]&&t.option("dead_code")){var i=[];return n.init instanceof Y?i.push(n.init):n.init&&i.push(e(K,n.init,{body:n.init})),c(t,n.body,i),e(Z,n,{body:i})}return v(n,t),n}),n(we,function(n,t){if(!t.option("conditionals"))return n;var r=n.condition.evaluate(t);if(n.condition=r[0],r.length>1)if(r[1]){if(t.warn("Condition always true [{file}:{line},{col}]",n.condition.start),t.option("dead_code")){var o=[];return n.alternative&&c(t,n.alternative,o),o.push(n.body),e(Z,n,{body:o}).transform(t)}}else if(t.warn("Condition always false [{file}:{line},{col}]",n.condition.start),t.option("dead_code")){var o=[];return c(t,n.body,o),n.alternative&&o.push(n.alternative),e(Z,n,{body:o}).transform(t)}i(n.alternative)&&(n.alternative=null);var a=n.condition.negate(t),u=f(n.condition,a)===a;if(n.alternative&&u){u=!1,n.condition=a;var s=n.body;n.body=n.alternative||e(Q),n.alternative=s}if(i(n.body)&&i(n.alternative))return e(K,n.condition,{body:n.condition}).transform(t);if(n.body instanceof K&&n.alternative instanceof K)return e(K,n,{body:e(Ve,n,{condition:n.condition,consequent:n.body.body,alternative:n.alternative.body})}).transform(t);if(i(n.alternative)&&n.body instanceof K)return u?e(K,n,{body:e(Ue,n,{operator:"||",left:a,right:n.body.body})}).transform(t):e(K,n,{body:e(Ue,n,{operator:"&&",left:n.condition,right:n.body.body})}).transform(t);if(n.body instanceof Q&&n.alternative&&n.alternative instanceof K)return e(K,n,{body:e(Ue,n,{operator:"||",left:n.condition,right:n.alternative.body})}).transform(t);if(n.body instanceof me&&n.alternative instanceof me&&n.body.TYPE==n.alternative.TYPE)return e(n.body.CTOR,n,{value:e(Ve,n,{condition:n.condition,consequent:n.body.value||e(gt,n.body).optimize(t),alternative:n.alternative.value||e(gt,n.alternative).optimize(t)})}).transform(t);if(n.body instanceof we&&!n.body.alternative&&!n.alternative&&(n.condition=e(Ue,n.condition,{operator:"&&",left:n.condition,right:n.body.condition}).transform(t),n.body=n.body.body),m(n.body)&&n.alternative){var l=n.alternative;return n.alternative=null,e(Z,n,{body:[n,l]}).transform(t)}if(m(n.alternative)){var p=n.body;return n.body=n.alternative,n.condition=u?a:n.condition.negate(t),n.alternative=null,e(Z,n,{body:[n,p]}).transform(t)}return n}),n(Ee,function(n,t){if(0==n.body.length&&t.option("conditionals"))return e(K,n,{body:n.expression}).transform(t);for(;;){var r=n.body[n.body.length-1];if(r){var i=r.body[r.body.length-1];if(i instanceof ye&&u(t.loopcontrol_target(i.label))===n&&r.body.pop(),r instanceof Fe&&0==r.body.length){n.body.pop();continue}}break}var o=n.expression.evaluate(t);n:if(2==o.length)try{if(n.expression=o[0],!t.option("dead_code"))break n;var a=o[1],s=!1,c=!1,f=!1,l=!1,p=!1,d=new z(function(r,i,o){if(r instanceof le||r instanceof K)return r;if(r instanceof Ee&&r===n)return r=r.clone(),i(r,this),p?r:e(Z,r,{body:r.body.reduce(function(n,e){return n.concat(e.body)},[])}).transform(t);if(r instanceof we||r instanceof Ce){var u=s;return s=!c,i(r,this),s=u,r}if(r instanceof ne||r instanceof Ee){var u=c;return c=!0,i(r,this),c=u,r}if(r instanceof ye&&this.loopcontrol_target(r.label)===n)return s?(p=!0,r):c?r:(l=!0,o?V.skip:e(Q,r));if(r instanceof De&&this.parent()===n){if(l)return V.skip;if(r instanceof Se){var d=r.expression.evaluate(t);if(d.length<2)throw n;return d[1]===a||f?(f=!0,m(r)&&(l=!0),i(r,this),r):V.skip}return i(r,this),r}});d.stack=t.stack.slice(),n=n.transform(d)}catch(h){if(h!==n)throw h}return n}),n(Se,function(n,e){return n.body=s(n.body,e),n}),n(Ce,function(n,e){return n.body=s(n.body,e),n}),Be.DEFMETHOD("remove_initializers",function(){this.definitions.forEach(function(n){n.value=null})}),Be.DEFMETHOD("to_assignments",function(){var n=this.definitions.reduce(function(n,t){if(t.value){var r=e(st,t.name,t.name);n.push(e(Le,t,{operator:"=",left:r,right:t.value}))}return n},[]);return 0==n.length?null:Re.from_array(n)}),n(Be,function(n){return 0==n.definitions.length?e(Q,n):n}),n(de,function(n,e){return n=le.prototype.optimize.call(n,e),e.option("unused")&&n.name&&n.name.unreferenced()&&(n.name=null),n}),n(Me,function(n,r){if(r.option("unsafe")){var i=n.expression;if(i instanceof st&&i.undeclared())switch(i.name){case"Array":if(1!=n.args.length)return e(We,n,{elements:n.args}).transform(r);break;case"Object":if(0==n.args.length)return e(Ye,n,{properties:[]});break;case"String":if(0==n.args.length)return e(pt,n,{value:""});if(n.args.length<=1)return e(Ue,n,{left:n.args[0],operator:"+",right:e(pt,n,{value:""})}).transform(r);break;case"Number":if(0==n.args.length)return e(dt,n,{value:0});if(1==n.args.length)return e(je,n,{expression:n.args[0],operator:"+"}).transform(r);case"Boolean":if(0==n.args.length)return e(wt,n);if(1==n.args.length)return e(je,n,{expression:e(je,null,{expression:n.args[0],operator:"!"}),operator:"!"}).transform(r);break;case"Function":if(b(n.args,function(n){return n instanceof pt}))try{var o="(function("+n.args.slice(0,-1).map(function(n){return n.value}).join(",")+"){"+n.args[n.args.length-1].value+"})()",a=H(o);
+a.figure_out_scope({screw_ie8:r.option("screw_ie8")});var u=new I(r.options);a=a.transform(u),a.figure_out_scope({screw_ie8:r.option("screw_ie8")}),a.mangle_names();var s;try{a.walk(new E(function(n){if(n instanceof le)throw s=n,a}))}catch(c){if(c!==a)throw c}var l=s.argnames.map(function(t,r){return e(pt,n.args[r],{value:t.print_to_string()})}),o=j();return Z.prototype._codegen.call(s,s,o),o=o.toString().replace(/^\{|\}$/g,""),l.push(e(pt,n.args[n.args.length-1],{value:o})),n.args=l,n}catch(c){if(!(c instanceof M))throw console.log(c),c;r.warn("Error parsing code passed to new Function [{file}:{line},{col}]",n.args[n.args.length-1].start),r.warn(c.toString())}}else{if(i instanceof He&&"toString"==i.property&&0==n.args.length)return e(Ue,n,{left:e(pt,n,{value:""}),operator:"+",right:i.expression}).transform(r);if(i instanceof He&&i.expression instanceof We&&"join"==i.property){var p=0==n.args.length?",":n.args[0].evaluate(r)[1];if(null!=p){var d=i.expression.elements.reduce(function(n,e){if(e=e.evaluate(r),0==n.length||1==e.length)n.push(e);else{var i=n[n.length-1];if(2==i.length){var o=""+i[1]+p+e[1];n[n.length-1]=[t(r,o,i[0]),o]}else n.push(e)}return n},[]);if(0==d.length)return e(pt,n,{value:""});if(1==d.length)return d[0][0];if(""==p){var h;return h=d[0][0]instanceof pt||d[1][0]instanceof pt?d.shift()[0]:e(pt,n,{value:""}),d.reduce(function(n,t){return e(Ue,t[0],{operator:"+",left:n,right:t[0]})},h).transform(r)}var _=n.clone();return _.expression=_.expression.clone(),_.expression.expression=_.expression.expression.clone(),_.expression.expression.elements=d.map(function(n){return n[0]}),f(n,_)}}}}return r.option("side_effects")&&n.expression instanceof de&&0==n.args.length&&!J.prototype.has_side_effects.call(n.expression,r)?e(gt,n).transform(r):r.option("drop_console")&&n.expression instanceof qe&&n.expression.expression instanceof st&&"console"==n.expression.expression.name&&n.expression.expression.undeclared()?e(gt,n).transform(r):n.evaluate(r)[0]}),n(Ne,function(n,t){if(t.option("unsafe")){var r=n.expression;if(r instanceof st&&r.undeclared())switch(r.name){case"Object":case"RegExp":case"Function":case"Error":case"Array":return e(Me,n,n).transform(t)}}return n}),n(Re,function(n,t){if(!t.option("side_effects"))return n;if(!n.car.has_side_effects(t)){var r;if(!(n.cdr instanceof st&&"eval"==n.cdr.name&&n.cdr.undeclared()&&(r=t.parent())instanceof Me&&r.expression===n))return n.cdr}if(t.option("cascade")){if(n.car instanceof Le&&!n.car.left.has_side_effects(t)){if(n.car.left.equivalent_to(n.cdr))return n.car;if(n.cdr instanceof Me&&n.cdr.expression.equivalent_to(n.car.left))return n.cdr.expression=n.car,n.cdr}if(!n.car.has_side_effects(t)&&!n.cdr.has_side_effects(t)&&n.car.equivalent_to(n.cdr))return n.car}return n.cdr instanceof je&&"void"==n.cdr.operator&&!n.cdr.expression.has_side_effects(t)?(n.cdr.operator=n.car,n.cdr):n.cdr instanceof gt?e(je,n,{operator:"void",expression:n.car}):n}),Pe.DEFMETHOD("lift_sequences",function(n){if(n.option("sequences")&&this.expression instanceof Re){var e=this.expression,t=e.to_array();return this.expression=t.pop(),t.push(this),e=Re.from_array(t).transform(n)}return this}),n(Ie,function(n,e){return n.lift_sequences(e)}),n(je,function(n,t){n=n.lift_sequences(t);var r=n.expression;if(t.option("booleans")&&t.in_boolean_context()){switch(n.operator){case"!":if(r instanceof je&&"!"==r.operator)return r.expression;break;case"typeof":return t.warn("Boolean expression always true [{file}:{line},{col}]",n.start),e(Et,n)}r instanceof Ue&&"!"==n.operator&&(n=f(n,r.negate(t)))}return n.evaluate(t)[0]}),Ue.DEFMETHOD("lift_sequences",function(n){if(n.option("sequences")){if(this.left instanceof Re){var e=this.left,t=e.to_array();return this.left=t.pop(),t.push(this),e=Re.from_array(t).transform(n)}if(this.right instanceof Re&&this instanceof Le&&!A(this.left,n)){var e=this.right,t=e.to_array();return this.right=t.pop(),t.push(this),e=Re.from_array(t).transform(n)}}return this});var D=g("== === != !== * & | ^");n(Ue,function(n,t){var r=t.has_directive("use asm")?l:function(e,r){if(r||!n.left.has_side_effects(t)&&!n.right.has_side_effects(t)){e&&(n.operator=e);var i=n.left;n.left=n.right,n.right=i}};if(D(n.operator)&&(n.right instanceof lt&&!(n.left instanceof lt)&&(n.left instanceof Ue&&It[n.left.operator]>=It[n.operator]||r(null,!0)),/^[!=]==?$/.test(n.operator))){if(n.left instanceof st&&n.right instanceof Ve){if(n.right.consequent instanceof st&&n.right.consequent.definition()===n.left.definition()){if(/^==/.test(n.operator))return n.right.condition;if(/^!=/.test(n.operator))return n.right.condition.negate(t)}if(n.right.alternative instanceof st&&n.right.alternative.definition()===n.left.definition()){if(/^==/.test(n.operator))return n.right.condition.negate(t);if(/^!=/.test(n.operator))return n.right.condition}}if(n.right instanceof st&&n.left instanceof Ve){if(n.left.consequent instanceof st&&n.left.consequent.definition()===n.right.definition()){if(/^==/.test(n.operator))return n.left.condition;if(/^!=/.test(n.operator))return n.left.condition.negate(t)}if(n.left.alternative instanceof st&&n.left.alternative.definition()===n.right.definition()){if(/^==/.test(n.operator))return n.left.condition.negate(t);if(/^!=/.test(n.operator))return n.left.condition}}}if(n=n.lift_sequences(t),t.option("comparisons"))switch(n.operator){case"===":case"!==":(n.left.is_string(t)&&n.right.is_string(t)||n.left.is_boolean()&&n.right.is_boolean())&&(n.operator=n.operator.substr(0,2));case"==":case"!=":n.left instanceof pt&&"undefined"==n.left.value&&n.right instanceof je&&"typeof"==n.right.operator&&t.option("unsafe")&&(n.right.expression instanceof st&&n.right.expression.undeclared()||(n.right=n.right.expression,n.left=e(gt,n.left).optimize(t),2==n.operator.length&&(n.operator+="=")))}if(t.option("booleans")&&t.in_boolean_context())switch(n.operator){case"&&":var i=n.left.evaluate(t),o=n.right.evaluate(t);if(i.length>1&&!i[1]||o.length>1&&!o[1])return t.warn("Boolean && always false [{file}:{line},{col}]",n.start),e(wt,n);if(i.length>1&&i[1])return o[0];if(o.length>1&&o[1])return i[0];break;case"||":var i=n.left.evaluate(t),o=n.right.evaluate(t);if(i.length>1&&i[1]||o.length>1&&o[1])return t.warn("Boolean || always true [{file}:{line},{col}]",n.start),e(Et,n);if(i.length>1&&!i[1])return o[0];if(o.length>1&&!o[1])return i[0];break;case"+":var i=n.left.evaluate(t),o=n.right.evaluate(t);if(i.length>1&&i[0]instanceof pt&&i[1]||o.length>1&&o[0]instanceof pt&&o[1])return t.warn("+ in boolean context always true [{file}:{line},{col}]",n.start),e(Et,n)}if(t.option("comparisons")){if(!(t.parent()instanceof Ue)||t.parent()instanceof Le){var a=e(je,n,{operator:"!",expression:n.negate(t)});n=f(n,a)}switch(n.operator){case"<":r(">");break;case"<=":r(">=")}}return"+"==n.operator&&n.right instanceof pt&&""===n.right.getValue()&&n.left instanceof Ue&&"+"==n.left.operator&&n.left.is_string(t)?n.left:(t.option("evaluate")&&"+"==n.operator&&(n.left instanceof lt&&n.right instanceof Ue&&"+"==n.right.operator&&n.right.left instanceof lt&&n.right.is_string(t)&&(n=e(Ue,n,{operator:"+",left:e(pt,null,{value:""+n.left.getValue()+n.right.left.getValue(),start:n.left.start,end:n.right.left.end}),right:n.right.right})),n.right instanceof lt&&n.left instanceof Ue&&"+"==n.left.operator&&n.left.right instanceof lt&&n.left.is_string(t)&&(n=e(Ue,n,{operator:"+",left:n.left.left,right:e(pt,null,{value:""+n.left.right.getValue()+n.right.getValue(),start:n.left.right.start,end:n.right.end})})),n.left instanceof Ue&&"+"==n.left.operator&&n.left.is_string(t)&&n.left.right instanceof lt&&n.right instanceof Ue&&"+"==n.right.operator&&n.right.left instanceof lt&&n.right.is_string(t)&&(n=e(Ue,n,{operator:"+",left:e(Ue,n.left,{operator:"+",left:n.left.left,right:e(pt,null,{value:""+n.left.right.getValue()+n.right.left.getValue(),start:n.left.right.start,end:n.right.left.end})}),right:n.right.right}))),n.right instanceof Ue&&n.right.operator==n.operator&&("*"==n.operator||"&&"==n.operator||"||"==n.operator)?(n.left=e(Ue,n.left,{operator:n.operator,left:n.left,right:n.right.left}),n.right=n.right.right,n.transform(t)):n.evaluate(t)[0])}),n(st,function(n,r){if(n.undeclared()){var i=r.option("global_defs");if(i&&i.hasOwnProperty(n.name))return t(r,i[n.name],n);switch(n.name){case"undefined":return e(gt,n);case"NaN":return e(vt,n);case"Infinity":return e(yt,n)}}return n}),n(gt,function(n,t){if(t.option("unsafe")){var r=t.find_parent(ce),i=r.find_variable("undefined");if(i){var o=e(st,n,{name:"undefined",scope:r,thedef:i});return o.reference(),o}}return n});var F=["+","-","/","*","%",">>","<<",">>>","|","^","&"];n(Le,function(n,e){return n=n.lift_sequences(e),"="==n.operator&&n.left instanceof st&&n.right instanceof Ue&&n.right.left instanceof st&&n.right.left.name==n.left.name&&o(n.right.operator,F)&&(n.operator=n.right.operator+"=",n.right=n.right.right),n}),n(Ve,function(n,t){if(!t.option("conditionals"))return n;if(n.condition instanceof Re){var r=n.condition.car;return n.condition=n.condition.cdr,Re.cons(r,n)}var i=n.condition.evaluate(t);if(i.length>1)return i[1]?(t.warn("Condition always true [{file}:{line},{col}]",n.start),n.consequent):(t.warn("Condition always false [{file}:{line},{col}]",n.start),n.alternative);var o=i[0].negate(t);f(i[0],o)===o&&(n=e(Ve,n,{condition:o,consequent:n.alternative,alternative:n.consequent}));var a=n.consequent,u=n.alternative;if(a instanceof Le&&u instanceof Le&&a.operator==u.operator&&a.left.equivalent_to(u.left))return e(Le,n,{operator:a.operator,left:a.left,right:e(Ve,n,{condition:n.condition,consequent:a.right,alternative:u.right})});if(a instanceof Me&&u.TYPE===a.TYPE&&a.args.length==u.args.length&&a.expression.equivalent_to(u.expression)){if(0==a.args.length)return e(Re,n,{car:n.condition,cdr:a});if(1==a.args.length)return a.args[0]=e(Ve,n,{condition:n.condition,consequent:a.args[0],alternative:u.args[0]}),a}return a instanceof Ve&&a.alternative.equivalent_to(u)?e(Ve,n,{condition:e(Ue,n,{left:n.condition,operator:"&&",right:a.condition}),consequent:a.consequent,alternative:u}):n}),n(At,function(n,t){if(t.option("booleans")){var r=t.parent();return r instanceof Ue&&("=="==r.operator||"!="==r.operator)?(t.warn("Non-strict equality against boolean: {operator} {value} [{file}:{line},{col}]",{operator:r.operator,value:n.value,file:r.start.file,line:r.start.line,col:r.start.col}),e(dt,n,{value:+n.value})):e(je,n,{operator:"!",expression:e(dt,n,{value:1-n.value})})}return n}),n(ze,function(n,t){var r=n.property;if(r instanceof pt&&t.option("properties")){if(r=r.getValue(),St(r)?t.option("screw_ie8"):$(r))return e(He,n,{expression:n.expression,property:r}).optimize(t);var i=parseFloat(r);isNaN(i)||i.toString()!=r||(n.property=e(dt,n.property,{value:i}))}return n}),n(He,function(n,e){return n.evaluate(e)[0]}),n(We,w),n(Ye,w),n(ht,w)}(),function(){function n(n){var r="prefix"in n?n.prefix:"UnaryExpression"==n.type?!0:!1;return new(r?je:Ie)({start:e(n),end:t(n),operator:n.operator,expression:i(n.argument)})}function e(n){return new L({file:n.loc&&n.loc.source,line:n.loc&&n.loc.start.line,col:n.loc&&n.loc.start.column,pos:n.start,endpos:n.start})}function t(n){return new L({file:n.loc&&n.loc.source,line:n.loc&&n.loc.end.line,col:n.loc&&n.loc.end.column,pos:n.end,endpos:n.end})}function r(n,r,a){var u="function From_Moz_"+n+"(M){\n";return u+="return new mytype({\nstart: my_start_token(M),\nend: my_end_token(M)",a&&a.split(/\s*,\s*/).forEach(function(n){var e=/([a-z0-9$_]+)(=|@|>|%)([a-z0-9$_]+)/i.exec(n);if(!e)throw new Error("Can't understand property map: "+n);var t="M."+e[1],r=e[2],i=e[3];if(u+=",\n"+i+": ","@"==r)u+=t+".map(from_moz)";else if(">"==r)u+="from_moz("+t+")";else if("="==r)u+=t;else{if("%"!=r)throw new Error("Can't understand operator in propmap: "+n);u+="from_moz("+t+").body"}}),u+="\n})}",u=new Function("mytype","my_start_token","my_end_token","from_moz","return("+u+")")(r,e,t,i),o[n]=u}function i(n){a.push(n);var e=null!=n?o[n.type](n):null;return a.pop(),e}var o={TryStatement:function(n){return new Ce({start:e(n),end:t(n),body:i(n.block).body,bcatch:i(n.handlers?n.handlers[0]:n.handler),bfinally:n.finalizer?new xe(i(n.finalizer)):null})},CatchClause:function(n){return new ke({start:e(n),end:t(n),argname:i(n.param),body:i(n.body).body})},ObjectExpression:function(n){return new Ye({start:e(n),end:t(n),properties:n.properties.map(function(n){var r=n.key,o="Identifier"==r.type?r.name:r.value,a={start:e(r),end:t(n.value),key:o,value:i(n.value)};switch(n.kind){case"init":return new Ge(a);case"set":return a.value.name=i(r),new Ke(a);case"get":return a.value.name=i(r),new Je(a)}})})},SequenceExpression:function(n){return Re.from_array(n.expressions.map(i))},MemberExpression:function(n){return new(n.computed?ze:He)({start:e(n),end:t(n),property:n.computed?i(n.property):n.property.name,expression:i(n.object)})},SwitchCase:function(n){return new(n.test?Se:Fe)({start:e(n),end:t(n),expression:i(n.test),body:n.consequent.map(i)})},Literal:function(n){var r=n.value,i={start:e(n),end:t(n)};if(null===r)return new mt(i);switch(typeof r){case"string":return i.value=r,new pt(i);case"number":return i.value=r,new dt(i);case"boolean":return new(r?Et:wt)(i);default:return i.value=r,new ht(i)}},UnaryExpression:n,UpdateExpression:n,Identifier:function(n){var r=a[a.length-2];return new("this"==n.name?ft:"LabeledStatement"==r.type?ut:"VariableDeclarator"==r.type&&r.id===n?"const"==r.kind?tt:et:"FunctionExpression"==r.type?r.id===n?ot:rt:"FunctionDeclaration"==r.type?r.id===n?it:rt:"CatchClause"==r.type?at:"BreakStatement"==r.type||"ContinueStatement"==r.type?ct:st)({start:e(n),end:t(n),name:n.name})}};r("Node",W),r("Program",fe,"body@body"),r("Function",de,"id>name, params@argnames, body%body"),r("EmptyStatement",Q),r("BlockStatement",Z,"body@body"),r("ExpressionStatement",K,"expression>body"),r("IfStatement",we,"test>condition, consequent>body, alternate>alternative"),r("LabeledStatement",ee,"label>label, body>body"),r("BreakStatement",ye,"label>label"),r("ContinueStatement",Ae,"label>label"),r("WithStatement",se,"object>expression, body>body"),r("SwitchStatement",Ee,"discriminant>expression, cases@body"),r("ReturnStatement",ve,"argument>value"),r("ThrowStatement",ge,"argument>value"),r("WhileStatement",oe,"test>condition, body>body"),r("DoWhileStatement",ie,"test>condition, body>body"),r("ForStatement",ae,"init>init, test>condition, update>step, body>body"),r("ForInStatement",ue,"left>init, right>object, body>body"),r("DebuggerStatement",X),r("FunctionDeclaration",he,"id>name, params@argnames, body%body"),r("VariableDeclaration",Te,"declarations@definitions"),r("VariableDeclarator",Oe,"id>name, init>value"),r("ThisExpression",ft),r("ArrayExpression",We,"elements@elements"),r("FunctionExpression",de,"id>name, params@argnames, body%body"),r("BinaryExpression",Ue,"operator=operator, left>left, right>right"),r("AssignmentExpression",Le,"operator=operator, left>left, right>right"),r("LogicalExpression",Ue,"operator=operator, left>left, right>right"),r("ConditionalExpression",Ve,"test>condition, consequent>consequent, alternate>alternative"),r("NewExpression",Ne,"callee>expression, arguments@args"),r("CallExpression",Me,"callee>expression, arguments@args");var a=null;W.from_mozilla_ast=function(n){var e=a;a=[];var t=i(n);return a=e,t}}(),n.array_to_hash=t,n.slice=r,n.characters=i,n.member=o,n.find_if=a,n.repeat_string=u,n.DefaultsError=s,n.defaults=c,n.merge=f,n.noop=l,n.MAP=V,n.push_uniq=p,n.string_template=d,n.remove=h,n.mergeSort=_,n.set_difference=m,n.set_intersection=v,n.makePredicate=g,n.all=b,n.Dictionary=y,n.DEFNODE=A,n.AST_Token=L,n.AST_Node=W,n.AST_Statement=Y,n.AST_Debugger=X,n.AST_Directive=G,n.AST_SimpleStatement=K,n.walk_body=w,n.AST_Block=J,n.AST_BlockStatement=Z,n.AST_EmptyStatement=Q,n.AST_StatementWithBody=ne,n.AST_LabeledStatement=ee,n.AST_IterationStatement=te,n.AST_DWLoop=re,n.AST_Do=ie,n.AST_While=oe,n.AST_For=ae,n.AST_ForIn=ue,n.AST_With=se,n.AST_Scope=ce,n.AST_Toplevel=fe,n.AST_Lambda=le,n.AST_Accessor=pe,n.AST_Function=de,n.AST_Defun=he,n.AST_Jump=_e,n.AST_Exit=me,n.AST_Return=ve,n.AST_Throw=ge,n.AST_LoopControl=be,n.AST_Break=ye,n.AST_Continue=Ae,n.AST_If=we,n.AST_Switch=Ee,n.AST_SwitchBranch=De,n.AST_Default=Fe,n.AST_Case=Se,n.AST_Try=Ce,n.AST_Catch=ke,n.AST_Finally=xe,n.AST_Definitions=Be,n.AST_Var=Te,n.AST_Const=$e,n.AST_VarDef=Oe,n.AST_Call=Me,n.AST_New=Ne,n.AST_Seq=Re,n.AST_PropAccess=qe,n.AST_Dot=He,n.AST_Sub=ze,n.AST_Unary=Pe,n.AST_UnaryPrefix=je,n.AST_UnaryPostfix=Ie,n.AST_Binary=Ue,n.AST_Conditional=Ve,n.AST_Assign=Le,n.AST_Array=We,n.AST_Object=Ye,n.AST_ObjectProperty=Xe,n.AST_ObjectKeyVal=Ge,n.AST_ObjectSetter=Ke,n.AST_ObjectGetter=Je,n.AST_Symbol=Ze,n.AST_SymbolAccessor=Qe,n.AST_SymbolDeclaration=nt,n.AST_SymbolVar=et,n.AST_SymbolConst=tt,n.AST_SymbolFunarg=rt,n.AST_SymbolDefun=it,n.AST_SymbolLambda=ot,n.AST_SymbolCatch=at,n.AST_Label=ut,n.AST_SymbolRef=st,n.AST_LabelRef=ct,n.AST_This=ft,n.AST_Constant=lt,n.AST_String=pt,n.AST_Number=dt,n.AST_RegExp=ht,n.AST_Atom=_t,n.AST_Null=mt,n.AST_NaN=vt,n.AST_Undefined=gt,n.AST_Hole=bt,n.AST_Infinity=yt,n.AST_Boolean=At,n.AST_False=wt,n.AST_True=Et,n.TreeWalker=E,n.KEYWORDS=Dt,n.KEYWORDS_ATOM=Ft,n.RESERVED_WORDS=St,n.KEYWORDS_BEFORE_EXPRESSION=Ct,n.OPERATOR_CHARS=kt,n.RE_HEX_NUMBER=xt,n.RE_OCT_NUMBER=Bt,n.RE_DEC_NUMBER=Tt,n.OPERATORS=$t,n.WHITESPACE_CHARS=Ot,n.PUNC_BEFORE_EXPRESSION=Mt,n.PUNC_CHARS=Nt,n.REGEXP_MODIFIERS=Rt,n.UNICODE=qt,n.is_letter=D,n.is_digit=F,n.is_alphanumeric_char=S,n.is_unicode_combining_mark=C,n.is_unicode_connector_punctuation=k,n.is_identifier=x,n.is_identifier_start=B,n.is_identifier_char=T,n.is_identifier_string=$,n.parse_js_number=O,n.JS_Parse_Error=M,n.js_error=N,n.is_token=R,n.EX_EOF=Ht,n.tokenizer=q,n.UNARY_PREFIX=zt,n.UNARY_POSTFIX=Pt,n.ASSIGNMENT=jt,n.PRECEDENCE=It,n.STATEMENTS_WITH_LABELS=Ut,n.ATOMIC_START_TOKEN=Vt,n.parse=H,n.TreeTransformer=z,n.SymbolDef=P,n.base54=Lt,n.OutputStream=j,n.Compressor=I,n.SourceMap=U}({},function(){return this}());
\ No newline at end of file
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/index.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/index.html
new file mode 100644
index 0000000..8a91a11
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/demo/index.html
@@ -0,0 +1,357 @@
+<!DOCTYPE html>
+<html lang="en" ng-app="ui.bootstrap.demo" id="top">
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
+    <link rel="icon" href="assets/favicon.ico" />
+    <title>Angular directives for Bootstrap</title>
+    <meta name="description" content="AngularJS (Angular) native directives for Bootstrap. Small footprint (5kB gzipped!), no 3rd party JS dependencies (jQuery, bootstrap JS) required! Widgets: <% demoModules.forEach(function(module) { %><%= module.displayName %>, <% }); %>">
+    <meta name="google-site-verification" content="7lc5HyceLDqpV_6oNHteYFfxDJH7-S3DwnJKtNUKcRg" />
+    <script src="//cdnjs.cloudflare.com/ajax/libs/fastclick/0.6.7/fastclick.min.js"></script>
+    <script src="//cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.0.0/FileSaver.min.js"></script>
+    <script src="//cdnjs.cloudflare.com/ajax/libs/jszip/2.4.0/jszip.min.js"></script>
+    <script src="//ajax.googleapis.com/ajax/libs/angularjs/<%= ngversion %>/angular.min.js"></script>
+    <script src="//ajax.googleapis.com/ajax/libs/angularjs/<%= ngversion %>/angular-animate.min.js"></script>
+    <script src="//ajax.googleapis.com/ajax/libs/angularjs/<%= ngversion %>/angular-touch.min.js"></script>
+    <script src="//ajax.googleapis.com/ajax/libs/angularjs/<%= ngversion %>/angular-sanitize.js"></script>
+    <script src="ui-bootstrap-tpls-<%= pkg.version%>.min.js"></script>
+    <script src="assets/plunker.js"></script>
+    <script src="assets/app.js"></script>
+
+    <link href="//netdna.bootstrapcdn.com/bootstrap/<%= bsversion %>/css/bootstrap.min.css" rel="stylesheet"/>
+    <link rel="stylesheet" href="assets/rainbow.css"/>
+    <link rel="stylesheet" href="assets/demo.css"/>
+    <link rel="author" href="https://github.com/angular-ui/bootstrap/graphs/contributors">
+</head>
+<body class="ng-cloak" ng-controller="MainCtrl">
+<header class="navbar navbar-default navbar-fixed-top navbar-inner">
+    <div class="container">
+        <div class="navbar-header">
+            <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-3" ng-click="isCollapsed = !isCollapsed">
+                <span class="sr-only">Toggle navigation</span>
+                <span class="icon-bar"></span>
+                <span class="icon-bar"></span>
+                <span class="icon-bar"></span>
+            </button>
+            <a class="navbar-brand visible-xs" href="#">UI Bootstrap</a>
+        </div>
+        <nav class="hidden-xs">
+            <ul class="nav navbar-nav">
+                <a href="#top" role="button" class="navbar-brand">
+                    UI Bootstrap
+                </a>
+                <li class="dropdown" uib-dropdown>
+                    <a role="button" class="dropdown-toggle" uib-dropdown-toggle>
+                        Directives <b class="caret"></b>
+                    </a>
+                    <ul class="dropdown-menu">
+                        <% demoModules.forEach(function(module) {  %><li><a href="#<%= module.name %>"><%= module.displayName %></a></li><% }); %>
+                    </ul>
+                </li>
+                <li><a href="#getting_started">Getting started</a></li>
+                <li class="dropdown" uib-dropdown>
+                    <a role="button" class="dropdown-toggle" uib-dropdown-toggle>
+                        Previous docs <b class="caret"></b>
+                    </a>
+                    <ul class="dropdown-menu">
+                        <li ng-repeat="version in oldDocs">
+                            <a ng-href="{{version.url}}">{{version.version}}</a>
+                        </li>
+                    </ul>
+                </li>
+            </ul>
+        </nav>
+        <nav class="visible-xs" uib-collapse="!isCollapsed">
+            <ul class="nav navbar-nav">
+                <li><a href="#getting_started" ng-click="isCollapsed = !isCollapsed">Getting started</a></li>
+                <li><a href="#directives_small" ng-click="isCollapsed = !isCollapsed">Directives</a></li>
+            </ul>
+        </nav>
+    </div>
+</header>
+<div class="header-placeholder"></div>
+<div role="main">
+    <header class="bs-header text-center" id="overview">
+        <div class="container">
+            <h1>
+                UI Bootstrap
+            </h1>
+
+            <p>Bootstrap components written in pure <a href="http://angularjs.org">AngularJS</a> by the <a
+                    href="http://angular-ui.github.io">AngularUI Team</a></p>
+
+            <p>
+                <a class="btn btn-outline-inverse btn-lg" href="https://github.com/angular-ui/bootstrap"><i class="icon-github"></i>Code on Github</a>
+                <button type="button" class="btn btn-outline-inverse btn-lg" ng-click="showDownloadModal()">
+                    <i class="glyphicon glyphicon-download-alt"></i> Download <small>(<%= pkg.version%>)</small>
+                </button>
+                <button type="button" class="btn btn-outline-inverse btn-lg" ng-click="showBuildModal()"><i class="glyphicon glyphicon-wrench"></i> Create a Build</button>
+            </p>
+        </div>
+        <div class="bs-social container">
+            <ul class="bs-social-buttons">
+                <li>
+                    <iframe src="//ghbtns.com/github-btn.html?user=angular-ui&repo=bootstrap&type=watch&count=true"
+                            allowtransparency="true" frameborder="0" scrolling="0" width="110" height="20"></iframe>
+                </li>
+                <li>
+                    <iframe src="//ghbtns.com/github-btn.html?user=angular-ui&repo=bootstrap&type=fork&count=true"
+                            allowtransparency="true" frameborder="0" scrolling="0" width="110" height="20"></iframe>
+                </li>
+                <li>
+                    <a href="https://twitter.com/share" class="twitter-share-button"
+                       data-hashtags="angularjs">Tweet</a>
+                    <script>!function (d, s, id) {
+                        var js, fjs = d.getElementsByTagName(s)[0];
+                        if (!d.getElementById(id)) {
+                            js = d.createElement(s);
+                            js.id = id;
+                            js.src = "//platform.twitter.com/widgets.js";
+                            fjs.parentNode.insertBefore(js, fjs);
+                        }
+                    }(document, "script", "twitter-wjs");</script>
+                </li>
+                <li>
+                    <!-- Place this tag where you want the +1 button to render. -->
+                    <div class="g-plusone" data-size="medium"></div>
+
+                    <!-- Place this tag after the last +1 button tag. -->
+                    <script type="text/javascript">
+                        (function () {
+                            var po = document.createElement('script');
+                            po.type = 'text/javascript';
+                            po.async = true;
+                            po.src = 'https://apis.google.com/js/plusone.js';
+                            var s = document.getElementsByTagName('script')[0];
+                            s.parentNode.insertBefore(po, s);
+                        })();
+                    </script>
+                </li>
+            </ul>
+        </div>
+    </header>
+    <div class="container">
+        <div class="row">
+            <div class="col-md-12">
+                <section class="bs-sidebar visible-xs" id="directives_small">
+                    <ul class="nav bs-sidenav">
+                        <li><a href="#"><strong>Directives</strong></a></li>
+                        <% demoModules.forEach(function(module) {  %>
+                            <li><a href="#<%= module.name %>"><%= module.displayName %></a></li>
+                        <% }); %>
+                    </ul>
+                </section>
+
+                <section id="getting_started">
+                    <div class="page-header">
+                        <h1>Getting started</h1>
+                    </div>
+                    <h3>Dependencies</h3>
+                    <p>
+                        This repository contains a set of <strong>native AngularJS directives</strong> based on
+                        Bootstrap's markup and CSS. As a result no dependency on jQuery or Bootstrap's
+                        JavaScript is required. The <strong>only required dependencies</strong> are:
+                    </p>
+                    <ul>
+                        <li><a href="http://angularjs.org" target="_blank">AngularJS</a> (requires AngularJS 1.3.x, tested with <%= ngversion %>).
+                            0.12.0 is the last version of this library that supports AngularJS 1.2.x.</li>
+                        <li><a href="http://getbootstrap.com" target="_blank">Bootstrap CSS</a> (tested with version <%= bsversion %>).
+                            This version of the library (<%= pkg.version%>) works only with Bootstrap CSS in version 3.x.
+                            0.8.0 is the last version of this library that supports Bootstrap CSS in version 2.3.x.
+                        </li>
+                    </ul>
+                    <h3>Files to download</h3>
+                    <p>
+                        Build files for all directives are distributed in several flavours: minified for production usage, un-minified
+                        for development, with or without templates. All the options are described and can be
+                        <a href="https://github.com/angular-ui/bootstrap/tree/gh-pages">downloaded from here</a>. It should be noted that the <code>-tpls</code> files contain the templates bundled in JavaScript, while the regular version does not contain the bundled templates. For more information, check out the FAQ <a href="https://github.com/angular-ui/bootstrap/wiki/FAQ#full-explanation">here</a> and the README <a href="https://github.com/angular-ui/bootstrap/tree/gh-pages#build-files">here</a>.
+                    </p>
+                    <p>Alternativelly, if you are only interested in a subset of directives, you can
+                        <a ng-click="showBuildModal()">create your own build</a>.
+                    </p>
+                    <p>Whichever method you choose the good news that the overall size of a download is very small:
+                       &lt;76kB for all directives (~20kB with gzip compression!)</p>
+                    <h3>Installation</h3>
+                    <p>As soon as you've got all the files downloaded and included in your page you just need to declare
+                       a dependency on the <code>ui.bootstrap</code> <a href="http://docs.angularjs.org/guide/module">module</a>:<br>
+                       <pre><code>angular.module('myModule', ['ui.bootstrap']);</code></pre>
+                    </p>
+                    <p>You can fork one of the plunkers from this page to see a working example of what is described here.</p>
+                    <h3>CSS</h3>
+                    <p>Original Bootstrap's CSS depends on empty <code>href</code> attributes to style cursors for several components (pagination, tabs etc.).
+                    But in AngularJS adding empty <code>href</code> attributes to link tags will cause unwanted route changes. This is why we need to remove empty <code>href</code> attributes from directive templates and as a result styling is not applied correctly. The remedy is simple, just add the following styling to your application: <pre><code>.nav, .pagination, .carousel, .panel-title a { cursor: pointer; }</code></pre>
+                    </p>
+                    <h3>FAQ</h3>
+                    <p>Please check <a href="https://github.com/angular-ui/bootstrap/wiki/FAQ" target="_blank">our FAQ section</a> for common problems / solutions.</p>
+                    <h3>Reading the documentation</h3>
+                    <p>
+                        Each of the components provided in <code>ui-bootstrap</code> have documentation and interactive Plunker examples.
+                    </p>
+                    <p>
+                        For the directives, we list the different attributes with their default values, and whether they are readonly. In addition to this, some settings
+                        have an eye icon next to them like this <i class="glyphicon glyphicon-eye-open"></i>. The eye means that the setting has an Angular
+                        <a href="https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch" title="Angular $watch" target="_blank">$watch</a> listener applied to it.
+                    </p>
+                    <p>
+                        For the services (you will recognize them with the <code>$</code> prefix), we list all the possible parameters you can pass to them and their default values if any.
+                    </p>
+                    <p>
+                        Also, some components have default values that you can override globally within a <code>.config</code> function for example.
+                    </p>
+                </section>
+                <% demoModules.forEach(function(module) { %>
+                    <section id="<%= module.name %>">
+                        <div class="page-header">
+                          <h1><%= module.displayName %><small>
+                              (<a target="_blank" href="https://github.com/angular-ui/bootstrap/tree/master/src/<%= module.name %>">ui.bootstrap.<%= module.name %></a>)
+                          </small></h1>
+                          </div>
+                          <div class="row">
+                            <div class="col-md-6 show-grid">
+                                <%= module.docs.html %>
+                            </div>
+                            <div class="col-md-6">
+                                <%= module.docs.md %>
+                            </div>
+                        </div>
+                        <hr>
+                        <div class="row code">
+                            <div class="col-md-12" ng-controller="PlunkerCtrl">
+                                <div class="pull-right">
+                                    <button type="button" class="btn btn-info plunk-btn" ng-click="edit('<%= ngversion%>', '<%= bsversion %>', '<%= pkg.version%>', '<%= module.name %>')"><i class="glyphicon glyphicon-edit"></i> Edit in plunker</button>
+                                </div>
+                                <uib-tabset>
+                                    <uib-tab heading="Markup">
+                                        <div plunker-content="markup">
+                                            <pre ng-non-bindable><code data-language="html"><%- module.docs.html %></code></pre>
+                                        </div>
+                                    </uib-tab>
+                                    <uib-tab heading="JavaScript">
+                                        <div plunker-content="javascript">
+                                            <pre ng-non-bindable><code data-language="javascript"><%- module.docs.js %></code></pre>
+                                        </div>
+                                    </uib-tab>
+                                </uib-tabset>
+                            </div>
+                        </div>
+                    </section>
+                    <script><%= module.docs.js %></script>
+                <% }); %>
+            </div>
+        </div>
+    </div><!-- /.container -->
+</div><!-- /.main -->
+<footer class="footer">
+    <div class="container">
+        <p>Designed and built by <a href="https://github.com/angular-ui?tab=members" target="_blank">Angular-UI team</a> and <a href="https://github.com/angular-ui/bootstrap/graphs/contributors" target="_blank">contributors</a>.</p>
+        <p>Code licensed under <a href="https://github.com/angular-ui/bootstrap/blob/master/LICENSE"><%= pkg.license %> License</a>.</p>
+        <p><a href="https://github.com/angular-ui/bootstrap/issues?state=open">Issues</a></p>
+    </div>
+</footer>
+<script src="assets/rainbow.js"></script>
+<script src="assets/rainbow-generic.js"></script>
+<script src="assets/rainbow-javascript.js"></script>
+<script src="assets/rainbow-html.js"></script>
+<script type="text/ng-template" id="downloadModal.html">
+    <div class="modal-header"><h4 class="modal-title">Download Angular UI Bootstrap</h4></div>
+    <div class="modal-body">
+        <form class="form-horizontal">
+          <div class="form-group">
+            <label class="col-sm-3 control-label"><strong>Build</strong></label>
+            <div class="col-sm-9">
+              <span class="btn-group">
+                <button type="button" class="btn btn-default" ng-model="options.minified" uib-btn-radio="true">Minified</button>
+                <button type="button" class="btn btn-default" ng-model="options.minified" uib-btn-radio="false">Uncompressed</button>
+              </span>
+              <small class="help-block">Use <b>Minified</b> version in your deployed application. <b>Uncompressed</b> source code is useful only for debugging and development purpose.</small>
+            </div>
+          </div>
+          <div class="form-group">
+            <label class="col-sm-3 control-label"><strong>Include Templates</strong></label>
+            <div class="col-sm-9">
+              <span class="btn-group">
+                <button type="button" class="btn btn-default" ng-model="options.tpls" uib-btn-radio="true">Yes</button>
+                <button type="button" class="btn btn-default" ng-model="options.tpls" uib-btn-radio="false">No</button>
+              </span>
+              <small class="help-block">Whether you want to include the <i>default templates</i>, bundled with most of the directives. These templates are based on Bootstrap's markup and CSS.</small>
+            </div>
+          </div>
+          <div class="form-group">
+            <label class="col-sm-3 control-label"><strong>Bower</strong></label>
+            <div class="col-sm-9">
+              <small>If you are using Bower just run:</small>
+              <pre style="margin-bottom:0;">bower install angular-bootstrap</pre>
+              <small class="help-block"><a href="http://bower.io/" target="_blank">Bower</a> is a package manager for the web.</small>
+            </div>
+          </div>
+        </form>
+    </div>
+    <div class="modal-footer">
+        <a class="btn btn-default" ng-click="cancel()">Close</a>
+        <a class="btn btn-primary" ng-href="{{download('<%= pkg.version%>')}}" download><i class="glyphicon glyphicon-download-alt"></i> Download <%= pkg.version %></a>
+    </div>
+</script>
+<script type="text/ng-template" id="buildModal.html">
+    <div class="modal-header">
+      <h4>
+      Create a Custom Build
+      <br>
+      <small>Select the modules you wish to download</small>
+      </h4>
+    </div>
+    <div class="modal-body">
+        <div ng-show="isOldBrowser()">
+            Your current browser doesn't support creating custom builds.
+            Please take a second to <a href="http://browsehappy.com/">upgrade to a
+            more modern browser</a> (other than Safari).
+        </div>
+        <div ng-show="buildErrorText">
+            <h4 style="text-align: center;">{{buildErrorText}}</h4>
+        </div>
+        <div ng-hide="buildErrorText || isOldBrowser()">
+            <% modules.forEach(function(module,i) { %>
+              <% if (i % 3 === 0) {%>
+                <div class="btn-group" style="width: 100%;">
+              <% } %>
+              <button type="button" class="btn btn-default"
+               style="width: 33%; border-radius: 0;"
+               ng-class="{'btn-primary': module.<%= module.name %>}"
+               ng-model="module.<%= module.name %>"
+               uib-btn-checkbox
+               ng-change="selectedChanged('<%= module.name %>', module.<%= module.name %>)">
+                  <%= module.displayName %>
+              </button>
+              <% if ((i+1) % 3 === 0) { //end button group%>
+                </div>
+              <% } %>
+            <% }); %>
+        </div>
+    </div>
+    <div class="modal-footer">
+        <a class="btn btn-default" ng-click="cancel()">Close</a>
+        <a class="btn btn-primary" ng-hide="isOldBrowser()"
+            ng-disabled="isOldBrowser() !== false && !selectedModules.length"
+            ng-click="selectedModules.length && build(selectedModules, '<%= pkg.version %>')">
+            <i class="glyphicon glyphicon-download-alt"></i> Download {{selectedModules.length}} Modules
+        </a>
+    </div>
+</script>
+
+<script type="text/javascript">
+
+    var _gaq = _gaq || [];
+    _gaq.push(['_setAccount', 'UA-37467169-1']);
+    _gaq.push(['_trackPageview']);
+
+    (function() {
+        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+    })();
+
+</script>
+<script src="assets/smoothscroll-angular-custom.js"></script>
+<script src="assets/uglifyjs.js"></script>
+</body>
+</html>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/raw-files-generator.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/raw-files-generator.js
new file mode 100644
index 0000000..4e563e6
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/raw-files-generator.js
@@ -0,0 +1,46 @@
+/*!
+ * Forked from:
+ * Bootstrap Grunt task for generating raw-files.min.js for the Customizer
+ * http://getbootstrap.com
+ * Copyright 2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+
+/* jshint node: true */
+
+'use strict';
+var fs = require('fs');
+
+function getFiles(filePaths) {
+  var files = {};
+  filePaths
+    .forEach(function (path) {
+      files[path] = fs.readFileSync(path, 'utf8');
+    });
+  return files;
+}
+
+module.exports = function generateRawFilesJs(grunt, jsFilename, files, banner, cssBanner) {
+  if (!banner) {
+    banner = '';
+  }
+
+  if (!cssBanner) {
+    cssBanner = '';
+  }
+
+  var filesJsObject = {
+    banner: banner,
+    cssBanner: cssBanner,
+    files: getFiles(files),
+  };
+
+  var filesJsContent = JSON.stringify(filesJsObject);
+  try {
+    fs.writeFileSync(jsFilename, filesJsContent);
+  }
+  catch (err) {
+    grunt.fail.warn(err);
+  }
+  grunt.log.writeln('File ' + jsFilename.cyan + ' created.');
+};
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/test-lib/helpers.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/test-lib/helpers.js
new file mode 100644
index 0000000..452c7fd
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/test-lib/helpers.js
@@ -0,0 +1,57 @@
+// jasmine matcher for expecting an element to have a css class
+// https://github.com/angular/angular.js/blob/master/test/matchers.js
+beforeEach(function() {
+  jasmine.addMatchers({
+    toHaveClass: function(util, customEqualityTesters) {
+      return {
+        compare: function(actual, expected) {
+          var result = {
+            pass: actual.hasClass(expected)
+          };
+
+          if (result.pass) {
+            result.message = 'Expected "' + actual + '" not to have the "' + expected + '" class.';
+          } else {
+            result.message = 'Expected "' + actual + '" to have the "' + expected + '" class.';
+          }
+
+          return result;
+        }
+      }
+    },
+    toBeHidden: function(util, customEqualityTesters) {
+      return {
+        compare: function(actual) {
+          var result = {
+            pass: actual.hasClass('ng-hide') || actual.css('display') === 'none'
+          };
+
+          if (result.pass) {
+            result.message = 'Expected "' + actual + '" not to be hidden';
+          } else {
+            result.message = 'Expected "' + actual + '" to be hidden';
+          }
+
+          return result;
+        }
+      }
+    },
+    toHaveFocus: function(util, customEqualityTesters) {
+      return {
+        compare: function(actual) {
+          var result = {
+            pass: document.activeElement === actual[0]
+          };
+
+          if (result.pass) {
+            result.message = 'Expected "' + actual + '" not to have focus';
+          } else {
+            result.message = 'Expected "' + actual + '" to have focus';
+          }
+
+          return result;
+        }
+      }
+    }
+  });
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/test-lib/jquery-1.8.2.min.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/test-lib/jquery-1.8.2.min.js
new file mode 100644
index 0000000..bc3fbc8
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/test-lib/jquery-1.8.2.min.js
@@ -0,0 +1,2 @@
+/*! jQuery v1.8.2 jquery.com | jquery.org/license */
+(function(a,b){function G(a){var b=F[a]={};return p.each(a.split(s),function(a,c){b[c]=!0}),b}function J(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(I,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:+d+""===d?+d:H.test(d)?p.parseJSON(d):d}catch(f){}p.data(a,c,d)}else d=b}return d}function K(a){var b;for(b in a){if(b==="data"&&p.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function ba(){return!1}function bb(){return!0}function bh(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function bi(a,b){do a=a[b];while(a&&a.nodeType!==1);return a}function bj(a,b,c){b=b||0;if(p.isFunction(b))return p.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return p.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=p.grep(a,function(a){return a.nodeType===1});if(be.test(b))return p.filter(b,d,!c);b=p.filter(b,d)}return p.grep(a,function(a,d){return p.inArray(a,b)>=0===c})}function bk(a){var b=bl.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function bC(a,b){return a.getElementsByTagName(b)[0]||a.appendChild(a.ownerDocument.createElement(b))}function bD(a,b){if(b.nodeType!==1||!p.hasData(a))return;var c,d,e,f=p._data(a),g=p._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;d<e;d++)p.event.add(b,c,h[c][d])}g.data&&(g.data=p.extend({},g.data))}function bE(a,b){var c;if(b.nodeType!==1)return;b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase(),c==="object"?(b.parentNode&&(b.outerHTML=a.outerHTML),p.support.html5Clone&&a.innerHTML&&!p.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):c==="input"&&bv.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):c==="option"?b.selected=a.defaultSelected:c==="input"||c==="textarea"?b.defaultValue=a.defaultValue:c==="script"&&b.text!==a.text&&(b.text=a.text),b.removeAttribute(p.expando)}function bF(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bG(a){bv.test(a.type)&&(a.defaultChecked=a.checked)}function bY(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=bW.length;while(e--){b=bW[e]+c;if(b in a)return b}return d}function bZ(a,b){return a=b||a,p.css(a,"display")==="none"||!p.contains(a.ownerDocument,a)}function b$(a,b){var c,d,e=[],f=0,g=a.length;for(;f<g;f++){c=a[f];if(!c.style)continue;e[f]=p._data(c,"olddisplay"),b?(!e[f]&&c.style.display==="none"&&(c.style.display=""),c.style.display===""&&bZ(c)&&(e[f]=p._data(c,"olddisplay",cc(c.nodeName)))):(d=bH(c,"display"),!e[f]&&d!=="none"&&p._data(c,"olddisplay",d))}for(f=0;f<g;f++){c=a[f];if(!c.style)continue;if(!b||c.style.display==="none"||c.style.display==="")c.style.display=b?e[f]||"":"none"}return a}function b_(a,b,c){var d=bP.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function ca(a,b,c,d){var e=c===(d?"border":"content")?4:b==="width"?1:0,f=0;for(;e<4;e+=2)c==="margin"&&(f+=p.css(a,c+bV[e],!0)),d?(c==="content"&&(f-=parseFloat(bH(a,"padding"+bV[e]))||0),c!=="margin"&&(f-=parseFloat(bH(a,"border"+bV[e]+"Width"))||0)):(f+=parseFloat(bH(a,"padding"+bV[e]))||0,c!=="padding"&&(f+=parseFloat(bH(a,"border"+bV[e]+"Width"))||0));return f}function cb(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=!0,f=p.support.boxSizing&&p.css(a,"boxSizing")==="border-box";if(d<=0||d==null){d=bH(a,b);if(d<0||d==null)d=a.style[b];if(bQ.test(d))return d;e=f&&(p.support.boxSizingReliable||d===a.style[b]),d=parseFloat(d)||0}return d+ca(a,b,c||(f?"border":"content"),e)+"px"}function cc(a){if(bS[a])return bS[a];var b=p("<"+a+">").appendTo(e.body),c=b.css("display");b.remove();if(c==="none"||c===""){bI=e.body.appendChild(bI||p.extend(e.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!bJ||!bI.createElement)bJ=(bI.contentWindow||bI.contentDocument).document,bJ.write("<!doctype html><html><body>"),bJ.close();b=bJ.body.appendChild(bJ.createElement(a)),c=bH(b,"display"),e.body.removeChild(bI)}return bS[a]=c,c}function ci(a,b,c,d){var e;if(p.isArray(b))p.each(b,function(b,e){c||ce.test(a)?d(a,e):ci(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&p.type(b)==="object")for(e in b)ci(a+"["+e+"]",b[e],c,d);else d(a,b)}function cz(a){return function(b,c){typeof b!="string"&&(c=b,b="*");var d,e,f,g=b.toLowerCase().split(s),h=0,i=g.length;if(p.isFunction(c))for(;h<i;h++)d=g[h],f=/^\+/.test(d),f&&(d=d.substr(1)||"*"),e=a[d]=a[d]||[],e[f?"unshift":"push"](c)}}function cA(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h,i=a[f],j=0,k=i?i.length:0,l=a===cv;for(;j<k&&(l||!h);j++)h=i[j](c,d,e),typeof h=="string"&&(!l||g[h]?h=b:(c.dataTypes.unshift(h),h=cA(a,c,d,e,h,g)));return(l||!h)&&!g["*"]&&(h=cA(a,c,d,e,"*",g)),h}function cB(a,c){var d,e,f=p.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((f[d]?a:e||(e={}))[d]=c[d]);e&&p.extend(!0,a,e)}function cC(a,c,d){var e,f,g,h,i=a.contents,j=a.dataTypes,k=a.responseFields;for(f in k)f in d&&(c[k[f]]=d[f]);while(j[0]==="*")j.shift(),e===b&&(e=a.mimeType||c.getResponseHeader("content-type"));if(e)for(f in i)if(i[f]&&i[f].test(e)){j.unshift(f);break}if(j[0]in d)g=j[0];else{for(f in d){if(!j[0]||a.converters[f+" "+j[0]]){g=f;break}h||(h=f)}g=g||h}if(g)return g!==j[0]&&j.unshift(g),d[g]}function cD(a,b){var c,d,e,f,g=a.dataTypes.slice(),h=g[0],i={},j=0;a.dataFilter&&(b=a.dataFilter(b,a.dataType));if(g[1])for(c in a.converters)i[c.toLowerCase()]=a.converters[c];for(;e=g[++j];)if(e!=="*"){if(h!=="*"&&h!==e){c=i[h+" "+e]||i["* "+e];if(!c)for(d in i){f=d.split(" ");if(f[1]===e){c=i[h+" "+f[0]]||i["* "+f[0]];if(c){c===!0?c=i[d]:i[d]!==!0&&(e=f[0],g.splice(j--,0,e));break}}}if(c!==!0)if(c&&a["throws"])b=c(b);else try{b=c(b)}catch(k){return{state:"parsererror",error:c?k:"No conversion from "+h+" to "+e}}}h=e}return{state:"success",data:b}}function cL(){try{return new a.XMLHttpRequest}catch(b){}}function cM(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function cU(){return setTimeout(function(){cN=b},0),cN=p.now()}function cV(a,b){p.each(b,function(b,c){var d=(cT[b]||[]).concat(cT["*"]),e=0,f=d.length;for(;e<f;e++)if(d[e].call(a,b,c))return})}function cW(a,b,c){var d,e=0,f=0,g=cS.length,h=p.Deferred().always(function(){delete i.elem}),i=function(){var b=cN||cU(),c=Math.max(0,j.startTime+j.duration-b),d=1-(c/j.duration||0),e=0,f=j.tweens.length;for(;e<f;e++)j.tweens[e].run(d);return h.notifyWith(a,[j,d,c]),d<1&&f?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:p.extend({},b),opts:p.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:cN||cU(),duration:c.duration,tweens:[],createTween:function(b,c,d){var e=p.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(e),e},stop:function(b){var c=0,d=b?j.tweens.length:0;for(;c<d;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;cX(k,j.opts.specialEasing);for(;e<g;e++){d=cS[e].call(j,a,k,j.opts);if(d)return d}return cV(j,k),p.isFunction(j.opts.start)&&j.opts.start.call(a,j),p.fx.timer(p.extend(i,{anim:j,queue:j.opts.queue,elem:a})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}function cX(a,b){var c,d,e,f,g;for(c in a){d=p.camelCase(c),e=b[d],f=a[c],p.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=p.cssHooks[d];if(g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}}function cY(a,b,c){var d,e,f,g,h,i,j,k,l=this,m=a.style,n={},o=[],q=a.nodeType&&bZ(a);c.queue||(j=p._queueHooks(a,"fx"),j.unqueued==null&&(j.unqueued=0,k=j.empty.fire,j.empty.fire=function(){j.unqueued||k()}),j.unqueued++,l.always(function(){l.always(function(){j.unqueued--,p.queue(a,"fx").length||j.empty.fire()})})),a.nodeType===1&&("height"in b||"width"in b)&&(c.overflow=[m.overflow,m.overflowX,m.overflowY],p.css(a,"display")==="inline"&&p.css(a,"float")==="none"&&(!p.support.inlineBlockNeedsLayout||cc(a.nodeName)==="inline"?m.display="inline-block":m.zoom=1)),c.overflow&&(m.overflow="hidden",p.support.shrinkWrapBlocks||l.done(function(){m.overflow=c.overflow[0],m.overflowX=c.overflow[1],m.overflowY=c.overflow[2]}));for(d in b){f=b[d];if(cP.exec(f)){delete b[d];if(f===(q?"hide":"show"))continue;o.push(d)}}g=o.length;if(g){h=p._data(a,"fxshow")||p._data(a,"fxshow",{}),q?p(a).show():l.done(function(){p(a).hide()}),l.done(function(){var b;p.removeData(a,"fxshow",!0);for(b in n)p.style(a,b,n[b])});for(d=0;d<g;d++)e=o[d],i=l.createTween(e,q?h[e]:0),n[e]=h[e]||p.style(a,e),e in h||(h[e]=i.start,q&&(i.end=i.start,i.start=e==="width"||e==="height"?1:0))}}function cZ(a,b,c,d,e){return new cZ.prototype.init(a,b,c,d,e)}function c$(a,b){var c,d={height:a},e=0;b=b?1:0;for(;e<4;e+=2-b)c=bV[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function da(a){return p.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}var c,d,e=a.document,f=a.location,g=a.navigator,h=a.jQuery,i=a.$,j=Array.prototype.push,k=Array.prototype.slice,l=Array.prototype.indexOf,m=Object.prototype.toString,n=Object.prototype.hasOwnProperty,o=String.prototype.trim,p=function(a,b){return new p.fn.init(a,b,c)},q=/[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,r=/\S/,s=/\s+/,t=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,u=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,y=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,z=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,A=/^-ms-/,B=/-([\da-z])/gi,C=function(a,b){return(b+"").toUpperCase()},D=function(){e.addEventListener?(e.removeEventListener("DOMContentLoaded",D,!1),p.ready()):e.readyState==="complete"&&(e.detachEvent("onreadystatechange",D),p.ready())},E={};p.fn=p.prototype={constructor:p,init:function(a,c,d){var f,g,h,i;if(!a)return this;if(a.nodeType)return this.context=this[0]=a,this.length=1,this;if(typeof a=="string"){a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3?f=[null,a,null]:f=u.exec(a);if(f&&(f[1]||!c)){if(f[1])return c=c instanceof p?c[0]:c,i=c&&c.nodeType?c.ownerDocument||c:e,a=p.parseHTML(f[1],i,!0),v.test(f[1])&&p.isPlainObject(c)&&this.attr.call(a,c,!0),p.merge(this,a);g=e.getElementById(f[2]);if(g&&g.parentNode){if(g.id!==f[2])return d.find(a);this.length=1,this[0]=g}return this.context=e,this.selector=a,this}return!c||c.jquery?(c||d).find(a):this.constructor(c).find(a)}return p.isFunction(a)?d.ready(a):(a.selector!==b&&(this.selector=a.selector,this.context=a.context),p.makeArray(a,this))},selector:"",jquery:"1.8.2",length:0,size:function(){return this.length},toArray:function(){return k.call(this)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=p.merge(this.constructor(),a);return d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")"),d},each:function(a,b){return p.each(this,a,b)},ready:function(a){return p.ready.promise().done(a),this},eq:function(a){return a=+a,a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(k.apply(this,arguments),"slice",k.call(arguments).join(","))},map:function(a){return this.pushStack(p.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:j,sort:[].sort,splice:[].splice},p.fn.init.prototype=p.fn,p.extend=p.fn.extend=function(){var a,c,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;typeof h=="boolean"&&(k=h,h=arguments[1]||{},i=2),typeof h!="object"&&!p.isFunction(h)&&(h={}),j===i&&(h=this,--i);for(;i<j;i++)if((a=arguments[i])!=null)for(c in a){d=h[c],e=a[c];if(h===e)continue;k&&e&&(p.isPlainObject(e)||(f=p.isArray(e)))?(f?(f=!1,g=d&&p.isArray(d)?d:[]):g=d&&p.isPlainObject(d)?d:{},h[c]=p.extend(k,g,e)):e!==b&&(h[c]=e)}return h},p.extend({noConflict:function(b){return a.$===p&&(a.$=i),b&&a.jQuery===p&&(a.jQuery=h),p},isReady:!1,readyWait:1,holdReady:function(a){a?p.readyWait++:p.ready(!0)},ready:function(a){if(a===!0?--p.readyWait:p.isReady)return;if(!e.body)return setTimeout(p.ready,1);p.isReady=!0;if(a!==!0&&--p.readyWait>0)return;d.resolveWith(e,[p]),p.fn.trigger&&p(e).trigger("ready").off("ready")},isFunction:function(a){return p.type(a)==="function"},isArray:Array.isArray||function(a){return p.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):E[m.call(a)]||"object"},isPlainObject:function(a){if(!a||p.type(a)!=="object"||a.nodeType||p.isWindow(a))return!1;try{if(a.constructor&&!n.call(a,"constructor")&&!n.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||n.call(a,d)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},error:function(a){throw new Error(a)},parseHTML:function(a,b,c){var d;return!a||typeof a!="string"?null:(typeof b=="boolean"&&(c=b,b=0),b=b||e,(d=v.exec(a))?[b.createElement(d[1])]:(d=p.buildFragment([a],b,c?null:[]),p.merge([],(d.cacheable?p.clone(d.fragment):d.fragment).childNodes)))},parseJSON:function(b){if(!b||typeof b!="string")return null;b=p.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(w.test(b.replace(y,"@").replace(z,"]").replace(x,"")))return(new Function("return "+b))();p.error("Invalid JSON: "+b)},parseXML:function(c){var d,e;if(!c||typeof c!="string")return null;try{a.DOMParser?(e=new DOMParser,d=e.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(f){d=b}return(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&p.error("Invalid XML: "+c),d},noop:function(){},globalEval:function(b){b&&r.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(A,"ms-").replace(B,C)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,c,d){var e,f=0,g=a.length,h=g===b||p.isFunction(a);if(d){if(h){for(e in a)if(c.apply(a[e],d)===!1)break}else for(;f<g;)if(c.apply(a[f++],d)===!1)break}else if(h){for(e in a)if(c.call(a[e],e,a[e])===!1)break}else for(;f<g;)if(c.call(a[f],f,a[f++])===!1)break;return a},trim:o&&!o.call(" ")?function(a){return a==null?"":o.call(a)}:function(a){return a==null?"":(a+"").replace(t,"")},makeArray:function(a,b){var c,d=b||[];return a!=null&&(c=p.type(a),a.length==null||c==="string"||c==="function"||c==="regexp"||p.isWindow(a)?j.call(d,a):p.merge(d,a)),d},inArray:function(a,b,c){var d;if(b){if(l)return l.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=c.length,e=a.length,f=0;if(typeof d=="number")for(;f<d;f++)a[e++]=c[f];else while(c[f]!==b)a[e++]=c[f++];return a.length=e,a},grep:function(a,b,c){var d,e=[],f=0,g=a.length;c=!!c;for(;f<g;f++)d=!!b(a[f],f),c!==d&&e.push(a[f]);return e},map:function(a,c,d){var e,f,g=[],h=0,i=a.length,j=a instanceof p||i!==b&&typeof i=="number"&&(i>0&&a[0]&&a[i-1]||i===0||p.isArray(a));if(j)for(;h<i;h++)e=c(a[h],h,d),e!=null&&(g[g.length]=e);else for(f in a)e=c(a[f],f,d),e!=null&&(g[g.length]=e);return g.concat.apply([],g)},guid:1,proxy:function(a,c){var d,e,f;return typeof c=="string"&&(d=a[c],c=a,a=d),p.isFunction(a)?(e=k.call(arguments,2),f=function(){return a.apply(c,e.concat(k.call(arguments)))},f.guid=a.guid=a.guid||p.guid++,f):b},access:function(a,c,d,e,f,g,h){var i,j=d==null,k=0,l=a.length;if(d&&typeof d=="object"){for(k in d)p.access(a,c,k,d[k],1,g,e);f=1}else if(e!==b){i=h===b&&p.isFunction(e),j&&(i?(i=c,c=function(a,b,c){return i.call(p(a),c)}):(c.call(a,e),c=null));if(c)for(;k<l;k++)c(a[k],d,i?e.call(a[k],k,c(a[k],d)):e,h);f=1}return f?a:j?c.call(a):l?c(a[0],d):g},now:function(){return(new Date).getTime()}}),p.ready.promise=function(b){if(!d){d=p.Deferred();if(e.readyState==="complete")setTimeout(p.ready,1);else if(e.addEventListener)e.addEventListener("DOMContentLoaded",D,!1),a.addEventListener("load",p.ready,!1);else{e.attachEvent("onreadystatechange",D),a.attachEvent("onload",p.ready);var c=!1;try{c=a.frameElement==null&&e.documentElement}catch(f){}c&&c.doScroll&&function g(){if(!p.isReady){try{c.doScroll("left")}catch(a){return setTimeout(g,50)}p.ready()}}()}}return d.promise(b)},p.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){E["[object "+b+"]"]=b.toLowerCase()}),c=p(e);var F={};p.Callbacks=function(a){a=typeof a=="string"?F[a]||G(a):p.extend({},a);var c,d,e,f,g,h,i=[],j=!a.once&&[],k=function(b){c=a.memory&&b,d=!0,h=f||0,f=0,g=i.length,e=!0;for(;i&&h<g;h++)if(i[h].apply(b[0],b[1])===!1&&a.stopOnFalse){c=!1;break}e=!1,i&&(j?j.length&&k(j.shift()):c?i=[]:l.disable())},l={add:function(){if(i){var b=i.length;(function d(b){p.each(b,function(b,c){var e=p.type(c);e==="function"&&(!a.unique||!l.has(c))?i.push(c):c&&c.length&&e!=="string"&&d(c)})})(arguments),e?g=i.length:c&&(f=b,k(c))}return this},remove:function(){return i&&p.each(arguments,function(a,b){var c;while((c=p.inArray(b,i,c))>-1)i.splice(c,1),e&&(c<=g&&g--,c<=h&&h--)}),this},has:function(a){return p.inArray(a,i)>-1},empty:function(){return i=[],this},disable:function(){return i=j=c=b,this},disabled:function(){return!i},lock:function(){return j=b,c||l.disable(),this},locked:function(){return!j},fireWith:function(a,b){return b=b||[],b=[a,b.slice?b.slice():b],i&&(!d||j)&&(e?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!d}};return l},p.extend({Deferred:function(a){var b=[["resolve","done",p.Callbacks("once memory"),"resolved"],["reject","fail",p.Callbacks("once memory"),"rejected"],["notify","progress",p.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return p.Deferred(function(c){p.each(b,function(b,d){var f=d[0],g=a[b];e[d[1]](p.isFunction(g)?function(){var a=g.apply(this,arguments);a&&p.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f+"With"](this===e?c:this,[a])}:c[f])}),a=null}).promise()},promise:function(a){return a!=null?p.extend(a,d):d}},e={};return d.pipe=d.then,p.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[a^1][2].disable,b[2][2].lock),e[f[0]]=g.fire,e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=k.call(arguments),d=c.length,e=d!==1||a&&p.isFunction(a.promise)?d:0,f=e===1?a:p.Deferred(),g=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?k.call(arguments):d,c===h?f.notifyWith(b,c):--e||f.resolveWith(b,c)}},h,i,j;if(d>1){h=new Array(d),i=new Array(d),j=new Array(d);for(;b<d;b++)c[b]&&p.isFunction(c[b].promise)?c[b].promise().done(g(b,j,c)).fail(f.reject).progress(g(b,i,h)):--e}return e||f.resolveWith(j,c),f.promise()}}),p.support=function(){var b,c,d,f,g,h,i,j,k,l,m,n=e.createElement("div");n.setAttribute("className","t"),n.innerHTML="  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",c=n.getElementsByTagName("*"),d=n.getElementsByTagName("a")[0],d.style.cssText="top:1px;float:left;opacity:.5";if(!c||!c.length)return{};f=e.createElement("select"),g=f.appendChild(e.createElement("option")),h=n.getElementsByTagName("input")[0],b={leadingWhitespace:n.firstChild.nodeType===3,tbody:!n.getElementsByTagName("tbody").length,htmlSerialize:!!n.getElementsByTagName("link").length,style:/top/.test(d.getAttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.5/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:n.className!=="t",enctype:!!e.createElement("form").enctype,html5Clone:e.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",boxModel:e.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},h.checked=!0,b.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,b.optDisabled=!g.disabled;try{delete n.test}catch(o){b.deleteExpando=!1}!n.addEventListener&&n.attachEvent&&n.fireEvent&&(n.attachEvent("onclick",m=function(){b.noCloneEvent=!1}),n.cloneNode(!0).fireEvent("onclick"),n.detachEvent("onclick",m)),h=e.createElement("input"),h.value="t",h.setAttribute("type","radio"),b.radioValue=h.value==="t",h.setAttribute("checked","checked"),h.setAttribute("name","t"),n.appendChild(h),i=e.createDocumentFragment(),i.appendChild(n.lastChild),b.checkClone=i.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=h.checked,i.removeChild(h),i.appendChild(n);if(n.attachEvent)for(k in{submit:!0,change:!0,focusin:!0})j="on"+k,l=j in n,l||(n.setAttribute(j,"return;"),l=typeof n[j]=="function"),b[k+"Bubbles"]=l;return p(function(){var c,d,f,g,h="padding:0;margin:0;border:0;display:block;overflow:hidden;",i=e.getElementsByTagName("body")[0];if(!i)return;c=e.createElement("div"),c.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",i.insertBefore(c,i.firstChild),d=e.createElement("div"),c.appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",f=d.getElementsByTagName("td"),f[0].style.cssText="padding:0;margin:0;border:0;display:none",l=f[0].offsetHeight===0,f[0].style.display="",f[1].style.display="none",b.reliableHiddenOffsets=l&&f[0].offsetHeight===0,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",b.boxSizing=d.offsetWidth===4,b.doesNotIncludeMarginInBodyOffset=i.offsetTop!==1,a.getComputedStyle&&(b.pixelPosition=(a.getComputedStyle(d,null)||{}).top!=="1%",b.boxSizingReliable=(a.getComputedStyle(d,null)||{width:"4px"}).width==="4px",g=e.createElement("div"),g.style.cssText=d.style.cssText=h,g.style.marginRight=g.style.width="0",d.style.width="1px",d.appendChild(g),b.reliableMarginRight=!parseFloat((a.getComputedStyle(g,null)||{}).marginRight)),typeof d.style.zoom!="undefined"&&(d.innerHTML="",d.style.cssText=h+"width:1px;padding:1px;display:inline;zoom:1",b.inlineBlockNeedsLayout=d.offsetWidth===3,d.style.display="block",d.style.overflow="visible",d.innerHTML="<div></div>",d.firstChild.style.width="5px",b.shrinkWrapBlocks=d.offsetWidth!==3,c.style.zoom=1),i.removeChild(c),c=d=f=g=null}),i.removeChild(n),c=d=f=g=h=i=n=null,b}();var H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,I=/([A-Z])/g;p.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(p.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){return a=a.nodeType?p.cache[a[p.expando]]:a[p.expando],!!a&&!K(a)},data:function(a,c,d,e){if(!p.acceptData(a))return;var f,g,h=p.expando,i=typeof c=="string",j=a.nodeType,k=j?p.cache:a,l=j?a[h]:a[h]&&h;if((!l||!k[l]||!e&&!k[l].data)&&i&&d===b)return;l||(j?a[h]=l=p.deletedIds.pop()||p.guid++:l=h),k[l]||(k[l]={},j||(k[l].toJSON=p.noop));if(typeof c=="object"||typeof c=="function")e?k[l]=p.extend(k[l],c):k[l].data=p.extend(k[l].data,c);return f=k[l],e||(f.data||(f.data={}),f=f.data),d!==b&&(f[p.camelCase(c)]=d),i?(g=f[c],g==null&&(g=f[p.camelCase(c)])):g=f,g},removeData:function(a,b,c){if(!p.acceptData(a))return;var d,e,f,g=a.nodeType,h=g?p.cache:a,i=g?a[p.expando]:p.expando;if(!h[i])return;if(b){d=c?h[i]:h[i].data;if(d){p.isArray(b)||(b in d?b=[b]:(b=p.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,f=b.length;e<f;e++)delete d[b[e]];if(!(c?K:p.isEmptyObject)(d))return}}if(!c){delete h[i].data;if(!K(h[i]))return}g?p.cleanData([a],!0):p.support.deleteExpando||h!=h.window?delete h[i]:h[i]=null},_data:function(a,b,c){return p.data(a,b,c,!0)},acceptData:function(a){var b=a.nodeName&&p.noData[a.nodeName.toLowerCase()];return!b||b!==!0&&a.getAttribute("classid")===b}}),p.fn.extend({data:function(a,c){var d,e,f,g,h,i=this[0],j=0,k=null;if(a===b){if(this.length){k=p.data(i);if(i.nodeType===1&&!p._data(i,"parsedAttrs")){f=i.attributes;for(h=f.length;j<h;j++)g=f[j].name,g.indexOf("data-")||(g=p.camelCase(g.substring(5)),J(i,g,k[g]));p._data(i,"parsedAttrs",!0)}}return k}return typeof a=="object"?this.each(function(){p.data(this,a)}):(d=a.split(".",2),d[1]=d[1]?"."+d[1]:"",e=d[1]+"!",p.access(this,function(c){if(c===b)return k=this.triggerHandler("getData"+e,[d[0]]),k===b&&i&&(k=p.data(i,a),k=J(i,a,k)),k===b&&d[1]?this.data(d[0]):k;d[1]=c,this.each(function(){var b=p(this);b.triggerHandler("setData"+e,d),p.data(this,a,c),b.triggerHandler("changeData"+e,d)})},null,c,arguments.length>1,null,!1))},removeData:function(a){return this.each(function(){p.removeData(this,a)})}}),p.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=p._data(a,b),c&&(!d||p.isArray(c)?d=p._data(a,b,p.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=p.queue(a,b),d=c.length,e=c.shift(),f=p._queueHooks(a,b),g=function(){p.dequeue(a,b)};e==="inprogress"&&(e=c.shift(),d--),e&&(b==="fx"&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return p._data(a,c)||p._data(a,c,{empty:p.Callbacks("once memory").add(function(){p.removeData(a,b+"queue",!0),p.removeData(a,c,!0)})})}}),p.fn.extend({queue:function(a,c){var d=2;return typeof a!="string"&&(c=a,a="fx",d--),arguments.length<d?p.queue(this[0],a):c===b?this:this.each(function(){var b=p.queue(this,a,c);p._queueHooks(this,a),a==="fx"&&b[0]!=="inprogress"&&p.dequeue(this,a)})},dequeue:function(a){return this.each(function(){p.dequeue(this,a)})},delay:function(a,b){return a=p.fx?p.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){var d,e=1,f=p.Deferred(),g=this,h=this.length,i=function(){--e||f.resolveWith(g,[g])};typeof a!="string"&&(c=a,a=b),a=a||"fx";while(h--)d=p._data(g[h],a+"queueHooks"),d&&d.empty&&(e++,d.empty.add(i));return i(),f.promise(c)}});var L,M,N,O=/[\t\r\n]/g,P=/\r/g,Q=/^(?:button|input)$/i,R=/^(?:button|input|object|select|textarea)$/i,S=/^a(?:rea|)$/i,T=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,U=p.support.getSetAttribute;p.fn.extend({attr:function(a,b){return p.access(this,p.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){p.removeAttr(this,a)})},prop:function(a,b){return p.access(this,p.prop,a,b,arguments.length>1)},removeProp:function(a){return a=p.propFix[a]||a,this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,f,g,h;if(p.isFunction(a))return this.each(function(b){p(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(s);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{f=" "+e.className+" ";for(g=0,h=b.length;g<h;g++)f.indexOf(" "+b[g]+" ")<0&&(f+=b[g]+" ");e.className=p.trim(f)}}}return this},removeClass:function(a){var c,d,e,f,g,h,i;if(p.isFunction(a))return this.each(function(b){p(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(s);for(h=0,i=this.length;h<i;h++){e=this[h];if(e.nodeType===1&&e.className){d=(" "+e.className+" ").replace(O," ");for(f=0,g=c.length;f<g;f++)while(d.indexOf(" "+c[f]+" ")>=0)d=d.replace(" "+c[f]+" "," ");e.className=a?p.trim(d):""}}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";return p.isFunction(a)?this.each(function(c){p(this).toggleClass(a.call(this,c,this.className,b),b)}):this.each(function(){if(c==="string"){var e,f=0,g=p(this),h=b,i=a.split(s);while(e=i[f++])h=d?h:!g.hasClass(e),g[h?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&p._data(this,"__className__",this.className),this.className=this.className||a===!1?"":p._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(O," ").indexOf(b)>=0)return!0;return!1},val:function(a){var c,d,e,f=this[0];if(!arguments.length){if(f)return c=p.valHooks[f.type]||p.valHooks[f.nodeName.toLowerCase()],c&&"get"in c&&(d=c.get(f,"value"))!==b?d:(d=f.value,typeof d=="string"?d.replace(P,""):d==null?"":d);return}return e=p.isFunction(a),this.each(function(d){var f,g=p(this);if(this.nodeType!==1)return;e?f=a.call(this,d,g.val()):f=a,f==null?f="":typeof f=="number"?f+="":p.isArray(f)&&(f=p.map(f,function(a){return a==null?"":a+""})),c=p.valHooks[this.type]||p.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,f,"value")===b)this.value=f})}}),p.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,f=a.selectedIndex,g=[],h=a.options,i=a.type==="select-one";if(f<0)return null;c=i?f:0,d=i?f+1:h.length;for(;c<d;c++){e=h[c];if(e.selected&&(p.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!p.nodeName(e.parentNode,"optgroup"))){b=p(e).val();if(i)return b;g.push(b)}}return i&&!g.length&&h.length?p(h[f]).val():g},set:function(a,b){var c=p.makeArray(b);return p(a).find("option").each(function(){this.selected=p.inArray(p(this).val(),c)>=0}),c.length||(a.selectedIndex=-1),c}}},attrFn:{},attr:function(a,c,d,e){var f,g,h,i=a.nodeType;if(!a||i===3||i===8||i===2)return;if(e&&p.isFunction(p.fn[c]))return p(a)[c](d);if(typeof a.getAttribute=="undefined")return p.prop(a,c,d);h=i!==1||!p.isXMLDoc(a),h&&(c=c.toLowerCase(),g=p.attrHooks[c]||(T.test(c)?M:L));if(d!==b){if(d===null){p.removeAttr(a,c);return}return g&&"set"in g&&h&&(f=g.set(a,d,c))!==b?f:(a.setAttribute(c,d+""),d)}return g&&"get"in g&&h&&(f=g.get(a,c))!==null?f:(f=a.getAttribute(c),f===null?b:f)},removeAttr:function(a,b){var c,d,e,f,g=0;if(b&&a.nodeType===1){d=b.split(s);for(;g<d.length;g++)e=d[g],e&&(c=p.propFix[e]||e,f=T.test(e),f||p.attr(a,e,""),a.removeAttribute(U?e:c),f&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(Q.test(a.nodeName)&&a.parentNode)p.error("type property can't be changed");else if(!p.support.radioValue&&b==="radio"&&p.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}},value:{get:function(a,b){return L&&p.nodeName(a,"button")?L.get(a,b):b in a?a.value:null},set:function(a,b,c){if(L&&p.nodeName(a,"button"))return L.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,f,g,h=a.nodeType;if(!a||h===3||h===8||h===2)return;return g=h!==1||!p.isXMLDoc(a),g&&(c=p.propFix[c]||c,f=p.propHooks[c]),d!==b?f&&"set"in f&&(e=f.set(a,d,c))!==b?e:a[c]=d:f&&"get"in f&&(e=f.get(a,c))!==null?e:a[c]},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):R.test(a.nodeName)||S.test(a.nodeName)&&a.href?0:b}}}}),M={get:function(a,c){var d,e=p.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;return b===!1?p.removeAttr(a,c):(d=p.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase())),c}},U||(N={name:!0,id:!0,coords:!0},L=p.valHooks.button={get:function(a,c){var d;return d=a.getAttributeNode(c),d&&(N[c]?d.value!=="":d.specified)?d.value:b},set:function(a,b,c){var d=a.getAttributeNode(c);return d||(d=e.createAttribute(c),a.setAttributeNode(d)),d.value=b+""}},p.each(["width","height"],function(a,b){p.attrHooks[b]=p.extend(p.attrHooks[b],{set:function(a,c){if(c==="")return a.setAttribute(b,"auto"),c}})}),p.attrHooks.contenteditable={get:L.get,set:function(a,b,c){b===""&&(b="false"),L.set(a,b,c)}}),p.support.hrefNormalized||p.each(["href","src","width","height"],function(a,c){p.attrHooks[c]=p.extend(p.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),p.support.style||(p.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=b+""}}),p.support.optSelected||(p.propHooks.selected=p.extend(p.propHooks.selected,{get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}})),p.support.enctype||(p.propFix.enctype="encoding"),p.support.checkOn||p.each(["radio","checkbox"],function(){p.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),p.each(["radio","checkbox"],function(){p.valHooks[this]=p.extend(p.valHooks[this],{set:function(a,b){if(p.isArray(b))return a.checked=p.inArray(p(a).val(),b)>=0}})});var V=/^(?:textarea|input|select)$/i,W=/^([^\.]*|)(?:\.(.+)|)$/,X=/(?:^|\s)hover(\.\S+|)\b/,Y=/^key/,Z=/^(?:mouse|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=function(a){return p.event.special.hover?a:a.replace(X,"mouseenter$1 mouseleave$1")};p.event={add:function(a,c,d,e,f){var g,h,i,j,k,l,m,n,o,q,r;if(a.nodeType===3||a.nodeType===8||!c||!d||!(g=p._data(a)))return;d.handler&&(o=d,d=o.handler,f=o.selector),d.guid||(d.guid=p.guid++),i=g.events,i||(g.events=i={}),h=g.handle,h||(g.handle=h=function(a){return typeof p!="undefined"&&(!a||p.event.triggered!==a.type)?p.event.dispatch.apply(h.elem,arguments):b},h.elem=a),c=p.trim(_(c)).split(" ");for(j=0;j<c.length;j++){k=W.exec(c[j])||[],l=k[1],m=(k[2]||"").split(".").sort(),r=p.event.special[l]||{},l=(f?r.delegateType:r.bindType)||l,r=p.event.special[l]||{},n=p.extend({type:l,origType:k[1],data:e,handler:d,guid:d.guid,selector:f,needsContext:f&&p.expr.match.needsContext.test(f),namespace:m.join(".")},o),q=i[l];if(!q){q=i[l]=[],q.delegateCount=0;if(!r.setup||r.setup.call(a,e,m,h)===!1)a.addEventListener?a.addEventListener(l,h,!1):a.attachEvent&&a.attachEvent("on"+l,h)}r.add&&(r.add.call(a,n),n.handler.guid||(n.handler.guid=d.guid)),f?q.splice(q.delegateCount++,0,n):q.push(n),p.event.global[l]=!0}a=null},global:{},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,q,r=p.hasData(a)&&p._data(a);if(!r||!(m=r.events))return;b=p.trim(_(b||"")).split(" ");for(f=0;f<b.length;f++){g=W.exec(b[f])||[],h=i=g[1],j=g[2];if(!h){for(h in m)p.event.remove(a,h+b[f],c,d,!0);continue}n=p.event.special[h]||{},h=(d?n.delegateType:n.bindType)||h,o=m[h]||[],k=o.length,j=j?new RegExp("(^|\\.)"+j.split(".").sort().join("\\.(?:.*\\.|)")+"(\\.|$)"):null;for(l=0;l<o.length;l++)q=o[l],(e||i===q.origType)&&(!c||c.guid===q.guid)&&(!j||j.test(q.namespace))&&(!d||d===q.selector||d==="**"&&q.selector)&&(o.splice(l--,1),q.selector&&o.delegateCount--,n.remove&&n.remove.call(a,q));o.length===0&&k!==o.length&&((!n.teardown||n.teardown.call(a,j,r.handle)===!1)&&p.removeEvent(a,h,r.handle),delete m[h])}p.isEmptyObject(m)&&(delete r.handle,p.removeData(a,"events",!0))},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,f,g){if(!f||f.nodeType!==3&&f.nodeType!==8){var h,i,j,k,l,m,n,o,q,r,s=c.type||c,t=[];if($.test(s+p.event.triggered))return;s.indexOf("!")>=0&&(s=s.slice(0,-1),i=!0),s.indexOf(".")>=0&&(t=s.split("."),s=t.shift(),t.sort());if((!f||p.event.customEvent[s])&&!p.event.global[s])return;c=typeof c=="object"?c[p.expando]?c:new p.Event(s,c):new p.Event(s),c.type=s,c.isTrigger=!0,c.exclusive=i,c.namespace=t.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+t.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,m=s.indexOf(":")<0?"on"+s:"";if(!f){h=p.cache;for(j in h)h[j].events&&h[j].events[s]&&p.event.trigger(c,d,h[j].handle.elem,!0);return}c.result=b,c.target||(c.target=f),d=d!=null?p.makeArray(d):[],d.unshift(c),n=p.event.special[s]||{};if(n.trigger&&n.trigger.apply(f,d)===!1)return;q=[[f,n.bindType||s]];if(!g&&!n.noBubble&&!p.isWindow(f)){r=n.delegateType||s,k=$.test(r+s)?f:f.parentNode;for(l=f;k;k=k.parentNode)q.push([k,r]),l=k;l===(f.ownerDocument||e)&&q.push([l.defaultView||l.parentWindow||a,r])}for(j=0;j<q.length&&!c.isPropagationStopped();j++)k=q[j][0],c.type=q[j][1],o=(p._data(k,"events")||{})[c.type]&&p._data(k,"handle"),o&&o.apply(k,d),o=m&&k[m],o&&p.acceptData(k)&&o.apply&&o.apply(k,d)===!1&&c.preventDefault();return c.type=s,!g&&!c.isDefaultPrevented()&&(!n._default||n._default.apply(f.ownerDocument,d)===!1)&&(s!=="click"||!p.nodeName(f,"a"))&&p.acceptData(f)&&m&&f[s]&&(s!=="focus"&&s!=="blur"||c.target.offsetWidth!==0)&&!p.isWindow(f)&&(l=f[m],l&&(f[m]=null),p.event.triggered=s,f[s](),p.event.triggered=b,l&&(f[m]=l)),c.result}return},dispatch:function(c){c=p.event.fix(c||a.event);var d,e,f,g,h,i,j,l,m,n,o=(p._data(this,"events")||{})[c.type]||[],q=o.delegateCount,r=k.call(arguments),s=!c.exclusive&&!c.namespace,t=p.event.special[c.type]||{},u=[];r[0]=c,c.delegateTarget=this;if(t.preDispatch&&t.preDispatch.call(this,c)===!1)return;if(q&&(!c.button||c.type!=="click"))for(f=c.target;f!=this;f=f.parentNode||this)if(f.disabled!==!0||c.type!=="click"){h={},j=[];for(d=0;d<q;d++)l=o[d],m=l.selector,h[m]===b&&(h[m]=l.needsContext?p(m,this).index(f)>=0:p.find(m,this,null,[f]).length),h[m]&&j.push(l);j.length&&u.push({elem:f,matches:j})}o.length>q&&u.push({elem:this,matches:o.slice(q)});for(d=0;d<u.length&&!c.isPropagationStopped();d++){i=u[d],c.currentTarget=i.elem;for(e=0;e<i.matches.length&&!c.isImmediatePropagationStopped();e++){l=i.matches[e];if(s||!c.namespace&&!l.namespace||c.namespace_re&&c.namespace_re.test(l.namespace))c.data=l.data,c.handleObj=l,g=((p.event.special[l.origType]||{}).handle||l.handler).apply(i.elem,r),g!==b&&(c.result=g,g===!1&&(c.preventDefault(),c.stopPropagation()))}}return t.postDispatch&&t.postDispatch.call(this,c),c.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,c){var d,f,g,h=c.button,i=c.fromElement;return a.pageX==null&&c.clientX!=null&&(d=a.target.ownerDocument||e,f=d.documentElement,g=d.body,a.pageX=c.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=c.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?c.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0),a}},fix:function(a){if(a[p.expando])return a;var b,c,d=a,f=p.event.fixHooks[a.type]||{},g=f.props?this.props.concat(f.props):this.props;a=p.Event(d);for(b=g.length;b;)c=g[--b],a[c]=d[c];return a.target||(a.target=d.srcElement||e),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey=!!a.metaKey,f.filter?f.filter(a,d):a},special:{load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){p.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=p.extend(new p.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?p.event.trigger(e,null,b):p.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},p.event.handle=p.event.dispatch,p.removeEvent=e.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){var d="on"+b;a.detachEvent&&(typeof a[d]=="undefined"&&(a[d]=null),a.detachEvent(d,c))},p.Event=function(a,b){if(this instanceof p.Event)a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?bb:ba):this.type=a,b&&p.extend(this,b),this.timeStamp=a&&a.timeStamp||p.now(),this[p.expando]=!0;else return new p.Event(a,b)},p.Event.prototype={preventDefault:function(){this.isDefaultPrevented=bb;var a=this.originalEvent;if(!a)return;a.preventDefault?a.preventDefault():a.returnValue=!1},stopPropagation:function(){this.isPropagationStopped=bb;var a=this.originalEvent;if(!a)return;a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=bb,this.stopPropagation()},isDefaultPrevented:ba,isPropagationStopped:ba,isImmediatePropagationStopped:ba},p.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){p.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj,g=f.selector;if(!e||e!==d&&!p.contains(d,e))a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b;return c}}}),p.support.submitBubbles||(p.event.special.submit={setup:function(){if(p.nodeName(this,"form"))return!1;p.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=p.nodeName(c,"input")||p.nodeName(c,"button")?c.form:b;d&&!p._data(d,"_submit_attached")&&(p.event.add(d,"submit._submit",function(a){a._submit_bubble=!0}),p._data(d,"_submit_attached",!0))})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&p.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){if(p.nodeName(this,"form"))return!1;p.event.remove(this,"._submit")}}),p.support.changeBubbles||(p.event.special.change={setup:function(){if(V.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")p.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),p.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1),p.event.simulate("change",this,a,!0)});return!1}p.event.add(this,"beforeactivate._change",function(a){var b=a.target;V.test(b.nodeName)&&!p._data(b,"_change_attached")&&(p.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&p.event.simulate("change",this.parentNode,a,!0)}),p._data(b,"_change_attached",!0))})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){return p.event.remove(this,"._change"),!V.test(this.nodeName)}}),p.support.focusinBubbles||p.each({focus:"focusin",blur:"focusout"},function(a,b){var c=0,d=function(a){p.event.simulate(b,a.target,p.event.fix(a),!0)};p.event.special[b]={setup:function(){c++===0&&e.addEventListener(a,d,!0)},teardown:function(){--c===0&&e.removeEventListener(a,d,!0)}}}),p.fn.extend({on:function(a,c,d,e,f){var g,h;if(typeof a=="object"){typeof c!="string"&&(d=d||c,c=b);for(h in a)this.on(h,c,d,a[h],f);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=ba;else if(!e)return this;return f===1&&(g=e,e=function(a){return p().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=p.guid++)),this.each(function(){p.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,c,d){var e,f;if(a&&a.preventDefault&&a.handleObj)return e=a.handleObj,p(a.delegateTarget).off(e.namespace?e.origType+"."+e.namespace:e.origType,e.selector,e.handler),this;if(typeof a=="object"){for(f in a)this.off(f,c,a[f]);return this}if(c===!1||typeof c=="function")d=c,c=b;return d===!1&&(d=ba),this.each(function(){p.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){return p(this.context).on(a,this.selector,b,c),this},die:function(a,b){return p(this.context).off(a,this.selector||"**",b),this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length===1?this.off(a,"**"):this.off(b,a||"**",c)},trigger:function(a,b){return this.each(function(){p.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return p.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||p.guid++,d=0,e=function(c){var e=(p._data(this,"lastToggle"+a.guid)||0)%d;return p._data(this,"lastToggle"+a.guid,e+1),c.preventDefault(),b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),p.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){p.fn[b]=function(a,c){return c==null&&(c=a,a=null),arguments.length>0?this.on(b,null,a,c):this.trigger(b)},Y.test(b)&&(p.event.fixHooks[b]=p.event.keyHooks),Z.test(b)&&(p.event.fixHooks[b]=p.event.mouseHooks)}),function(a,b){function bc(a,b,c,d){c=c||[],b=b||r;var e,f,i,j,k=b.nodeType;if(!a||typeof a!="string")return c;if(k!==1&&k!==9)return[];i=g(b);if(!i&&!d)if(e=P.exec(a))if(j=e[1]){if(k===9){f=b.getElementById(j);if(!f||!f.parentNode)return c;if(f.id===j)return c.push(f),c}else if(b.ownerDocument&&(f=b.ownerDocument.getElementById(j))&&h(b,f)&&f.id===j)return c.push(f),c}else{if(e[2])return w.apply(c,x.call(b.getElementsByTagName(a),0)),c;if((j=e[3])&&_&&b.getElementsByClassName)return w.apply(c,x.call(b.getElementsByClassName(j),0)),c}return bp(a.replace(L,"$1"),b,c,d,i)}function bd(a){return function(b){var c=b.nodeName.toLowerCase();return c==="input"&&b.type===a}}function be(a){return function(b){var c=b.nodeName.toLowerCase();return(c==="input"||c==="button")&&b.type===a}}function bf(a){return z(function(b){return b=+b,z(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function bg(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}function bh(a,b){var c,d,f,g,h,i,j,k=C[o][a];if(k)return b?0:k.slice(0);h=a,i=[],j=e.preFilter;while(h){if(!c||(d=M.exec(h)))d&&(h=h.slice(d[0].length)),i.push(f=[]);c=!1;if(d=N.exec(h))f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=d[0].replace(L," ");for(g in e.filter)(d=W[g].exec(h))&&(!j[g]||(d=j[g](d,r,!0)))&&(f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=g,c.matches=d);if(!c)break}return b?h.length:h?bc.error(a):C(a,i).slice(0)}function bi(a,b,d){var e=b.dir,f=d&&b.dir==="parentNode",g=u++;return b.first?function(b,c,d){while(b=b[e])if(f||b.nodeType===1)return a(b,c,d)}:function(b,d,h){if(!h){var i,j=t+" "+g+" ",k=j+c;while(b=b[e])if(f||b.nodeType===1){if((i=b[o])===k)return b.sizset;if(typeof i=="string"&&i.indexOf(j)===0){if(b.sizset)return b}else{b[o]=k;if(a(b,d,h))return b.sizset=!0,b;b.sizset=!1}}}else while(b=b[e])if(f||b.nodeType===1)if(a(b,d,h))return b}}function bj(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function bk(a,b,c,d,e){var f,g=[],h=0,i=a.length,j=b!=null;for(;h<i;h++)if(f=a[h])if(!c||c(f,d,e))g.push(f),j&&b.push(h);return g}function bl(a,b,c,d,e,f){return d&&!d[o]&&(d=bl(d)),e&&!e[o]&&(e=bl(e,f)),z(function(f,g,h,i){if(f&&e)return;var j,k,l,m=[],n=[],o=g.length,p=f||bo(b||"*",h.nodeType?[h]:h,[],f),q=a&&(f||!b)?bk(p,m,a,h,i):p,r=c?e||(f?a:o||d)?[]:g:q;c&&c(q,r,h,i);if(d){l=bk(r,n),d(l,[],h,i),j=l.length;while(j--)if(k=l[j])r[n[j]]=!(q[n[j]]=k)}if(f){j=a&&r.length;while(j--)if(k=r[j])f[m[j]]=!(g[m[j]]=k)}else r=bk(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):w.apply(g,r)})}function bm(a){var b,c,d,f=a.length,g=e.relative[a[0].type],h=g||e.relative[" "],i=g?1:0,j=bi(function(a){return a===b},h,!0),k=bi(function(a){return y.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==l)||((b=c).nodeType?j(a,c,d):k(a,c,d))}];for(;i<f;i++)if(c=e.relative[a[i].type])m=[bi(bj(m),c)];else{c=e.filter[a[i].type].apply(null,a[i].matches);if(c[o]){d=++i;for(;d<f;d++)if(e.relative[a[d].type])break;return bl(i>1&&bj(m),i>1&&a.slice(0,i-1).join("").replace(L,"$1"),c,i<d&&bm(a.slice(i,d)),d<f&&bm(a=a.slice(d)),d<f&&a.join(""))}m.push(c)}return bj(m)}function bn(a,b){var d=b.length>0,f=a.length>0,g=function(h,i,j,k,m){var n,o,p,q=[],s=0,u="0",x=h&&[],y=m!=null,z=l,A=h||f&&e.find.TAG("*",m&&i.parentNode||i),B=t+=z==null?1:Math.E;y&&(l=i!==r&&i,c=g.el);for(;(n=A[u])!=null;u++){if(f&&n){for(o=0;p=a[o];o++)if(p(n,i,j)){k.push(n);break}y&&(t=B,c=++g.el)}d&&((n=!p&&n)&&s--,h&&x.push(n))}s+=u;if(d&&u!==s){for(o=0;p=b[o];o++)p(x,q,i,j);if(h){if(s>0)while(u--)!x[u]&&!q[u]&&(q[u]=v.call(k));q=bk(q)}w.apply(k,q),y&&!h&&q.length>0&&s+b.length>1&&bc.uniqueSort(k)}return y&&(t=B,l=z),x};return g.el=0,d?z(g):g}function bo(a,b,c,d){var e=0,f=b.length;for(;e<f;e++)bc(a,b[e],c,d);return c}function bp(a,b,c,d,f){var g,h,j,k,l,m=bh(a),n=m.length;if(!d&&m.length===1){h=m[0]=m[0].slice(0);if(h.length>2&&(j=h[0]).type==="ID"&&b.nodeType===9&&!f&&e.relative[h[1].type]){b=e.find.ID(j.matches[0].replace(V,""),b,f)[0];if(!b)return c;a=a.slice(h.shift().length)}for(g=W.POS.test(a)?-1:h.length-1;g>=0;g--){j=h[g];if(e.relative[k=j.type])break;if(l=e.find[k])if(d=l(j.matches[0].replace(V,""),R.test(h[0].type)&&b.parentNode||b,f)){h.splice(g,1),a=d.length&&h.join("");if(!a)return w.apply(c,x.call(d,0)),c;break}}}return i(a,m)(d,b,f,c,R.test(a)),c}function bq(){}var c,d,e,f,g,h,i,j,k,l,m=!0,n="undefined",o=("sizcache"+Math.random()).replace(".",""),q=String,r=a.document,s=r.documentElement,t=0,u=0,v=[].pop,w=[].push,x=[].slice,y=[].indexOf||function(a){var b=0,c=this.length;for(;b<c;b++)if(this[b]===a)return b;return-1},z=function(a,b){return a[o]=b==null||b,a},A=function(){var a={},b=[];return z(function(c,d){return b.push(c)>e.cacheLength&&delete a[b.shift()],a[c]=d},a)},B=A(),C=A(),D=A(),E="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",G=F.replace("w","w#"),H="([*^$|!~]?=)",I="\\["+E+"*("+F+")"+E+"*(?:"+H+E+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+G+")|)|)"+E+"*\\]",J=":("+F+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+I+")|[^:]|\\\\.)*|.*))\\)|)",K=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+E+"*((?:-\\d)?\\d*)"+E+"*\\)|)(?=[^-]|$)",L=new RegExp("^"+E+"+|((?:^|[^\\\\])(?:\\\\.)*)"+E+"+$","g"),M=new RegExp("^"+E+"*,"+E+"*"),N=new RegExp("^"+E+"*([\\x20\\t\\r\\n\\f>+~])"+E+"*"),O=new RegExp(J),P=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,Q=/^:not/,R=/[\x20\t\r\n\f]*[+~]/,S=/:not\($/,T=/h\d/i,U=/input|select|textarea|button/i,V=/\\(?!\\)/g,W={ID:new RegExp("^#("+F+")"),CLASS:new RegExp("^\\.("+F+")"),NAME:new RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:new RegExp("^("+F.replace("w","w*")+")"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+J),POS:new RegExp(K,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+E+"*(even|odd|(([+-]|)(\\d*)n|)"+E+"*(?:([+-]|)"+E+"*(\\d+)|))"+E+"*\\)|)","i"),needsContext:new RegExp("^"+E+"*[>+~]|"+K,"i")},X=function(a){var b=r.createElement("div");try{return a(b)}catch(c){return!1}finally{b=null}},Y=X(function(a){return a.appendChild(r.createComment("")),!a.getElementsByTagName("*").length}),Z=X(function(a){return a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!==n&&a.firstChild.getAttribute("href")==="#"}),$=X(function(a){a.innerHTML="<select></select>";var b=typeof a.lastChild.getAttribute("multiple");return b!=="boolean"&&b!=="string"}),_=X(function(a){return a.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",!a.getElementsByClassName||!a.getElementsByClassName("e").length?!1:(a.lastChild.className="e",a.getElementsByClassName("e").length===2)}),ba=X(function(a){a.id=o+0,a.innerHTML="<a name='"+o+"'></a><div name='"+o+"'></div>",s.insertBefore(a,s.firstChild);var b=r.getElementsByName&&r.getElementsByName(o).length===2+r.getElementsByName(o+0).length;return d=!r.getElementById(o),s.removeChild(a),b});try{x.call(s.childNodes,0)[0].nodeType}catch(bb){x=function(a){var b,c=[];for(;b=this[a];a++)c.push(b);return c}}bc.matches=function(a,b){return bc(a,null,null,b)},bc.matchesSelector=function(a,b){return bc(b,null,null,[a]).length>0},f=bc.getText=function(a){var b,c="",d=0,e=a.nodeType;if(e){if(e===1||e===9||e===11){if(typeof a.textContent=="string")return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=f(a)}else if(e===3||e===4)return a.nodeValue}else for(;b=a[d];d++)c+=f(b);return c},g=bc.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?b.nodeName!=="HTML":!1},h=bc.contains=s.contains?function(a,b){var c=a.nodeType===9?a.documentElement:a,d=b&&b.parentNode;return a===d||!!(d&&d.nodeType===1&&c.contains&&c.contains(d))}:s.compareDocumentPosition?function(a,b){return b&&!!(a.compareDocumentPosition(b)&16)}:function(a,b){while(b=b.parentNode)if(b===a)return!0;return!1},bc.attr=function(a,b){var c,d=g(a);return d||(b=b.toLowerCase()),(c=e.attrHandle[b])?c(a):d||$?a.getAttribute(b):(c=a.getAttributeNode(b),c?typeof a[b]=="boolean"?a[b]?b:null:c.specified?c.value:null:null)},e=bc.selectors={cacheLength:50,createPseudo:z,match:W,attrHandle:Z?{}:{href:function(a){return a.getAttribute("href",2)},type:function(a){return a.getAttribute("type")}},find:{ID:d?function(a,b,c){if(typeof b.getElementById!==n&&!c){var d=b.getElementById(a);return d&&d.parentNode?[d]:[]}}:function(a,c,d){if(typeof c.getElementById!==n&&!d){var e=c.getElementById(a);return e?e.id===a||typeof e.getAttributeNode!==n&&e.getAttributeNode("id").value===a?[e]:b:[]}},TAG:Y?function(a,b){if(typeof b.getElementsByTagName!==n)return b.getElementsByTagName(a)}:function(a,b){var c=b.getElementsByTagName(a);if(a==="*"){var d,e=[],f=0;for(;d=c[f];f++)d.nodeType===1&&e.push(d);return e}return c},NAME:ba&&function(a,b){if(typeof b.getElementsByName!==n)return b.getElementsByName(name)},CLASS:_&&function(a,b,c){if(typeof b.getElementsByClassName!==n&&!c)return b.getElementsByClassName(a)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(V,""),a[3]=(a[4]||a[5]||"").replace(V,""),a[2]==="~="&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),a[1]==="nth"?(a[2]||bc.error(a[0]),a[3]=+(a[3]?a[4]+(a[5]||1):2*(a[2]==="even"||a[2]==="odd")),a[4]=+(a[6]+a[7]||a[2]==="odd")):a[2]&&bc.error(a[0]),a},PSEUDO:function(a){var b,c;if(W.CHILD.test(a[0]))return null;if(a[3])a[2]=a[3];else if(b=a[4])O.test(b)&&(c=bh(b,!0))&&(c=b.indexOf(")",b.length-c)-b.length)&&(b=b.slice(0,c),a[0]=a[0].slice(0,c)),a[2]=b;return a.slice(0,3)}},filter:{ID:d?function(a){return a=a.replace(V,""),function(b){return b.getAttribute("id")===a}}:function(a){return a=a.replace(V,""),function(b){var c=typeof b.getAttributeNode!==n&&b.getAttributeNode("id");return c&&c.value===a}},TAG:function(a){return a==="*"?function(){return!0}:(a=a.replace(V,"").toLowerCase(),function(b){return b.nodeName&&b.nodeName.toLowerCase()===a})},CLASS:function(a){var b=B[o][a];return b||(b=B(a,new RegExp("(^|"+E+")"+a+"("+E+"|$)"))),function(a){return b.test(a.className||typeof a.getAttribute!==n&&a.getAttribute("class")||"")}},ATTR:function(a,b,c){return function(d,e){var f=bc.attr(d,a);return f==null?b==="!=":b?(f+="",b==="="?f===c:b==="!="?f!==c:b==="^="?c&&f.indexOf(c)===0:b==="*="?c&&f.indexOf(c)>-1:b==="$="?c&&f.substr(f.length-c.length)===c:b==="~="?(" "+f+" ").indexOf(c)>-1:b==="|="?f===c||f.substr(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d){return a==="nth"?function(a){var b,e,f=a.parentNode;if(c===1&&d===0)return!0;if(f){e=0;for(b=f.firstChild;b;b=b.nextSibling)if(b.nodeType===1){e++;if(a===b)break}}return e-=d,e===c||e%c===0&&e/c>=0}:function(b){var c=b;switch(a){case"only":case"first":while(c=c.previousSibling)if(c.nodeType===1)return!1;if(a==="first")return!0;c=b;case"last":while(c=c.nextSibling)if(c.nodeType===1)return!1;return!0}}},PSEUDO:function(a,b){var c,d=e.pseudos[a]||e.setFilters[a.toLowerCase()]||bc.error("unsupported pseudo: "+a);return d[o]?d(b):d.length>1?(c=[a,a,"",b],e.setFilters.hasOwnProperty(a.toLowerCase())?z(function(a,c){var e,f=d(a,b),g=f.length;while(g--)e=y.call(a,f[g]),a[e]=!(c[e]=f[g])}):function(a){return d(a,0,c)}):d}},pseudos:{not:z(function(a){var b=[],c=[],d=i(a.replace(L,"$1"));return d[o]?z(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)if(f=g[h])a[h]=!(b[h]=f)}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:z(function(a){return function(b){return bc(a,b).length>0}}),contains:z(function(a){return function(b){return(b.textContent||b.innerText||f(b)).indexOf(a)>-1}}),enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&!!a.checked||b==="option"&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},parent:function(a){return!e.pseudos.empty(a)},empty:function(a){var b;a=a.firstChild;while(a){if(a.nodeName>"@"||(b=a.nodeType)===3||b===4)return!1;a=a.nextSibling}return!0},header:function(a){return T.test(a.nodeName)},text:function(a){var b,c;return a.nodeName.toLowerCase()==="input"&&(b=a.type)==="text"&&((c=a.getAttribute("type"))==null||c.toLowerCase()===b)},radio:bd("radio"),checkbox:bd("checkbox"),file:bd("file"),password:bd("password"),image:bd("image"),submit:be("submit"),reset:be("reset"),button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&a.type==="button"||b==="button"},input:function(a){return U.test(a.nodeName)},focus:function(a){var b=a.ownerDocument;return a===b.activeElement&&(!b.hasFocus||b.hasFocus())&&(!!a.type||!!a.href)},active:function(a){return a===a.ownerDocument.activeElement},first:bf(function(a,b,c){return[0]}),last:bf(function(a,b,c){return[b-1]}),eq:bf(function(a,b,c){return[c<0?c+b:c]}),even:bf(function(a,b,c){for(var d=0;d<b;d+=2)a.push(d);return a}),odd:bf(function(a,b,c){for(var d=1;d<b;d+=2)a.push(d);return a}),lt:bf(function(a,b,c){for(var d=c<0?c+b:c;--d>=0;)a.push(d);return a}),gt:bf(function(a,b,c){for(var d=c<0?c+b:c;++d<b;)a.push(d);return a})}},j=s.compareDocumentPosition?function(a,b){return a===b?(k=!0,0):(!a.compareDocumentPosition||!b.compareDocumentPosition?a.compareDocumentPosition:a.compareDocumentPosition(b)&4)?-1:1}:function(a,b){if(a===b)return k=!0,0;if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,h=b.parentNode,i=g;if(g===h)return bg(a,b);if(!g)return-1;if(!h)return 1;while(i)e.unshift(i),i=i.parentNode;i=h;while(i)f.unshift(i),i=i.parentNode;c=e.length,d=f.length;for(var j=0;j<c&&j<d;j++)if(e[j]!==f[j])return bg(e[j],f[j]);return j===c?bg(a,f[j],-1):bg(e[j],b,1)},[0,0].sort(j),m=!k,bc.uniqueSort=function(a){var b,c=1;k=m,a.sort(j);if(k)for(;b=a[c];c++)b===a[c-1]&&a.splice(c--,1);return a},bc.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},i=bc.compile=function(a,b){var c,d=[],e=[],f=D[o][a];if(!f){b||(b=bh(a)),c=b.length;while(c--)f=bm(b[c]),f[o]?d.push(f):e.push(f);f=D(a,bn(e,d))}return f},r.querySelectorAll&&function(){var a,b=bp,c=/'|\\/g,d=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,e=[":focus"],f=[":active",":focus"],h=s.matchesSelector||s.mozMatchesSelector||s.webkitMatchesSelector||s.oMatchesSelector||s.msMatchesSelector;X(function(a){a.innerHTML="<select><option selected=''></option></select>",a.querySelectorAll("[selected]").length||e.push("\\["+E+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),a.querySelectorAll(":checked").length||e.push(":checked")}),X(function(a){a.innerHTML="<p test=''></p>",a.querySelectorAll("[test^='']").length&&e.push("[*^$]="+E+"*(?:\"\"|'')"),a.innerHTML="<input type='hidden'/>",a.querySelectorAll(":enabled").length||e.push(":enabled",":disabled")}),e=new RegExp(e.join("|")),bp=function(a,d,f,g,h){if(!g&&!h&&(!e||!e.test(a))){var i,j,k=!0,l=o,m=d,n=d.nodeType===9&&a;if(d.nodeType===1&&d.nodeName.toLowerCase()!=="object"){i=bh(a),(k=d.getAttribute("id"))?l=k.replace(c,"\\$&"):d.setAttribute("id",l),l="[id='"+l+"'] ",j=i.length;while(j--)i[j]=l+i[j].join("");m=R.test(a)&&d.parentNode||d,n=i.join(",")}if(n)try{return w.apply(f,x.call(m.querySelectorAll(n),0)),f}catch(p){}finally{k||d.removeAttribute("id")}}return b(a,d,f,g,h)},h&&(X(function(b){a=h.call(b,"div");try{h.call(b,"[test!='']:sizzle"),f.push("!=",J)}catch(c){}}),f=new RegExp(f.join("|")),bc.matchesSelector=function(b,c){c=c.replace(d,"='$1']");if(!g(b)&&!f.test(c)&&(!e||!e.test(c)))try{var i=h.call(b,c);if(i||a||b.document&&b.document.nodeType!==11)return i}catch(j){}return bc(c,null,null,[b]).length>0})}(),e.pseudos.nth=e.pseudos.eq,e.filters=bq.prototype=e.pseudos,e.setFilters=new bq,bc.attr=p.attr,p.find=bc,p.expr=bc.selectors,p.expr[":"]=p.expr.pseudos,p.unique=bc.uniqueSort,p.text=bc.getText,p.isXMLDoc=bc.isXML,p.contains=bc.contains}(a);var bc=/Until$/,bd=/^(?:parents|prev(?:Until|All))/,be=/^.[^:#\[\.,]*$/,bf=p.expr.match.needsContext,bg={children:!0,contents:!0,next:!0,prev:!0};p.fn.extend({find:function(a){var b,c,d,e,f,g,h=this;if(typeof a!="string")return p(a).filter(function(){for(b=0,c=h.length;b<c;b++)if(p.contains(h[b],this))return!0});g=this.pushStack("","find",a);for(b=0,c=this.length;b<c;b++){d=g.length,p.find(a,this[b],g);if(b>0)for(e=d;e<g.length;e++)for(f=0;f<d;f++)if(g[f]===g[e]){g.splice(e--,1);break}}return g},has:function(a){var b,c=p(a,this),d=c.length;return this.filter(function(){for(b=0;b<d;b++)if(p.contains(this,c[b]))return!0})},not:function(a){return this.pushStack(bj(this,a,!1),"not",a)},filter:function(a){return this.pushStack(bj(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?bf.test(a)?p(a,this.context).index(this[0])>=0:p.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c,d=0,e=this.length,f=[],g=bf.test(a)||typeof a!="string"?p(a,b||this.context):0;for(;d<e;d++){c=this[d];while(c&&c.ownerDocument&&c!==b&&c.nodeType!==11){if(g?g.index(c)>-1:p.find.matchesSelector(c,a)){f.push(c);break}c=c.parentNode}}return f=f.length>1?p.unique(f):f,this.pushStack(f,"closest",a)},index:function(a){return a?typeof a=="string"?p.inArray(this[0],p(a)):p.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(a,b){var c=typeof a=="string"?p(a,b):p.makeArray(a&&a.nodeType?[a]:a),d=p.merge(this.get(),c);return this.pushStack(bh(c[0])||bh(d[0])?d:p.unique(d))},addBack:function(a){return this.add(a==null?this.prevObject:this.prevObject.filter(a))}}),p.fn.andSelf=p.fn.addBack,p.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return p.dir(a,"parentNode")},parentsUntil:function(a,b,c){return p.dir(a,"parentNode",c)},next:function(a){return bi(a,"nextSibling")},prev:function(a){return bi(a,"previousSibling")},nextAll:function(a){return p.dir(a,"nextSibling")},prevAll:function(a){return p.dir(a,"previousSibling")},nextUntil:function(a,b,c){return p.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return p.dir(a,"previousSibling",c)},siblings:function(a){return p.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return p.sibling(a.firstChild)},contents:function(a){return p.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:p.merge([],a.childNodes)}},function(a,b){p.fn[a]=function(c,d){var e=p.map(this,b,c);return bc.test(a)||(d=c),d&&typeof d=="string"&&(e=p.filter(d,e)),e=this.length>1&&!bg[a]?p.unique(e):e,this.length>1&&bd.test(a)&&(e=e.reverse()),this.pushStack(e,a,k.call(arguments).join(","))}}),p.extend({filter:function(a,b,c){return c&&(a=":not("+a+")"),b.length===1?p.find.matchesSelector(b[0],a)?[b[0]]:[]:p.find.matches(a,b)},dir:function(a,c,d){var e=[],f=a[c];while(f&&f.nodeType!==9&&(d===b||f.nodeType!==1||!p(f).is(d)))f.nodeType===1&&e.push(f),f=f[c];return e},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var bl="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",bm=/ jQuery\d+="(?:null|\d+)"/g,bn=/^\s+/,bo=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bp=/<([\w:]+)/,bq=/<tbody/i,br=/<|&#?\w+;/,bs=/<(?:script|style|link)/i,bt=/<(?:script|object|embed|option|style)/i,bu=new RegExp("<(?:"+bl+")[\\s/>]","i"),bv=/^(?:checkbox|radio)$/,bw=/checked\s*(?:[^=]|=\s*.checked.)/i,bx=/\/(java|ecma)script/i,by=/^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,bz={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bA=bk(e),bB=bA.appendChild(e.createElement("div"));bz.optgroup=bz.option,bz.tbody=bz.tfoot=bz.colgroup=bz.caption=bz.thead,bz.th=bz.td,p.support.htmlSerialize||(bz._default=[1,"X<div>","</div>"]),p.fn.extend({text:function(a){return p.access(this,function(a){return a===b?p.text(this):this.empty().append((this[0]&&this[0].ownerDocument||e).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(p.isFunction(a))return this.each(function(b){p(this).wrapAll(a.call(this,b))});if(this[0]){var b=p(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return p.isFunction(a)?this.each(function(b){p(this).wrapInner(a.call(this,b))}):this.each(function(){var b=p(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=p.isFunction(a);return this.each(function(c){p(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(a,this.firstChild)})},before:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(a,this),"before",this.selector)}},after:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(this,a),"after",this.selector)}},remove:function(a,b){var c,d=0;for(;(c=this[d])!=null;d++)if(!a||p.filter(a,[c]).length)!b&&c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),p.cleanData([c])),c.parentNode&&c.parentNode.removeChild(c);return this},empty:function(){var a,b=0;for(;(a=this[b])!=null;b++){a.nodeType===1&&p.cleanData(a.getElementsByTagName("*"));while(a.firstChild)a.removeChild(a.firstChild)}return this},clone:function(a,b){return a=a==null?!1:a,b=b==null?a:b,this.map(function(){return p.clone(this,a,b)})},html:function(a){return p.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(bm,""):b;if(typeof a=="string"&&!bs.test(a)&&(p.support.htmlSerialize||!bu.test(a))&&(p.support.leadingWhitespace||!bn.test(a))&&!bz[(bp.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(bo,"<$1></$2>");try{for(;d<e;d++)c=this[d]||{},c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),c.innerHTML=a);c=0}catch(f){}}c&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(a){return bh(this[0])?this.length?this.pushStack(p(p.isFunction(a)?a():a),"replaceWith",a):this:p.isFunction(a)?this.each(function(b){var c=p(this),d=c.html();c.replaceWith(a.call(this,b,d))}):(typeof a!="string"&&(a=p(a).detach()),this.each(function(){var b=this.nextSibling,c=this.parentNode;p(this).remove(),b?p(b).before(a):p(c).append(a)}))},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){a=[].concat.apply([],a);var e,f,g,h,i=0,j=a[0],k=[],l=this.length;if(!p.support.checkClone&&l>1&&typeof j=="string"&&bw.test(j))return this.each(function(){p(this).domManip(a,c,d)});if(p.isFunction(j))return this.each(function(e){var f=p(this);a[0]=j.call(this,e,c?f.html():b),f.domManip(a,c,d)});if(this[0]){e=p.buildFragment(a,this,k),g=e.fragment,f=g.firstChild,g.childNodes.length===1&&(g=f);if(f){c=c&&p.nodeName(f,"tr");for(h=e.cacheable||l-1;i<l;i++)d.call(c&&p.nodeName(this[i],"table")?bC(this[i],"tbody"):this[i],i===h?g:p.clone(g,!0,!0))}g=f=null,k.length&&p.each(k,function(a,b){b.src?p.ajax?p.ajax({url:b.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):p.error("no ajax"):p.globalEval((b.text||b.textContent||b.innerHTML||"").replace(by,"")),b.parentNode&&b.parentNode.removeChild(b)})}return this}}),p.buildFragment=function(a,c,d){var f,g,h,i=a[0];return c=c||e,c=!c.nodeType&&c[0]||c,c=c.ownerDocument||c,a.length===1&&typeof i=="string"&&i.length<512&&c===e&&i.charAt(0)==="<"&&!bt.test(i)&&(p.support.checkClone||!bw.test(i))&&(p.support.html5Clone||!bu.test(i))&&(g=!0,f=p.fragments[i],h=f!==b),f||(f=c.createDocumentFragment(),p.clean(a,c,f,d),g&&(p.fragments[i]=h&&f)),{fragment:f,cacheable:g}},p.fragments={},p.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){p.fn[a]=function(c){var d,e=0,f=[],g=p(c),h=g.length,i=this.length===1&&this[0].parentNode;if((i==null||i&&i.nodeType===11&&i.childNodes.length===1)&&h===1)return g[b](this[0]),this;for(;e<h;e++)d=(e>0?this.clone(!0):this).get(),p(g[e])[b](d),f=f.concat(d);return this.pushStack(f,a,g.selector)}}),p.extend({clone:function(a,b,c){var d,e,f,g;p.support.html5Clone||p.isXMLDoc(a)||!bu.test("<"+a.nodeName+">")?g=a.cloneNode(!0):(bB.innerHTML=a.outerHTML,bB.removeChild(g=bB.firstChild));if((!p.support.noCloneEvent||!p.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!p.isXMLDoc(a)){bE(a,g),d=bF(a),e=bF(g);for(f=0;d[f];++f)e[f]&&bE(d[f],e[f])}if(b){bD(a,g);if(c){d=bF(a),e=bF(g);for(f=0;d[f];++f)bD(d[f],e[f])}}return d=e=null,g},clean:function(a,b,c,d){var f,g,h,i,j,k,l,m,n,o,q,r,s=b===e&&bA,t=[];if(!b||typeof b.createDocumentFragment=="undefined")b=e;for(f=0;(h=a[f])!=null;f++){typeof h=="number"&&(h+="");if(!h)continue;if(typeof h=="string")if(!br.test(h))h=b.createTextNode(h);else{s=s||bk(b),l=b.createElement("div"),s.appendChild(l),h=h.replace(bo,"<$1></$2>"),i=(bp.exec(h)||["",""])[1].toLowerCase(),j=bz[i]||bz._default,k=j[0],l.innerHTML=j[1]+h+j[2];while(k--)l=l.lastChild;if(!p.support.tbody){m=bq.test(h),n=i==="table"&&!m?l.firstChild&&l.firstChild.childNodes:j[1]==="<table>"&&!m?l.childNodes:[];for(g=n.length-1;g>=0;--g)p.nodeName(n[g],"tbody")&&!n[g].childNodes.length&&n[g].parentNode.removeChild(n[g])}!p.support.leadingWhitespace&&bn.test(h)&&l.insertBefore(b.createTextNode(bn.exec(h)[0]),l.firstChild),h=l.childNodes,l.parentNode.removeChild(l)}h.nodeType?t.push(h):p.merge(t,h)}l&&(h=l=s=null);if(!p.support.appendChecked)for(f=0;(h=t[f])!=null;f++)p.nodeName(h,"input")?bG(h):typeof h.getElementsByTagName!="undefined"&&p.grep(h.getElementsByTagName("input"),bG);if(c){q=function(a){if(!a.type||bx.test(a.type))return d?d.push(a.parentNode?a.parentNode.removeChild(a):a):c.appendChild(a)};for(f=0;(h=t[f])!=null;f++)if(!p.nodeName(h,"script")||!q(h))c.appendChild(h),typeof h.getElementsByTagName!="undefined"&&(r=p.grep(p.merge([],h.getElementsByTagName("script")),q),t.splice.apply(t,[f+1,0].concat(r)),f+=r.length)}return t},cleanData:function(a,b){var c,d,e,f,g=0,h=p.expando,i=p.cache,j=p.support.deleteExpando,k=p.event.special;for(;(e=a[g])!=null;g++)if(b||p.acceptData(e)){d=e[h],c=d&&i[d];if(c){if(c.events)for(f in c.events)k[f]?p.event.remove(e,f):p.removeEvent(e,f,c.handle);i[d]&&(delete i[d],j?delete e[h]:e.removeAttribute?e.removeAttribute(h):e[h]=null,p.deletedIds.push(d))}}}}),function(){var a,b;p.uaMatch=function(a){a=a.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},a=p.uaMatch(g.userAgent),b={},a.browser&&(b[a.browser]=!0,b.version=a.version),b.chrome?b.webkit=!0:b.webkit&&(b.safari=!0),p.browser=b,p.sub=function(){function a(b,c){return new a.fn.init(b,c)}p.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function c(c,d){return d&&d instanceof p&&!(d instanceof a)&&(d=a(d)),p.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(e);return a}}();var bH,bI,bJ,bK=/alpha\([^)]*\)/i,bL=/opacity=([^)]*)/,bM=/^(top|right|bottom|left)$/,bN=/^(none|table(?!-c[ea]).+)/,bO=/^margin/,bP=new RegExp("^("+q+")(.*)$","i"),bQ=new RegExp("^("+q+")(?!px)[a-z%]+$","i"),bR=new RegExp("^([-+])=("+q+")","i"),bS={},bT={position:"absolute",visibility:"hidden",display:"block"},bU={letterSpacing:0,fontWeight:400},bV=["Top","Right","Bottom","Left"],bW=["Webkit","O","Moz","ms"],bX=p.fn.toggle;p.fn.extend({css:function(a,c){return p.access(this,function(a,c,d){return d!==b?p.style(a,c,d):p.css(a,c)},a,c,arguments.length>1)},show:function(){return b$(this,!0)},hide:function(){return b$(this)},toggle:function(a,b){var c=typeof a=="boolean";return p.isFunction(a)&&p.isFunction(b)?bX.apply(this,arguments):this.each(function(){(c?a:bZ(this))?p(this).show():p(this).hide()})}}),p.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bH(a,"opacity");return c===""?"1":c}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":p.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!a||a.nodeType===3||a.nodeType===8||!a.style)return;var f,g,h,i=p.camelCase(c),j=a.style;c=p.cssProps[i]||(p.cssProps[i]=bY(j,i)),h=p.cssHooks[c]||p.cssHooks[i];if(d===b)return h&&"get"in h&&(f=h.get(a,!1,e))!==b?f:j[c];g=typeof d,g==="string"&&(f=bR.exec(d))&&(d=(f[1]+1)*f[2]+parseFloat(p.css(a,c)),g="number");if(d==null||g==="number"&&isNaN(d))return;g==="number"&&!p.cssNumber[i]&&(d+="px");if(!h||!("set"in h)||(d=h.set(a,d,e))!==b)try{j[c]=d}catch(k){}},css:function(a,c,d,e){var f,g,h,i=p.camelCase(c);return c=p.cssProps[i]||(p.cssProps[i]=bY(a.style,i)),h=p.cssHooks[c]||p.cssHooks[i],h&&"get"in h&&(f=h.get(a,!0,e)),f===b&&(f=bH(a,c)),f==="normal"&&c in bU&&(f=bU[c]),d||e!==b?(g=parseFloat(f),d||p.isNumeric(g)?g||0:f):f},swap:function(a,b,c){var d,e,f={};for(e in b)f[e]=a.style[e],a.style[e]=b[e];d=c.call(a);for(e in b)a.style[e]=f[e];return d}}),a.getComputedStyle?bH=function(b,c){var d,e,f,g,h=a.getComputedStyle(b,null),i=b.style;return h&&(d=h[c],d===""&&!p.contains(b.ownerDocument,b)&&(d=p.style(b,c)),bQ.test(d)&&bO.test(c)&&(e=i.width,f=i.minWidth,g=i.maxWidth,i.minWidth=i.maxWidth=i.width=d,d=h.width,i.width=e,i.minWidth=f,i.maxWidth=g)),d}:e.documentElement.currentStyle&&(bH=function(a,b){var c,d,e=a.currentStyle&&a.currentStyle[b],f=a.style;return e==null&&f&&f[b]&&(e=f[b]),bQ.test(e)&&!bM.test(b)&&(c=f.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":e,e=f.pixelLeft+"px",f.left=c,d&&(a.runtimeStyle.left=d)),e===""?"auto":e}),p.each(["height","width"],function(a,b){p.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth===0&&bN.test(bH(a,"display"))?p.swap(a,bT,function(){return cb(a,b,d)}):cb(a,b,d)},set:function(a,c,d){return b_(a,c,d?ca(a,b,d,p.support.boxSizing&&p.css(a,"boxSizing")==="border-box"):0)}}}),p.support.opacity||(p.cssHooks.opacity={get:function(a,b){return bL.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=p.isNumeric(b)?"alpha(opacity="+b*100+")":"",f=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&p.trim(f.replace(bK,""))===""&&c.removeAttribute){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bK.test(f)?f.replace(bK,e):f+" "+e}}),p(function(){p.support.reliableMarginRight||(p.cssHooks.marginRight={get:function(a,b){return p.swap(a,{display:"inline-block"},function(){if(b)return bH(a,"marginRight")})}}),!p.support.pixelPosition&&p.fn.position&&p.each(["top","left"],function(a,b){p.cssHooks[b]={get:function(a,c){if(c){var d=bH(a,b);return bQ.test(d)?p(a).position()[b]+"px":d}}}})}),p.expr&&p.expr.filters&&(p.expr.filters.hidden=function(a){return a.offsetWidth===0&&a.offsetHeight===0||!p.support.reliableHiddenOffsets&&(a.style&&a.style.display||bH(a,"display"))==="none"},p.expr.filters.visible=function(a){return!p.expr.filters.hidden(a)}),p.each({margin:"",padding:"",border:"Width"},function(a,b){p.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bV[d]+b]=e[d]||e[d-2]||e[0];return f}},bO.test(a)||(p.cssHooks[a+b].set=b_)});var cd=/%20/g,ce=/\[\]$/,cf=/\r?\n/g,cg=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ch=/^(?:select|textarea)/i;p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?p.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ch.test(this.nodeName)||cg.test(this.type))}).map(function(a,b){var c=p(this).val();return c==null?null:p.isArray(c)?p.map(c,function(a,c){return{name:b.name,value:a.replace(cf,"\r\n")}}):{name:b.name,value:c.replace(cf,"\r\n")}}).get()}}),p.param=function(a,c){var d,e=[],f=function(a,b){b=p.isFunction(b)?b():b==null?"":b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=p.ajaxSettings&&p.ajaxSettings.traditional);if(p.isArray(a)||a.jquery&&!p.isPlainObject(a))p.each(a,function(){f(this.name,this.value)});else for(d in a)ci(d,a[d],c,f);return e.join("&").replace(cd,"+")};var cj,ck,cl=/#.*$/,cm=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,cn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,co=/^(?:GET|HEAD)$/,cp=/^\/\//,cq=/\?/,cr=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,cs=/([?&])_=[^&]*/,ct=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,cu=p.fn.load,cv={},cw={},cx=["*/"]+["*"];try{ck=f.href}catch(cy){ck=e.createElement("a"),ck.href="",ck=ck.href}cj=ct.exec(ck.toLowerCase())||[],p.fn.load=function(a,c,d){if(typeof a!="string"&&cu)return cu.apply(this,arguments);if(!this.length)return this;var e,f,g,h=this,i=a.indexOf(" ");return i>=0&&(e=a.slice(i,a.length),a=a.slice(0,i)),p.isFunction(c)?(d=c,c=b):c&&typeof c=="object"&&(f="POST"),p.ajax({url:a,type:f,dataType:"html",data:c,complete:function(a,b){d&&h.each(d,g||[a.responseText,b,a])}}).done(function(a){g=arguments,h.html(e?p("<div>").append(a.replace(cr,"")).find(e):a)}),this},p.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){p.fn[b]=function(a){return this.on(b,a)}}),p.each(["get","post"],function(a,c){p[c]=function(a,d,e,f){return p.isFunction(d)&&(f=f||e,e=d,d=b),p.ajax({type:c,url:a,data:d,success:e,dataType:f})}}),p.extend({getScript:function(a,c){return p.get(a,b,c,"script")},getJSON:function(a,b,c){return p.get(a,b,c,"json")},ajaxSetup:function(a,b){return b?cB(a,p.ajaxSettings):(b=a,a=p.ajaxSettings),cB(a,b),a},ajaxSettings:{url:ck,isLocal:cn.test(cj[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":cx},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":p.parseJSON,"text xml":p.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:cz(cv),ajaxTransport:cz(cw),ajax:function(a,c){function y(a,c,f,i){var k,s,t,u,w,y=c;if(v===2)return;v=2,h&&clearTimeout(h),g=b,e=i||"",x.readyState=a>0?4:0,f&&(u=cC(l,x,f));if(a>=200&&a<300||a===304)l.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(p.lastModified[d]=w),w=x.getResponseHeader("Etag"),w&&(p.etag[d]=w)),a===304?(y="notmodified",k=!0):(k=cD(l,u),y=k.state,s=k.data,t=k.error,k=!t);else{t=y;if(!y||a)y="error",a<0&&(a=0)}x.status=a,x.statusText=(c||y)+"",k?o.resolveWith(m,[s,y,x]):o.rejectWith(m,[x,y,t]),x.statusCode(r),r=b,j&&n.trigger("ajax"+(k?"Success":"Error"),[x,l,k?s:t]),q.fireWith(m,[x,y]),j&&(n.trigger("ajaxComplete",[x,l]),--p.active||p.event.trigger("ajaxStop"))}typeof a=="object"&&(c=a,a=b),c=c||{};var d,e,f,g,h,i,j,k,l=p.ajaxSetup({},c),m=l.context||l,n=m!==l&&(m.nodeType||m instanceof p)?p(m):p.event,o=p.Deferred(),q=p.Callbacks("once memory"),r=l.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,setRequestHeader:function(a,b){if(!v){var c=a.toLowerCase();a=u[c]=u[c]||a,t[a]=b}return this},getAllResponseHeaders:function(){return v===2?e:null},getResponseHeader:function(a){var c;if(v===2){if(!f){f={};while(c=cm.exec(e))f[c[1].toLowerCase()]=c[2]}c=f[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){return v||(l.mimeType=a),this},abort:function(a){return a=a||w,g&&g.abort(a),y(0,a),this}};o.promise(x),x.success=x.done,x.error=x.fail,x.complete=q.add,x.statusCode=function(a){if(a){var b;if(v<2)for(b in a)r[b]=[r[b],a[b]];else b=a[x.status],x.always(b)}return this},l.url=((a||l.url)+"").replace(cl,"").replace(cp,cj[1]+"//"),l.dataTypes=p.trim(l.dataType||"*").toLowerCase().split(s),l.crossDomain==null&&(i=ct.exec(l.url.toLowerCase())||!1,l.crossDomain=i&&i.join(":")+(i[3]?"":i[1]==="http:"?80:443)!==cj.join(":")+(cj[3]?"":cj[1]==="http:"?80:443)),l.data&&l.processData&&typeof l.data!="string"&&(l.data=p.param(l.data,l.traditional)),cA(cv,l,c,x);if(v===2)return x;j=l.global,l.type=l.type.toUpperCase(),l.hasContent=!co.test(l.type),j&&p.active++===0&&p.event.trigger("ajaxStart");if(!l.hasContent){l.data&&(l.url+=(cq.test(l.url)?"&":"?")+l.data,delete l.data),d=l.url;if(l.cache===!1){var z=p.now(),A=l.url.replace(cs,"$1_="+z);l.url=A+(A===l.url?(cq.test(l.url)?"&":"?")+"_="+z:"")}}(l.data&&l.hasContent&&l.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",l.contentType),l.ifModified&&(d=d||l.url,p.lastModified[d]&&x.setRequestHeader("If-Modified-Since",p.lastModified[d]),p.etag[d]&&x.setRequestHeader("If-None-Match",p.etag[d])),x.setRequestHeader("Accept",l.dataTypes[0]&&l.accepts[l.dataTypes[0]]?l.accepts[l.dataTypes[0]]+(l.dataTypes[0]!=="*"?", "+cx+"; q=0.01":""):l.accepts["*"]);for(k in l.headers)x.setRequestHeader(k,l.headers[k]);if(!l.beforeSend||l.beforeSend.call(m,x,l)!==!1&&v!==2){w="abort";for(k in{success:1,error:1,complete:1})x[k](l[k]);g=cA(cw,l,c,x);if(!g)y(-1,"No Transport");else{x.readyState=1,j&&n.trigger("ajaxSend",[x,l]),l.async&&l.timeout>0&&(h=setTimeout(function(){x.abort("timeout")},l.timeout));try{v=1,g.send(t,y)}catch(B){if(v<2)y(-1,B);else throw B}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var cE=[],cF=/\?/,cG=/(=)\?(?=&|$)|\?\?/,cH=p.now();p.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=cE.pop()||p.expando+"_"+cH++;return this[a]=!0,a}}),p.ajaxPrefilter("json jsonp",function(c,d,e){var f,g,h,i=c.data,j=c.url,k=c.jsonp!==!1,l=k&&cG.test(j),m=k&&!l&&typeof i=="string"&&!(c.contentType||"").indexOf("application/x-www-form-urlencoded")&&cG.test(i);if(c.dataTypes[0]==="jsonp"||l||m)return f=c.jsonpCallback=p.isFunction(c.jsonpCallback)?c.jsonpCallback():c.jsonpCallback,g=a[f],l?c.url=j.replace(cG,"$1"+f):m?c.data=i.replace(cG,"$1"+f):k&&(c.url+=(cF.test(j)?"&":"?")+c.jsonp+"="+f),c.converters["script json"]=function(){return h||p.error(f+" was not called"),h[0]},c.dataTypes[0]="json",a[f]=function(){h=arguments},e.always(function(){a[f]=g,c[f]&&(c.jsonpCallback=d.jsonpCallback,cE.push(f)),h&&p.isFunction(g)&&g(h[0]),h=g=b}),"script"}),p.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){return p.globalEval(a),a}}}),p.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),p.ajaxTransport("script",function(a){if(a.crossDomain){var c,d=e.head||e.getElementsByTagName("head")[0]||e.documentElement;return{send:function(f,g){c=e.createElement("script"),c.async="async",a.scriptCharset&&(c.charset=a.scriptCharset),c.src=a.url,c.onload=c.onreadystatechange=function(a,e){if(e||!c.readyState||/loaded|complete/.test(c.readyState))c.onload=c.onreadystatechange=null,d&&c.parentNode&&d.removeChild(c),c=b,e||g(200,"success")},d.insertBefore(c,d.firstChild)},abort:function(){c&&c.onload(0,1)}}}});var cI,cJ=a.ActiveXObject?function(){for(var a in cI)cI[a](0,1)}:!1,cK=0;p.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&cL()||cM()}:cL,function(a){p.extend(p.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(p.ajaxSettings.xhr()),p.support.ajax&&p.ajaxTransport(function(c){if(!c.crossDomain||p.support.cors){var d;return{send:function(e,f){var g,h,i=c.xhr();c.username?i.open(c.type,c.url,c.async,c.username,c.password):i.open(c.type,c.url,c.async);if(c.xhrFields)for(h in c.xhrFields)i[h]=c.xhrFields[h];c.mimeType&&i.overrideMimeType&&i.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(h in e)i.setRequestHeader(h,e[h])}catch(j){}i.send(c.hasContent&&c.data||null),d=function(a,e){var h,j,k,l,m;try{if(d&&(e||i.readyState===4)){d=b,g&&(i.onreadystatechange=p.noop,cJ&&delete cI[g]);if(e)i.readyState!==4&&i.abort();else{h=i.status,k=i.getAllResponseHeaders(),l={},m=i.responseXML,m&&m.documentElement&&(l.xml=m);try{l.text=i.responseText}catch(a){}try{j=i.statusText}catch(n){j=""}!h&&c.isLocal&&!c.crossDomain?h=l.text?200:404:h===1223&&(h=204)}}}catch(o){e||f(-1,o)}l&&f(h,j,l,k)},c.async?i.readyState===4?setTimeout(d,0):(g=++cK,cJ&&(cI||(cI={},p(a).unload(cJ)),cI[g]=d),i.onreadystatechange=d):d()},abort:function(){d&&d(0,1)}}}});var cN,cO,cP=/^(?:toggle|show|hide)$/,cQ=new RegExp("^(?:([-+])=|)("+q+")([a-z%]*)$","i"),cR=/queueHooks$/,cS=[cY],cT={"*":[function(a,b){var c,d,e=this.createTween(a,b),f=cQ.exec(b),g=e.cur(),h=+g||0,i=1,j=20;if(f){c=+f[2],d=f[3]||(p.cssNumber[a]?"":"px");if(d!=="px"&&h){h=p.css(e.elem,a,!0)||c||1;do i=i||".5",h=h/i,p.style(e.elem,a,h+d);while(i!==(i=e.cur()/g)&&i!==1&&--j)}e.unit=d,e.start=h,e.end=f[1]?h+(f[1]+1)*c:c}return e}]};p.Animation=p.extend(cW,{tweener:function(a,b){p.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");var c,d=0,e=a.length;for(;d<e;d++)c=a[d],cT[c]=cT[c]||[],cT[c].unshift(b)},prefilter:function(a,b){b?cS.unshift(a):cS.push(a)}}),p.Tween=cZ,cZ.prototype={constructor:cZ,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(p.cssNumber[c]?"":"px")},cur:function(){var a=cZ.propHooks[this.prop];return a&&a.get?a.get(this):cZ.propHooks._default.get(this)},run:function(a){var b,c=cZ.propHooks[this.prop];return this.options.duration?this.pos=b=p.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):cZ.propHooks._default.set(this),this}},cZ.prototype.init.prototype=cZ.prototype,cZ.propHooks={_default:{get:function(a){var b;return a.elem[a.prop]==null||!!a.elem.style&&a.elem.style[a.prop]!=null?(b=p.css(a.elem,a.prop,!1,""),!b||b==="auto"?0:b):a.elem[a.prop]},set:function(a){p.fx.step[a.prop]?p.fx.step[a.prop](a):a.elem.style&&(a.elem.style[p.cssProps[a.prop]]!=null||p.cssHooks[a.prop])?p.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},cZ.propHooks.scrollTop=cZ.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},p.each(["toggle","show","hide"],function(a,b){var c=p.fn[b];p.fn[b]=function(d,e,f){return d==null||typeof d=="boolean"||!a&&p.isFunction(d)&&p.isFunction(e)?c.apply(this,arguments):this.animate(c$(b,!0),d,e,f)}}),p.fn.extend({fadeTo:function(a,b,c,d){return this.filter(bZ).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=p.isEmptyObject(a),f=p.speed(b,c,d),g=function(){var b=cW(this,p.extend({},a),f);e&&b.stop(!0)};return e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,c,d){var e=function(a){var b=a.stop;delete a.stop,b(d)};return typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,c=a!=null&&a+"queueHooks",f=p.timers,g=p._data(this);if(c)g[c]&&g[c].stop&&e(g[c]);else for(c in g)g[c]&&g[c].stop&&cR.test(c)&&e(g[c]);for(c=f.length;c--;)f[c].elem===this&&(a==null||f[c].queue===a)&&(f[c].anim.stop(d),b=!1,f.splice(c,1));(b||!d)&&p.dequeue(this,a)})}}),p.each({slideDown:c$("show"),slideUp:c$("hide"),slideToggle:c$("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){p.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),p.speed=function(a,b,c){var d=a&&typeof a=="object"?p.extend({},a):{complete:c||!c&&b||p.isFunction(a)&&a,duration:a,easing:c&&b||b&&!p.isFunction(b)&&b};d.duration=p.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in p.fx.speeds?p.fx.speeds[d.duration]:p.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";return d.old=d.complete,d.complete=function(){p.isFunction(d.old)&&d.old.call(this),d.queue&&p.dequeue(this,d.queue)},d},p.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},p.timers=[],p.fx=cZ.prototype.init,p.fx.tick=function(){var a,b=p.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||p.fx.stop()},p.fx.timer=function(a){a()&&p.timers.push(a)&&!cO&&(cO=setInterval(p.fx.tick,p.fx.interval))},p.fx.interval=13,p.fx.stop=function(){clearInterval(cO),cO=null},p.fx.speeds={slow:600,fast:200,_default:400},p.fx.step={},p.expr&&p.expr.filters&&(p.expr.filters.animated=function(a){return p.grep(p.timers,function(b){return a===b.elem}).length});var c_=/^(?:body|html)$/i;p.fn.offset=function(a){if(arguments.length)return a===b?this:this.each(function(b){p.offset.setOffset(this,a,b)});var c,d,e,f,g,h,i,j={top:0,left:0},k=this[0],l=k&&k.ownerDocument;if(!l)return;return(d=l.body)===k?p.offset.bodyOffset(k):(c=l.documentElement,p.contains(c,k)?(typeof k.getBoundingClientRect!="undefined"&&(j=k.getBoundingClientRect()),e=da(l),f=c.clientTop||d.clientTop||0,g=c.clientLeft||d.clientLeft||0,h=e.pageYOffset||c.scrollTop,i=e.pageXOffset||c.scrollLeft,{top:j.top+h-f,left:j.left+i-g}):j)},p.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;return p.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(p.css(a,"marginTop"))||0,c+=parseFloat(p.css(a,"marginLeft"))||0),{top:b,left:c}},setOffset:function(a,b,c){var d=p.css(a,"position");d==="static"&&(a.style.position="relative");var e=p(a),f=e.offset(),g=p.css(a,"top"),h=p.css(a,"left"),i=(d==="absolute"||d==="fixed")&&p.inArray("auto",[g,h])>-1,j={},k={},l,m;i?(k=e.position(),l=k.top,m=k.left):(l=parseFloat(g)||0,m=parseFloat(h)||0),p.isFunction(b)&&(b=b.call(a,c,f)),b.top!=null&&(j.top=b.top-f.top+l),b.left!=null&&(j.left=b.left-f.left+m),"using"in b?b.using.call(a,j):e.css(j)}},p.fn.extend({position:function(){if(!this[0])return;var a=this[0],b=this.offsetParent(),c=this.offset(),d=c_.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(p.css(a,"marginTop"))||0,c.left-=parseFloat(p.css(a,"marginLeft"))||0,d.top+=parseFloat(p.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(p.css(b[0],"borderLeftWidth"))||0,{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||e.body;while(a&&!c_.test(a.nodeName)&&p.css(a,"position")==="static")a=a.offsetParent;return a||e.body})}}),p.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);p.fn[a]=function(e){return p.access(this,function(a,e,f){var g=da(a);if(f===b)return g?c in g?g[c]:g.document.documentElement[e]:a[e];g?g.scrollTo(d?p(g).scrollLeft():f,d?f:p(g).scrollTop()):a[e]=f},a,e,arguments.length,null)}}),p.each({Height:"height",Width:"width"},function(a,c){p.each({padding:"inner"+a,content:c,"":"outer"+a},function(d,e){p.fn[e]=function(e,f){var g=arguments.length&&(d||typeof e!="boolean"),h=d||(e===!0||f===!0?"margin":"border");return p.access(this,function(c,d,e){var f;return p.isWindow(c)?c.document.documentElement["client"+a]:c.nodeType===9?(f=c.documentElement,Math.max(c.body["scroll"+a],f["scroll"+a],c.body["offset"+a],f["offset"+a],f["client"+a])):e===b?p.css(c,d,e,h):p.style(c,d,e,h)},c,g?e:b,g,null)}})}),a.jQuery=a.$=p,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return p})})(window);
\ No newline at end of file
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/validate-commit-msg.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/validate-commit-msg.js
new file mode 100644
index 0000000..1a8df7f
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/misc/validate-commit-msg.js
@@ -0,0 +1,106 @@
+#!/usr/bin/env node
+
+/**
+ * Git COMMIT-MSG hook for validating commit message
+ * See https://docs.google.com/document/d/1rk04jEuGfk9kYzfqCuOlPTSJw3hEDZJTBN5E5f1SALo/edit
+ *
+ * Installation:
+ * >> cd <angular-repo>
+ * >> ln -s validate-commit-msg.js .git/hooks/commit-msg
+ */
+var fs = require('fs');
+var util = require('util');
+
+
+var MAX_LENGTH = 70;
+var PATTERN = /^(?:fixup!\s*)?(\w*)(\((\w+)\))?\: (.*)$/;
+var IGNORED = /^WIP\:/;
+var TYPES = {
+  chore: true,
+  demo: true,
+  docs: true,
+  feat: true,
+  fix: true,
+  refactor: true,
+  revert: true,
+  style: true,
+  test: true
+};
+
+
+var error = function() {
+  // gitx does not display it
+  // http://gitx.lighthouseapp.com/projects/17830/tickets/294-feature-display-hook-error-message-when-hook-fails
+  // https://groups.google.com/group/gitx/browse_thread/thread/a03bcab60844b812
+  console.error('INVALID COMMIT MSG: ' + util.format.apply(null, arguments));
+};
+
+
+var validateMessage = function(message) {
+  var isValid = true;
+
+  if (IGNORED.test(message)) {
+    console.log('Commit message validation ignored.');
+    return true;
+  }
+
+  if (message.length > MAX_LENGTH) {
+    error('is longer than %d characters !', MAX_LENGTH);
+    isValid = false;
+  }
+
+  var match = PATTERN.exec(message);
+
+  if (!match) {
+    error('does not match "<type>(<scope>): <subject>" ! was: "' + message + '"\nNote: <scope> must be only letters.');
+    return false;
+  }
+
+  var type = match[1];
+  var scope = match[3];
+  var subject = match[4];
+
+  if (!TYPES.hasOwnProperty(type)) {
+    error('"%s" is not allowed type !', type);
+    return false;
+  }
+
+  // Some more ideas, do want anything like this ?
+  // - allow only specific scopes (eg. fix(docs) should not be allowed ?
+  // - auto correct the type to lower case ?
+  // - auto correct first letter of the subject to lower case ?
+  // - auto add empty line after subject ?
+  // - auto remove empty () ?
+  // - auto correct typos in type ?
+  // - store incorrect messages, so that we can learn
+
+  return isValid;
+};
+
+
+var firstLineFromBuffer = function(buffer) {
+  return buffer.toString().split('\n').shift();
+};
+
+
+
+// publish for testing
+exports.validateMessage = validateMessage;
+
+// hacky start if not run by jasmine :-D
+if (process.argv.join('').indexOf('jasmine-node') === -1) {
+  var commitMsgFile = process.argv[2];
+  var incorrectLogFile = commitMsgFile.replace('COMMIT_EDITMSG', 'logs/incorrect-commit-msgs');
+
+  fs.readFile(commitMsgFile, function(err, buffer) {
+    var msg = firstLineFromBuffer(buffer);
+
+    if (!validateMessage(msg)) {
+      fs.appendFile(incorrectLogFile, msg + '\n', function() {
+        process.exit(1);
+      });
+    } else {
+      process.exit(0);
+    }
+  });
+}
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/package.json b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/package.json
new file mode 100644
index 0000000..0bc75a0
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/package.json
@@ -0,0 +1,39 @@
+{
+  "author": "https://github.com/angular-ui/bootstrap/graphs/contributors",
+  "name": "angular-ui-bootstrap",
+  "version": "0.14.3",
+  "homepage": "http://angular-ui.github.io/bootstrap/",
+  "dependencies": {},
+  "scripts":{
+    "test": "grunt"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/angular-ui/bootstrap.git"
+  },
+  "devDependencies": {
+    "angular": "^1.4.4",
+    "angular-mocks": "^1.4.4",
+    "angular-sanitize": "^1.4.4",
+    "grunt": "^0.4.5",
+    "grunt-contrib-concat": "^0.5.1",
+    "grunt-contrib-copy": "^0.8.0",
+    "grunt-contrib-jshint": "^0.11.1",
+    "grunt-contrib-uglify": "^0.9.1",
+    "grunt-contrib-watch": "^0.6.1",
+    "grunt-conventional-changelog": "^4.0.0",
+    "grunt-ddescribe-iit": "0.0.6",
+    "grunt-html2js": "^0.3.0",
+    "grunt-karma": "^0.12.0",
+    "jasmine-core": "^2.2.0",
+    "karma": "^0.13.3",
+    "karma-chrome-launcher": "^0.2.0",
+    "karma-coverage": "^0.5.0",
+    "karma-firefox-launcher": "^0.1.4",
+    "karma-jasmine": "^0.3.5",
+    "node-markdown": "0.1.1",
+    "semver": "^5.0.1",
+    "shelljs": "^0.5.1"
+  },
+  "license": "MIT"
+}
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/accordion/accordion.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/accordion/accordion.js
new file mode 100644
index 0000000..93a71ee
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/accordion/accordion.js
@@ -0,0 +1,249 @@
+angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
+
+.constant('uibAccordionConfig', {
+  closeOthers: true
+})
+
+.controller('UibAccordionController', ['$scope', '$attrs', 'uibAccordionConfig', function($scope, $attrs, accordionConfig) {
+  // This array keeps track of the accordion groups
+  this.groups = [];
+
+  // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
+  this.closeOthers = function(openGroup) {
+    var closeOthers = angular.isDefined($attrs.closeOthers) ?
+      $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
+    if (closeOthers) {
+      angular.forEach(this.groups, function(group) {
+        if (group !== openGroup) {
+          group.isOpen = false;
+        }
+      });
+    }
+  };
+
+  // This is called from the accordion-group directive to add itself to the accordion
+  this.addGroup = function(groupScope) {
+    var that = this;
+    this.groups.push(groupScope);
+
+    groupScope.$on('$destroy', function(event) {
+      that.removeGroup(groupScope);
+    });
+  };
+
+  // This is called from the accordion-group directive when to remove itself
+  this.removeGroup = function(group) {
+    var index = this.groups.indexOf(group);
+    if (index !== -1) {
+      this.groups.splice(index, 1);
+    }
+  };
+
+}])
+
+// The accordion directive simply sets up the directive controller
+// and adds an accordion CSS class to itself element.
+.directive('uibAccordion', function() {
+  return {
+    controller: 'UibAccordionController',
+    controllerAs: 'accordion',
+    transclude: true,
+    templateUrl: function(element, attrs) {
+      return attrs.templateUrl || 'template/accordion/accordion.html';
+    }
+  };
+})
+
+// The accordion-group directive indicates a block of html that will expand and collapse in an accordion
+.directive('uibAccordionGroup', function() {
+  return {
+    require: '^uibAccordion',         // We need this directive to be inside an accordion
+    transclude: true,              // It transcludes the contents of the directive into the template
+    replace: true,                // The element containing the directive will be replaced with the template
+    templateUrl: function(element, attrs) {
+      return attrs.templateUrl || 'template/accordion/accordion-group.html';
+    },
+    scope: {
+      heading: '@',               // Interpolate the heading attribute onto this scope
+      isOpen: '=?',
+      isDisabled: '=?'
+    },
+    controller: function() {
+      this.setHeading = function(element) {
+        this.heading = element;
+      };
+    },
+    link: function(scope, element, attrs, accordionCtrl) {
+      accordionCtrl.addGroup(scope);
+
+      scope.openClass = attrs.openClass || 'panel-open';
+      scope.panelClass = attrs.panelClass;
+      scope.$watch('isOpen', function(value) {
+        element.toggleClass(scope.openClass, !!value);
+        if (value) {
+          accordionCtrl.closeOthers(scope);
+        }
+      });
+
+      scope.toggleOpen = function($event) {
+        if (!scope.isDisabled) {
+          if (!$event || $event.which === 32) {
+            scope.isOpen = !scope.isOpen;
+          }
+        }
+      };
+    }
+  };
+})
+
+// Use accordion-heading below an accordion-group to provide a heading containing HTML
+.directive('uibAccordionHeading', function() {
+  return {
+    transclude: true,   // Grab the contents to be used as the heading
+    template: '',       // In effect remove this element!
+    replace: true,
+    require: '^uibAccordionGroup',
+    link: function(scope, element, attrs, accordionGroupCtrl, transclude) {
+      // Pass the heading to the accordion-group controller
+      // so that it can be transcluded into the right place in the template
+      // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
+      accordionGroupCtrl.setHeading(transclude(scope, angular.noop));
+    }
+  };
+})
+
+// Use in the accordion-group template to indicate where you want the heading to be transcluded
+// You must provide the property on the accordion-group controller that will hold the transcluded element
+.directive('uibAccordionTransclude', function() {
+  return {
+    require: ['?^uibAccordionGroup', '?^accordionGroup'],
+    link: function(scope, element, attrs, controller) {
+      controller = controller[0] ? controller[0] : controller[1]; // Delete after we remove deprecation
+      scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) {
+        if (heading) {
+          element.find('span').html('');
+          element.find('span').append(heading);
+        }
+      });
+    }
+  };
+});
+
+/* Deprecated accordion below */
+
+angular.module('ui.bootstrap.accordion')
+
+  .value('$accordionSuppressWarning', false)
+
+  .controller('AccordionController', ['$scope', '$attrs', '$controller', '$log', '$accordionSuppressWarning', function($scope, $attrs, $controller, $log, $accordionSuppressWarning) {
+    if (!$accordionSuppressWarning) {
+      $log.warn('AccordionController is now deprecated. Use UibAccordionController instead.');
+    }
+
+    angular.extend(this, $controller('UibAccordionController', {
+      $scope: $scope,
+      $attrs: $attrs
+    }));
+  }])
+
+  .directive('accordion', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) {
+    return {
+      restrict: 'EA',
+      controller: 'AccordionController',
+      controllerAs: 'accordion',
+      transclude: true,
+      replace: false,
+      templateUrl: function(element, attrs) {
+        return attrs.templateUrl || 'template/accordion/accordion.html';
+      },
+      link: function() {
+        if (!$accordionSuppressWarning) {
+          $log.warn('accordion is now deprecated. Use uib-accordion instead.');
+        }
+      }
+    };
+  }])
+
+  .directive('accordionGroup', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) {
+    return {
+      require: '^accordion',         // We need this directive to be inside an accordion
+      restrict: 'EA',
+      transclude: true,              // It transcludes the contents of the directive into the template
+      replace: true,                // The element containing the directive will be replaced with the template
+      templateUrl: function(element, attrs) {
+        return attrs.templateUrl || 'template/accordion/accordion-group.html';
+      },
+      scope: {
+        heading: '@',               // Interpolate the heading attribute onto this scope
+        isOpen: '=?',
+        isDisabled: '=?'
+      },
+      controller: function() {
+        this.setHeading = function(element) {
+          this.heading = element;
+        };
+      },
+      link: function(scope, element, attrs, accordionCtrl) {
+        if (!$accordionSuppressWarning) {
+          $log.warn('accordion-group is now deprecated. Use uib-accordion-group instead.');
+        }
+
+        accordionCtrl.addGroup(scope);
+
+        scope.openClass = attrs.openClass || 'panel-open';
+        scope.panelClass = attrs.panelClass;
+        scope.$watch('isOpen', function(value) {
+          element.toggleClass(scope.openClass, !!value);
+          if (value) {
+            accordionCtrl.closeOthers(scope);
+          }
+        });
+
+        scope.toggleOpen = function($event) {
+          if (!scope.isDisabled) {
+            if (!$event || $event.which === 32) {
+              scope.isOpen = !scope.isOpen;
+            }
+          }
+        };
+      }
+    };
+  }])
+
+  .directive('accordionHeading', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) {
+    return {
+      restrict: 'EA',
+      transclude: true,   // Grab the contents to be used as the heading
+      template: '',       // In effect remove this element!
+      replace: true,
+      require: '^accordionGroup',
+      link: function(scope, element, attr, accordionGroupCtrl, transclude) {
+        if (!$accordionSuppressWarning) {
+          $log.warn('accordion-heading is now deprecated. Use uib-accordion-heading instead.');
+        }
+        // Pass the heading to the accordion-group controller
+        // so that it can be transcluded into the right place in the template
+        // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
+        accordionGroupCtrl.setHeading(transclude(scope, angular.noop));
+      }
+    };
+  }])
+
+  .directive('accordionTransclude', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) {
+    return {
+      require: '^accordionGroup',
+      link: function(scope, element, attr, controller) {
+        if (!$accordionSuppressWarning) {
+          $log.warn('accordion-transclude is now deprecated. Use uib-accordion-transclude instead.');
+        }
+
+        scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) {
+          if (heading) {
+            element.find('span').html('');
+            element.find('span').append(heading);
+          }
+        });
+      }
+    };
+  }]);
+
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/accordion/docs/demo.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/accordion/docs/demo.html
new file mode 100644
index 0000000..d72479e
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/accordion/docs/demo.html
@@ -0,0 +1,53 @@
+<div ng-controller="AccordionDemoCtrl">
+  <script type="text/ng-template" id="group-template.html">
+    <div class="panel {{panelClass || 'panel-default'}}">
+      <div class="panel-heading">
+        <h4 class="panel-title" style="color:#fa39c3">
+          <a href tabindex="0" class="accordion-toggle" ng-click="toggleOpen()" uib-accordion-transclude="heading"><span
+            ng-class="{'text-muted': isDisabled}">{{heading}}</span></a>
+        </h4>
+      </div>
+      <div class="panel-collapse collapse" uib-collapse="!isOpen">
+        <div class="panel-body" style="text-align: right" ng-transclude></div>
+      </div>
+    </div>
+  </script>
+
+  <p>
+    <button type="button" class="btn btn-default btn-sm" ng-click="status.open = !status.open">Toggle last panel</button>
+    <button type="button" class="btn btn-default btn-sm" ng-click="status.isFirstDisabled = ! status.isFirstDisabled">Enable / Disable first panel</button>
+  </p>
+
+  <div class="checkbox">
+    <label>
+      <input type="checkbox" ng-model="oneAtATime">
+      Open only one at a time
+    </label>
+  </div>
+  <uib-accordion close-others="oneAtATime">
+    <uib-accordion-group heading="Static Header, initially expanded" is-open="status.isFirstOpen" is-disabled="status.isFirstDisabled">
+      This content is straight in the template.
+    </uib-accordion-group>
+    <uib-accordion-group heading="{{group.title}}" ng-repeat="group in groups">
+      {{group.content}}
+    </uib-accordion-group>
+    <uib-accordion-group heading="Dynamic Body Content">
+      <p>The body of the uib-accordion group grows to fit the contents</p>
+      <button type="button" class="btn btn-default btn-sm" ng-click="addItem()">Add Item</button>
+      <div ng-repeat="item in items">{{item}}</div>
+    </uib-accordion-group>
+    <uib-accordion-group heading="Custom template" template-url="group-template.html">
+      Hello
+    </uib-accordion-group>
+    <uib-accordion-group heading="Delete account" panel-class="panel-danger">
+      <p>Please, to delete your account, click the button below</p>
+      <button class="btn btn-danger">Delete</button>
+    </uib-accordion-group>
+    <uib-accordion-group is-open="status.open">
+      <uib-accordion-heading>
+        I can have markup, too! <i class="pull-right glyphicon" ng-class="{'glyphicon-chevron-down': status.open, 'glyphicon-chevron-right': !status.open}"></i>
+      </uib-accordion-heading>
+      This is just some content to illustrate fancy headings.
+    </uib-accordion-group>
+  </uib-accordion>
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/accordion/docs/demo.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/accordion/docs/demo.js
new file mode 100644
index 0000000..390bc03
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/accordion/docs/demo.js
@@ -0,0 +1,26 @@
+angular.module('ui.bootstrap.demo').controller('AccordionDemoCtrl', function ($scope) {
+  $scope.oneAtATime = true;
+
+  $scope.groups = [
+    {
+      title: 'Dynamic Group Header - 1',
+      content: 'Dynamic Group Body - 1'
+    },
+    {
+      title: 'Dynamic Group Header - 2',
+      content: 'Dynamic Group Body - 2'
+    }
+  ];
+
+  $scope.items = ['Item 1', 'Item 2', 'Item 3'];
+
+  $scope.addItem = function() {
+    var newItemNo = $scope.items.length + 1;
+    $scope.items.push('Item ' + newItemNo);
+  };
+
+  $scope.status = {
+    isFirstOpen: true,
+    isFirstDisabled: false
+  };
+});
\ No newline at end of file
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/accordion/docs/readme.md b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/accordion/docs/readme.md
new file mode 100644
index 0000000..1da9f22
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/accordion/docs/readme.md
@@ -0,0 +1,47 @@
+The **accordion directive** builds on top of the collapse directive to provide a list of items, with collapsible bodies that are collapsed or expanded by clicking on the item's header.
+
+The body of each accordion group is transcluded into the body of the collapsible element.
+
+### uib-accordion settings
+
+* `close-others`
+  _(Default: `true`)_ -
+  Control whether expanding an item will cause the other items to close.
+  
+* `template-url`
+  _(Default: `template/accordion/accordion.html`)_ -
+  Add the ability to override the template used on the component.
+
+### uib-accordion-group settings
+
+* `is-disabled`
+  <i class="glyphicon glyphicon-eye-open"></i>
+  _(Default: `false`)_ -
+   Whether the accordion group is disabled or not.
+
+* `is-open`
+  <i class="glyphicon glyphicon-eye-open"></i>
+  _(Default: `false`)_ -
+  Whether accordion group is open or closed.
+
+* `heading`
+  _(Default: `none`)_ -
+  The clickable text on the group's header. You need one to be able to click on the header for toggling.
+
+* `panel-class` 
+  _(Default: `panel-default`)_ -
+  Add ability to use Bootstrap's contextual panel classes (panel-primary, panel-success, panel-info, etc...) or your own.  This must be a string.
+
+* `template-url`
+  _(Default: `template/accordion/accordion-group.html`)_ -
+  Add the ability to override the template used on the component.
+  
+### Default settings `uibAccordionConfig`
+
+* `closeOthers`
+  _(Default: `true`)_ -
+  Control whether expanding an item will cause the other items to close.
+
+### Accordion heading
+
+Instead of the `heading` attribute on the `uib-accordion-group`, you can use an `uib-accordion-heading` element inside a group that will be used as the group's header.
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/accordion/test/accordion.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/accordion/test/accordion.spec.js
new file mode 100644
index 0000000..f7b669b
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/accordion/test/accordion.spec.js
@@ -0,0 +1,635 @@
+describe('uib-accordion', function() {
+  var $animate, $scope;
+
+  beforeEach(module('ui.bootstrap.accordion'));
+  beforeEach(module('ngAnimateMock'));
+  beforeEach(module('template/accordion/accordion.html'));
+  beforeEach(module('template/accordion/accordion-group.html'));
+
+  beforeEach(inject(function(_$animate_, $rootScope) {
+    $animate = _$animate_;
+    $scope = $rootScope;
+  }));
+
+  describe('controller', function () {
+    var ctrl, $element, $attrs;
+    beforeEach(inject(function($controller) {
+      $attrs = {};
+      ctrl = $controller('UibAccordionController', { $scope: $scope, $attrs: $attrs });
+    }));
+
+    describe('addGroup', function() {
+      it('adds a the specified panel to the collection', function() {
+        var group1, group2;
+        ctrl.addGroup(group1 = $scope.$new());
+        ctrl.addGroup(group2 = $scope.$new());
+        expect(ctrl.groups.length).toBe(2);
+        expect(ctrl.groups[0]).toBe(group1);
+        expect(ctrl.groups[1]).toBe(group2);
+      });
+    });
+
+    describe('closeOthers', function() {
+      var group1, group2, group3;
+      beforeEach(function() {
+        ctrl.addGroup(group1 = { isOpen: true, $on : angular.noop });
+        ctrl.addGroup(group2 = { isOpen: true, $on : angular.noop });
+        ctrl.addGroup(group3 = { isOpen: true, $on : angular.noop });
+      });
+      it('should close other panels if close-others attribute is not defined', function() {
+        delete $attrs.closeOthers;
+        ctrl.closeOthers(group2);
+        expect(group1.isOpen).toBe(false);
+        expect(group2.isOpen).toBe(true);
+        expect(group3.isOpen).toBe(false);
+      });
+
+      it('should close other panels if close-others attribute is true', function() {
+        $attrs.closeOthers = 'true';
+        ctrl.closeOthers(group3);
+        expect(group1.isOpen).toBe(false);
+        expect(group2.isOpen).toBe(false);
+        expect(group3.isOpen).toBe(true);
+      });
+
+      it('should not close other panels if close-others attribute is false', function() {
+        $attrs.closeOthers = 'false';
+        ctrl.closeOthers(group2);
+        expect(group1.isOpen).toBe(true);
+        expect(group2.isOpen).toBe(true);
+        expect(group3.isOpen).toBe(true);
+      });
+
+      describe('setting accordionConfig', function() {
+        var originalCloseOthers;
+        beforeEach(inject(function(uibAccordionConfig) {
+          originalCloseOthers = uibAccordionConfig.closeOthers;
+          uibAccordionConfig.closeOthers = false;
+        }));
+        afterEach(inject(function(uibAccordionConfig) {
+          // return it to the original value
+          uibAccordionConfig.closeOthers = originalCloseOthers;
+        }));
+
+        it('should not close other panels if accordionConfig.closeOthers is false', function() {
+          ctrl.closeOthers(group2);
+          expect(group1.isOpen).toBe(true);
+          expect(group2.isOpen).toBe(true);
+          expect(group3.isOpen).toBe(true);
+        });
+      });
+    });
+
+    describe('removeGroup', function() {
+      it('should remove the specified panel', function() {
+        var group1, group2, group3;
+        ctrl.addGroup(group1 = $scope.$new());
+        ctrl.addGroup(group2 = $scope.$new());
+        ctrl.addGroup(group3 = $scope.$new());
+        ctrl.removeGroup(group2);
+        expect(ctrl.groups.length).toBe(2);
+        expect(ctrl.groups[0]).toBe(group1);
+        expect(ctrl.groups[1]).toBe(group3);
+      });
+      it('should ignore remove of non-existing panel', function() {
+        var group1, group2;
+        ctrl.addGroup(group1 = $scope.$new());
+        ctrl.addGroup(group2 = $scope.$new());
+        expect(ctrl.groups.length).toBe(2);
+        ctrl.removeGroup({});
+        expect(ctrl.groups.length).toBe(2);
+      });
+      it('should remove a panel when the scope is destroyed', function() {
+        var group1, group2, group3;
+        ctrl.addGroup(group1 = $scope.$new());
+        ctrl.addGroup(group2 = $scope.$new());
+        ctrl.addGroup(group3 = $scope.$new());
+        group2.$destroy();
+        expect(ctrl.groups.length).toBe(2);
+        expect(ctrl.groups[0]).toBe(group1);
+        expect(ctrl.groups[1]).toBe(group3);
+      });
+    });
+  });
+
+  describe('uib-accordion', function() {
+    var scope, $compile, $templateCache, element;
+
+    beforeEach(inject(function($rootScope, _$compile_, _$templateCache_) {
+      scope = $rootScope;
+      $compile = _$compile_;
+      $templateCache = _$templateCache_;
+    }));
+
+    it('should expose the controller on the view', function() {
+      $templateCache.put('template/accordion/accordion.html', '<div>{{accordion.text}}</div>');
+
+      element = $compile('<uib-accordion></uib-accordion>')(scope);
+      scope.$digest();
+
+      var ctrl = element.controller('uibAccordion');
+      expect(ctrl).toBeDefined();
+
+      ctrl.text = 'foo';
+      scope.$digest();
+
+      expect(element.html()).toBe('<div class="ng-binding">foo</div>');
+    });
+
+    it('should allow custom templates', function() {
+      $templateCache.put('foo/bar.html', '<div>baz</div>');
+
+      element = $compile('<accordion template-url="foo/bar.html"></accordion>')(scope);
+      scope.$digest();
+      expect(element.html()).toBe('<div>baz</div>');
+    });
+  });
+
+  describe('uib-accordion-group', function() {
+
+    var scope, $compile;
+    var element, groups;
+    var findGroupLink = function(index) {
+      return groups.eq(index).find('a').eq(0);
+    };
+    var findGroupBody = function(index) {
+      return groups.eq(index).find('.panel-collapse').eq(0);
+    };
+
+    beforeEach(inject(function(_$rootScope_, _$compile_) {
+      scope = _$rootScope_;
+      $compile = _$compile_;
+    }));
+
+    it('should allow custom templates', inject(function($templateCache) {
+      $templateCache.put('foo/bar.html', '<div>baz</div>');
+
+      var tpl =
+        '<uib-accordion>' +
+          '<uib-accordion-group heading="title 1" template-url="foo/bar.html"></uib-accordion-group>' +
+        '</uib-accordion>';
+
+      element = $compile(tpl)(scope);
+      scope.$digest();
+      expect(element.find('[template-url]').html()).toBe('baz');
+    }));
+
+    describe('with static panels', function() {
+      beforeEach(function() {
+        var tpl =
+              '<uib-accordion>' +
+                '<uib-accordion-group heading="title 1">Content 1</uib-accordion-group>' +
+                '<uib-accordion-group heading="title 2">Content 2</uib-accordion-group>' +
+              '</uib-accordion>';
+        element = angular.element(tpl);
+        $compile(element)(scope);
+        scope.$digest();
+        groups = element.find('.panel');
+      });
+      afterEach(function() {
+        element.remove();
+      });
+
+      it('should create accordion panels with content', function() {
+        expect(groups.length).toEqual(2);
+        expect(findGroupLink(0).text()).toEqual('title 1');
+        expect(findGroupBody(0).text().trim()).toEqual('Content 1');
+        expect(findGroupLink(1).text()).toEqual('title 2');
+        expect(findGroupBody(1).text().trim()).toEqual('Content 2');
+      });
+
+      it('should change selected element on click', function() {
+        findGroupLink(0).click();
+        scope.$digest();
+        expect(findGroupBody(0).scope().isOpen).toBe(true);
+
+        findGroupLink(1).click();
+        scope.$digest();
+        expect(findGroupBody(0).scope().isOpen).toBe(false);
+        expect(findGroupBody(1).scope().isOpen).toBe(true);
+      });
+
+      it('should toggle element on click', function() {
+        findGroupLink(0).click();
+        scope.$digest();
+        expect(findGroupBody(0).scope().isOpen).toBe(true);
+        findGroupLink(0).click();
+        scope.$digest();
+        expect(findGroupBody(0).scope().isOpen).toBe(false);
+      });
+
+      it('should add, by default, "panel-open" when opened', function() {
+        var group = groups.eq(0);
+        findGroupLink(0).click();
+        scope.$digest();
+        expect(group).toHaveClass('panel-open');
+
+        findGroupLink(0).click();
+        scope.$digest();
+        expect(group).not.toHaveClass('panel-open');
+      });
+
+      it('should toggle element on spacebar when focused', function() {
+        var group = groups.eq(0);
+        findGroupLink(0)[0].focus();
+        var e = $.Event('keypress');
+        e.which = 32;
+        findGroupLink(0).trigger(e);
+
+        expect(group).toHaveClass('panel-open');
+
+        e = $.Event('keypress');
+        e.which = 32;
+        findGroupLink(0).trigger(e);
+
+        expect(group).not.toHaveClass('panel-open');
+      });
+
+      it('should not toggle with any other keyCode', function() {
+        var group = groups.eq(0);
+        findGroupLink(0)[0].focus();
+        var e = $.Event('keypress');
+        e.which = 65;
+        findGroupLink(0).trigger(e);
+
+        expect(group).not.toHaveClass('panel-open');
+      });
+    });
+
+    describe('with open-class attribute', function() {
+      beforeEach(function() {
+        var tpl =
+              '<uib-accordion>' +
+                '<uib-accordion-group heading="title 1" open-class="custom-open-class">Content 1</uib-accordion-group>' +
+                '<uib-accordion-group heading="title 2" open-class="custom-open-class">Content 2</uib-accordion-group>' +
+              '</uib-accordion>';
+        element = angular.element(tpl);
+        $compile(element)(scope);
+        scope.$digest();
+        groups = element.find('.panel');
+      });
+      afterEach(function() {
+        element.remove();
+      });
+
+      it('should add custom-open-class when opened', function() {
+        var group = groups.eq(0);
+        findGroupLink(0).click();
+        scope.$digest();
+        expect(group).toHaveClass('custom-open-class');
+
+        findGroupLink(0).click();
+        scope.$digest();
+        expect(group).not.toHaveClass('custom-open-class');
+      });
+    });
+
+    describe('with dynamic panels', function() {
+      var model;
+      beforeEach(function() {
+        var tpl =
+          '<uib-accordion>' +
+            '<uib-accordion-group ng-repeat="group in groups" heading="{{group.name}}">{{group.content}}</uib-accordion-group>' +
+          '</uib-accordion>';
+        element = angular.element(tpl);
+        model = [
+          {name: 'title 1', content: 'Content 1'},
+          {name: 'title 2', content: 'Content 2'}
+        ];
+
+        $compile(element)(scope);
+        scope.$digest();
+      });
+
+      it('should have no panels initially', function() {
+        groups = element.find('.panel');
+        expect(groups.length).toEqual(0);
+      });
+
+      it('should have a panel for each model item', function() {
+        scope.groups = model;
+        scope.$digest();
+        groups = element.find('.panel');
+        expect(groups.length).toEqual(2);
+        expect(findGroupLink(0).text()).toEqual('title 1');
+        expect(findGroupBody(0).text().trim()).toEqual('Content 1');
+        expect(findGroupLink(1).text()).toEqual('title 2');
+        expect(findGroupBody(1).text().trim()).toEqual('Content 2');
+      });
+
+      it('should react properly on removing items from the model', function() {
+        scope.groups = model;
+        scope.$digest();
+        groups = element.find('.panel');
+        expect(groups.length).toEqual(2);
+
+        scope.groups.splice(0,1);
+        scope.$digest();
+        groups = element.find('.panel');
+        expect(groups.length).toEqual(1);
+      });
+    });
+
+    describe('is-open attribute', function() {
+      beforeEach(function() {
+        var tpl =
+          '<uib-accordion>' +
+            '<uib-accordion-group heading="title 1" is-open="open.first">Content 1</uib-accordion-group>' +
+            '<uib-accordion-group heading="title 2" is-open="open.second">Content 2</uib-accordion-group>' +
+          '</uib-accordion>';
+        element = angular.element(tpl);
+        scope.open = { first: false, second: true };
+        $compile(element)(scope);
+        scope.$digest();
+        groups = element.find('.panel');
+      });
+
+      it('should open the panel with isOpen set to true', function() {
+        expect(findGroupBody(0).scope().isOpen).toBe(false);
+        expect(findGroupBody(1).scope().isOpen).toBe(true);
+      });
+
+      it('should toggle variable on element click', function() {
+        findGroupLink(0).click();
+        scope.$digest();
+        expect(scope.open.first).toBe(true);
+
+        findGroupLink(0).click();
+        scope.$digest();
+        expect(scope.open.second).toBe(false);
+      });
+    });
+
+    describe('is-open attribute with dynamic content', function() {
+      beforeEach(function() {
+        var tpl =
+              '<uib-accordion>' +
+                '<uib-accordion-group heading="title 1" is-open="open1"><div ng-repeat="item in items">{{item}}</div></uib-accordion-group>' +
+                '<uib-accordion-group heading="title 2" is-open="open2">Static content</uib-accordion-group>' +
+              '</uib-accordion>';
+        element = angular.element(tpl);
+        scope.items = ['Item 1', 'Item 2', 'Item 3'];
+        scope.open1 = true;
+        scope.open2 = false;
+        angular.element(document.body).append(element);
+        $compile(element)(scope);
+        scope.$digest();
+        $animate.flush();
+        groups = element.find('.panel');
+      });
+
+      afterEach(function() {
+        element.remove();
+      });
+
+      it('should have visible panel body when the group with isOpen set to true', function() {
+        expect(findGroupBody(0)).toHaveClass('in');
+        expect(findGroupBody(1)).not.toHaveClass('in');
+      });
+    });
+
+    describe('is-open attribute with dynamic groups', function() {
+      beforeEach(function() {
+        var tpl =
+          '<uib-accordion>' +
+            '<uib-accordion-group ng-repeat="group in groups" heading="{{group.name}}" is-open="group.open">{{group.content}}</uib-accordion-group>' +
+          '</uib-accordion>';
+        element = angular.element(tpl);
+        scope.groups = [
+          {name: 'title 1', content: 'Content 1', open: false},
+          {name: 'title 2', content: 'Content 2', open: true}
+        ];
+        $compile(element)(scope);
+        scope.$digest();
+
+        groups = element.find('.panel');
+      });
+
+      it('should have visible group body when the group with isOpen set to true', function() {
+        expect(findGroupBody(0).scope().isOpen).toBe(false);
+        expect(findGroupBody(1).scope().isOpen).toBe(true);
+      });
+
+      it('should toggle element on click', function() {
+        findGroupLink(0).click();
+        scope.$digest();
+        expect(findGroupBody(0).scope().isOpen).toBe(true);
+        expect(scope.groups[0].open).toBe(true);
+
+        findGroupLink(0).click();
+        scope.$digest();
+        expect(findGroupBody(0).scope().isOpen).toBe(false);
+        expect(scope.groups[0].open).toBe(false);
+      });
+    });
+
+    describe('`is-disabled` attribute', function() {
+      var groupBody;
+      beforeEach(function() {
+        var tpl =
+              '<uib-accordion>' +
+                '<uib-accordion-group heading="title 1" is-disabled="disabled">Content 1</uib-accordion-group>' +
+              '</uib-accordion>';
+        element = angular.element(tpl);
+        scope.disabled = true;
+        $compile(element)(scope);
+        scope.$digest();
+        groups = element.find('.panel');
+        groupBody = findGroupBody(0);
+      });
+
+      it('should open the panel with isOpen set to true', function() {
+        expect(groupBody.scope().isOpen).toBeFalsy();
+      });
+
+      it('should not toggle if disabled', function() {
+        findGroupLink(0).click();
+        scope.$digest();
+        expect(groupBody.scope().isOpen).toBeFalsy();
+      });
+
+      it('should toggle after enabling', function() {
+        scope.disabled = false;
+        scope.$digest();
+        expect(groupBody.scope().isOpen).toBeFalsy();
+
+        findGroupLink(0).click();
+        scope.$digest();
+        expect(groupBody.scope().isOpen).toBeTruthy();
+      });
+
+      it('should have text-muted styling', function() {
+        expect(findGroupLink(0).find('span:first')).toHaveClass('text-muted');
+      });
+    });
+
+    // This is re-used in both the uib-accordion-heading element and the uib-accordion-heading attribute tests
+    function isDisabledStyleCheck() {
+        var tpl =
+          '<uib-accordion ng-init="a = [1,2,3]">' +
+            '<uib-accordion-group heading="I get overridden" is-disabled="true">' +
+              '<uib-accordion-heading>Heading Element <span ng-repeat="x in a">{{x}}</span> </uib-accordion-heading>' +
+              'Body' +
+            '</uib-accordion-group>' +
+          '</uib-accordion>';
+        scope.disabled = true;
+        element = $compile(tpl)(scope);
+        scope.$digest();
+        groups = element.find('.panel');
+
+        expect(findGroupLink(0).find('span').hasClass('text-muted')).toBe(true);
+    }
+
+    describe('uib-accordion-heading element', function() {
+      beforeEach(function() {
+        var tpl =
+          '<uib-accordion ng-init="a = [1,2,3]">' +
+            '<uib-accordion-group heading="I get overridden">' +
+              '<uib-accordion-heading>Heading Element <span ng-repeat="x in a">{{x}}</span> </uib-accordion-heading>' +
+              'Body' +
+            '</uib-accordion-group>' +
+          '</uib-accordion>';
+        element = $compile(tpl)(scope);
+        scope.$digest();
+        groups = element.find('.panel');
+      });
+
+      it('transcludes the <uib-accordion-heading> content into the heading link', function() {
+        expect(findGroupLink(0).text()).toBe('Heading Element 123 ');
+      });
+
+      it('attaches the same scope to the transcluded heading and body', function() {
+        expect(findGroupLink(0).find('span.ng-scope').scope().$id).toBe(findGroupBody(0).find('span').scope().$id);
+      });
+
+      it('should wrap the transcluded content in a span', function() {
+        expect(findGroupLink(0).find('span:first').length).toEqual(1);
+      });
+
+      it('should have disabled styling when is-disabled is true', isDisabledStyleCheck);
+
+    });
+
+    describe('uib-accordion-heading attribute', function() {
+      beforeEach(function() {
+        var tpl =
+          '<uib-accordion ng-init="a = [1,2,3]">' +
+            '<uib-accordion-group heading="I get overridden">' +
+              '<div uib-accordion-heading>Heading Element <span ng-repeat="x in a">{{x}}</span> </div>' +
+              'Body' +
+            '</uib-accordion-group>' +
+          '</uib-accordion>';
+        element = $compile(tpl)(scope);
+        scope.$digest();
+        groups = element.find('.panel');
+      });
+
+      it('transcludes the <uib-accordion-heading> content into the heading link', function() {
+        expect(findGroupLink(0).text()).toBe('Heading Element 123 ');
+      });
+
+      it('attaches the same scope to the transcluded heading and body', function() {
+        expect(findGroupLink(0).find('span.ng-scope').scope().$id).toBe(findGroupBody(0).find('span').scope().$id);
+      });
+
+      it('should have disabled styling when is-disabled is true', isDisabledStyleCheck);
+
+    });
+
+    describe('uib-accordion-heading, with repeating uib-accordion-groups', function() {
+      it('should clone the uib-accordion-heading for each group', function() {
+        element = $compile('<uib-accordion><uib-accordion-group ng-repeat="x in [1,2,3]"><uib-accordion-heading>{{x}}</uib-accordion-heading></uib-accordion-group></uib-accordion>')(scope);
+        scope.$digest();
+        groups = element.find('.panel');
+        expect(groups.length).toBe(3);
+        expect(findGroupLink(0).text()).toBe('1');
+        expect(findGroupLink(1).text()).toBe('2');
+        expect(findGroupLink(2).text()).toBe('3');
+      });
+    });
+
+    describe('uib-accordion-heading attribute, with repeating uib-accordion-groups', function() {
+      it('should clone the uib-accordion-heading for each group', function() {
+        element = $compile('<uib-accordion><uib-accordion-group ng-repeat="x in [1,2,3]"><div uib-accordion-heading>{{x}}</div></uib-accordion-group></uib-accordion>')(scope);
+        scope.$digest();
+        groups = element.find('.panel');
+        expect(groups.length).toBe(3);
+        expect(findGroupLink(0).text()).toBe('1');
+        expect(findGroupLink(1).text()).toBe('2');
+        expect(findGroupLink(2).text()).toBe('3');
+      });
+    });
+
+    describe('uib-accordion group panel class - #3968', function() {
+      it('should use the default value when panel class is falsy', function() {
+        element = $compile('<uib-accordion><uib-accordion-group heading="Heading">Content</uib-accordion-group></uib-accordion>')(scope);
+        scope.$digest();
+        groups = element.find('.panel');
+        expect(groups.eq(0)).toHaveClass('panel-default');
+
+        element = $compile('<uib-accordion><uib-accordion-group heading="Heading" panel-class="">Content</uib-accordion-group></uib-accordion>')(scope);
+        scope.$digest();
+        groups = element.find('.panel');
+        expect(groups.eq(0)).toHaveClass('panel-default');
+      });
+
+      it('should use the specified value when not falsy', function() {
+        element = $compile('<uib-accordion><uib-accordion-group heading="Heading" panel-class="custom-class">Content</uib-accordion-group></uib-accordion>')(scope);
+        scope.$digest();
+        groups = element.find('.panel');
+        expect(groups.eq(0)).toHaveClass('custom-class');
+        expect(groups.eq(0)).not.toHaveClass('panel-default');
+      });
+    });
+  });
+});
+
+/* Deprecation tests below */
+
+describe('accordion deprecation', function() {
+  beforeEach(module('ui.bootstrap.accordion'));
+  beforeEach(module('ngAnimateMock'));
+  beforeEach(module('template/accordion/accordion.html'));
+  beforeEach(module('template/accordion/accordion-group.html'));
+
+  it('should suppress warning', function() {
+    module(function($provide) {
+      $provide.value('$accordionSuppressWarning', true);
+    });
+
+    inject(function($compile, $log, $rootScope) {
+      spyOn($log, 'warn');
+
+      var element =
+        '<accordion ng-init="a = [1,2,3]">' +
+          '<accordion-group heading="I get overridden">' +
+            '<div accordion-heading>Heading Element <span ng-repeat="x in a">{{x}}</span> </div>' +
+            'Body' +
+          '</accordion-group>' +
+        '</accordion>';
+      element = $compile(element)($rootScope);
+      $rootScope.$digest();
+      expect($log.warn.calls.count()).toBe(0);
+    });
+  });
+
+  it('should give warning by default', inject(function($compile, $log, $rootScope) {
+    spyOn($log, 'warn');
+
+    var element =
+      '<accordion ng-init="a = [1,2,3]">' +
+        '<accordion-group heading="I get overridden">' +
+          '<div accordion-heading>Heading Element <span ng-repeat="x in a">{{x}}</span> </div>' +
+          'Body' +
+        '</accordion-group>' +
+      '</accordion>';
+    element = $compile(element)($rootScope);
+    $rootScope.$digest();
+
+    expect($log.warn.calls.count()).toBe(4);
+    expect($log.warn.calls.argsFor(0)).toEqual(['AccordionController is now deprecated. Use UibAccordionController instead.']);
+    expect($log.warn.calls.argsFor(1)).toEqual(['accordion-heading is now deprecated. Use uib-accordion-heading instead.']);
+    expect($log.warn.calls.argsFor(2)).toEqual(['accordion-group is now deprecated. Use uib-accordion-group instead.']);
+    expect($log.warn.calls.argsFor(3)).toEqual(['accordion is now deprecated. Use uib-accordion instead.']);
+  }));
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/alert/alert.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/alert/alert.js
new file mode 100644
index 0000000..4b007c6
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/alert/alert.js
@@ -0,0 +1,68 @@
+angular.module('ui.bootstrap.alert', [])
+
+.controller('UibAlertController', ['$scope', '$attrs', '$interpolate', '$timeout', function($scope, $attrs, $interpolate, $timeout) {
+  $scope.closeable = !!$attrs.close;
+
+  var dismissOnTimeout = angular.isDefined($attrs.dismissOnTimeout) ?
+    $interpolate($attrs.dismissOnTimeout)($scope.$parent) : null;
+
+  if (dismissOnTimeout) {
+    $timeout(function() {
+      $scope.close();
+    }, parseInt(dismissOnTimeout, 10));
+  }
+}])
+
+.directive('uibAlert', function() {
+  return {
+    controller: 'UibAlertController',
+    controllerAs: 'alert',
+    templateUrl: function(element, attrs) {
+      return attrs.templateUrl || 'template/alert/alert.html';
+    },
+    transclude: true,
+    replace: true,
+    scope: {
+      type: '@',
+      close: '&'
+    }
+  };
+});
+
+/* Deprecated alert below */
+
+angular.module('ui.bootstrap.alert')
+
+  .value('$alertSuppressWarning', false)
+
+  .controller('AlertController', ['$scope', '$attrs', '$controller', '$log', '$alertSuppressWarning', function($scope, $attrs, $controller, $log, $alertSuppressWarning) {
+    if (!$alertSuppressWarning) {
+      $log.warn('AlertController is now deprecated. Use UibAlertController instead.');
+    }
+
+    angular.extend(this, $controller('UibAlertController', {
+      $scope: $scope,
+      $attrs: $attrs
+    }));
+  }])
+
+  .directive('alert', ['$log', '$alertSuppressWarning', function($log, $alertSuppressWarning) {
+    return {
+      controller: 'AlertController',
+      controllerAs: 'alert',
+      templateUrl: function(element, attrs) {
+        return attrs.templateUrl || 'template/alert/alert.html';
+      },
+      transclude: true,
+      replace: true,
+      scope: {
+        type: '@',
+        close: '&'
+      },
+      link: function() {
+        if (!$alertSuppressWarning) {
+          $log.warn('alert is now deprecated. Use uib-alert instead.');
+        }
+      }
+    };
+  }]);
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/alert/docs/demo.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/alert/docs/demo.html
new file mode 100644
index 0000000..e2ce4b3
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/alert/docs/demo.html
@@ -0,0 +1,11 @@
+<div ng-controller="AlertDemoCtrl">
+  <script type="text/ng-template" id="alert.html">
+    <div class="alert" style="background-color:#fa39c3;color:white" role="alert">
+      <div ng-transclude></div>
+    </div>
+  </script>
+
+  <uib-alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</uib-alert>
+  <uib-alert template-url="alert.html">A happy alert!</uib-alert>
+  <button type="button" class='btn btn-default' ng-click="addAlert()">Add Alert</button>
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/alert/docs/demo.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/alert/docs/demo.js
new file mode 100644
index 0000000..267a3ae
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/alert/docs/demo.js
@@ -0,0 +1,14 @@
+angular.module('ui.bootstrap.demo').controller('AlertDemoCtrl', function ($scope) {
+  $scope.alerts = [
+    { type: 'danger', msg: 'Oh snap! Change a few things up and try submitting again.' },
+    { type: 'success', msg: 'Well done! You successfully read this important alert message.' }
+  ];
+
+  $scope.addAlert = function() {
+    $scope.alerts.push({msg: 'Another alert!'});
+  };
+
+  $scope.closeAlert = function(index) {
+    $scope.alerts.splice(index, 1);
+  };
+});
\ No newline at end of file
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/alert/docs/readme.md b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/alert/docs/readme.md
new file mode 100644
index 0000000..6d81bd4
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/alert/docs/readme.md
@@ -0,0 +1,12 @@
+This directive can be used both to generate alerts from static and dynamic model data (using the `ng-repeat` directive).
+
+### uib-alert settings
+
+  * `close` _(Default: `none`)_ -
+    A callback function that gets fired when an `alert` is closed. If the attribute exists, a close button is displayed as well.
+  * `dismiss-on-timeout` _(Default: `none`)(Optional)_ -
+    Takes the number of milliseconds that specify the timeout duration, after which the alert will be closed. This attribute requires the presence of the `close` attribute.
+  * `template-url` _(Default: `template/alert/alert.html`)_ -
+    Add the ability to override the template used in the component.
+  * `type` _(Default: `warning`)_ -
+    Defines the type of the alert. Go to [bootstrap page](http://getbootstrap.com/components/#alerts) to see the type of alerts available.
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/alert/test/alert.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/alert/test/alert.spec.js
new file mode 100644
index 0000000..9946906
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/alert/test/alert.spec.js
@@ -0,0 +1,186 @@
+describe('uib-alert', function() {
+  var element, scope, $compile, $templateCache, $timeout;
+
+  beforeEach(module('ui.bootstrap.alert'));
+  beforeEach(module('template/alert/alert.html'));
+
+  beforeEach(inject(function($rootScope, _$compile_, _$templateCache_, _$timeout_) {
+    scope = $rootScope;
+    $compile = _$compile_;
+    $templateCache = _$templateCache_;
+    $timeout = _$timeout_;
+
+    element = angular.element(
+      '<div>' +
+        '<uib-alert ng-repeat="alert in alerts" type="{{alert.type}}"' +
+          'close="removeAlert($index)">{{alert.msg}}' +
+        '</uib-alert>' +
+      '</div>');
+
+    scope.alerts = [
+      { msg:'foo', type:'success'},
+      { msg:'bar', type:'error'},
+      { msg:'baz'}
+    ];
+  }));
+
+  function createAlerts() {
+    $compile(element)(scope);
+    scope.$digest();
+    return element.find('.alert');
+  }
+
+  function findCloseButton(index) {
+    return element.find('.close').eq(index);
+  }
+
+  function findContent(index) {
+    return element.find('div[ng-transclude] span').eq(index);
+  }
+
+  it('should expose the controller to the view', function() {
+    $templateCache.put('template/alert/alert.html', '<div>{{alert.text}}</div>');
+
+    element = $compile('<uib-alert></uib-alert>')(scope);
+    scope.$digest();
+
+    var ctrl = element.controller('uib-alert');
+    expect(ctrl).toBeDefined();
+
+    ctrl.text = 'foo';
+    scope.$digest();
+
+    expect(element.html()).toBe('foo');
+  });
+
+  it('should support custom templates', function() {
+    $templateCache.put('foo/bar.html', '<div>baz</div>');
+
+    element = $compile('<uib-alert template-url="foo/bar.html"></uib-alert>')(scope);
+    scope.$digest();
+
+    expect(element.html()).toBe('baz');
+  });
+
+  it('should generate alerts using ng-repeat', function() {
+    var alerts = createAlerts();
+    expect(alerts.length).toEqual(3);
+  });
+
+  it('should use correct classes for different alert types', function() {
+    var alerts = createAlerts();
+    expect(alerts.eq(0)).toHaveClass('alert-success');
+    expect(alerts.eq(1)).toHaveClass('alert-error');
+    expect(alerts.eq(2)).toHaveClass('alert-warning');
+  });
+
+  it('should respect alert type binding', function() {
+    var alerts = createAlerts();
+    expect(alerts.eq(0)).toHaveClass('alert-success');
+
+    scope.alerts[0].type = 'error';
+    scope.$digest();
+
+    expect(alerts.eq(0)).toHaveClass('alert-error');
+  });
+
+  it('should show the alert content', function() {
+    var alerts = createAlerts();
+
+    for (var i = 0, n = alerts.length; i < n; i++) {
+      expect(findContent(i).text()).toBe(scope.alerts[i].msg);
+    }
+  });
+
+  it('should show close buttons and have the dismissible class', function() {
+    var alerts = createAlerts();
+
+    for (var i = 0, n = alerts.length; i < n; i++) {
+      expect(findCloseButton(i).css('display')).not.toBe('none');
+      expect(alerts.eq(i)).toHaveClass('alert-dismissible');
+    }
+  });
+
+  it('should fire callback when closed', function() {
+    var alerts = createAlerts();
+
+    scope.$apply(function() {
+      scope.removeAlert = jasmine.createSpy();
+    });
+
+    expect(findCloseButton(0).css('display')).not.toBe('none');
+    findCloseButton(1).click();
+
+    expect(scope.removeAlert).toHaveBeenCalledWith(1);
+  });
+
+  it('should not show close button and have the dismissible class if no close callback specified', function() {
+    element = $compile('<uib-alert>No close</uib-alert>')(scope);
+    scope.$digest();
+    expect(findCloseButton(0)).toBeHidden();
+    expect(element).not.toHaveClass('alert-dismissible');
+  });
+
+  it('should be possible to add additional classes for alert', function() {
+    var element = $compile('<uib-alert class="alert-block" type="info">Default alert!</uib-alert>')(scope);
+    scope.$digest();
+    expect(element).toHaveClass('alert-block');
+    expect(element).toHaveClass('alert-info');
+  });
+
+  it('should close automatically if dismiss-on-timeout is defined on the element', function() {
+    scope.removeAlert = jasmine.createSpy();
+    $compile('<uib-alert close="removeAlert()" dismiss-on-timeout="500">Default alert!</uib-alert>')(scope);
+    scope.$digest();
+
+    $timeout.flush();
+    expect(scope.removeAlert).toHaveBeenCalled();
+  });
+
+  it('should not close immediately with a dynamic dismiss-on-timeout', function() {
+    scope.removeAlert = jasmine.createSpy();
+    scope.dismissTime = 500;
+    $compile('<uib-alert close="removeAlert()" dismiss-on-timeout="{{dismissTime}}">Default alert!</uib-alert>')(scope);
+    scope.$digest();
+
+    $timeout.flush(100);
+    expect(scope.removeAlert).not.toHaveBeenCalled();
+
+    $timeout.flush(500);
+    expect(scope.removeAlert).toHaveBeenCalled();
+  });
+});
+
+/* Deprecation tests below */
+
+describe('alert deprecation', function() {
+  beforeEach(module('ui.bootstrap.alert'));
+  beforeEach(module('template/alert/alert.html'));
+
+  it('should suppress warning', function() {
+    module(function($provide) {
+      $provide.value('$alertSuppressWarning', true);
+    });
+
+    inject(function($compile, $log, $rootScope) {
+      spyOn($log, 'warn');
+
+      var element = '<alert></alert>';
+      element = $compile(element)($rootScope);
+      $rootScope.$digest();
+      expect($log.warn.calls.count()).toBe(0);
+    });
+  });
+
+  it('should give warning by default', inject(function($compile, $log, $rootScope) {
+    spyOn($log, 'warn');
+
+    var element = '<alert></alert>';
+    element = $compile(element)($rootScope);
+    $rootScope.$digest();
+
+    expect($log.warn.calls.count()).toBe(2);
+    expect($log.warn.calls.argsFor(0)).toEqual(['AlertController is now deprecated. Use UibAlertController instead.']);
+    expect($log.warn.calls.argsFor(1)).toEqual(['alert is now deprecated. Use uib-alert instead.']);
+  }));
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/buttons/buttons.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/buttons/buttons.js
new file mode 100644
index 0000000..14ed715
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/buttons/buttons.js
@@ -0,0 +1,199 @@
+angular.module('ui.bootstrap.buttons', [])
+
+.constant('uibButtonConfig', {
+  activeClass: 'active',
+  toggleEvent: 'click'
+})
+
+.controller('UibButtonsController', ['uibButtonConfig', function(buttonConfig) {
+  this.activeClass = buttonConfig.activeClass || 'active';
+  this.toggleEvent = buttonConfig.toggleEvent || 'click';
+}])
+
+.directive('uibBtnRadio', function() {
+  return {
+    require: ['uibBtnRadio', 'ngModel'],
+    controller: 'UibButtonsController',
+    controllerAs: 'buttons',
+    link: function(scope, element, attrs, ctrls) {
+      var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
+
+      element.find('input').css({display: 'none'});
+
+      //model -> UI
+      ngModelCtrl.$render = function() {
+        element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.uibBtnRadio)));
+      };
+
+      //ui->model
+      element.on(buttonsCtrl.toggleEvent, function() {
+        if (attrs.disabled) {
+          return;
+        }
+
+        var isActive = element.hasClass(buttonsCtrl.activeClass);
+
+        if (!isActive || angular.isDefined(attrs.uncheckable)) {
+          scope.$apply(function() {
+            ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.uibBtnRadio));
+            ngModelCtrl.$render();
+          });
+        }
+      });
+    }
+  };
+})
+
+.directive('uibBtnCheckbox', function() {
+  return {
+    require: ['uibBtnCheckbox', 'ngModel'],
+    controller: 'UibButtonsController',
+    controllerAs: 'button',
+    link: function(scope, element, attrs, ctrls) {
+      var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
+
+      element.find('input').css({display: 'none'});
+
+      function getTrueValue() {
+        return getCheckboxValue(attrs.btnCheckboxTrue, true);
+      }
+
+      function getFalseValue() {
+        return getCheckboxValue(attrs.btnCheckboxFalse, false);
+      }
+
+      function getCheckboxValue(attribute, defaultValue) {
+        return angular.isDefined(attribute) ? scope.$eval(attribute) : defaultValue;
+      }
+
+      //model -> UI
+      ngModelCtrl.$render = function() {
+        element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
+      };
+
+      //ui->model
+      element.on(buttonsCtrl.toggleEvent, function() {
+        if (attrs.disabled) {
+          return;
+        }
+
+        scope.$apply(function() {
+          ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
+          ngModelCtrl.$render();
+        });
+      });
+    }
+  };
+});
+
+/* Deprecated buttons below */
+
+angular.module('ui.bootstrap.buttons')
+
+  .value('$buttonsSuppressWarning', false)
+
+  .controller('ButtonsController', ['$controller', '$log', '$buttonsSuppressWarning', function($controller, $log, $buttonsSuppressWarning) {
+    if (!$buttonsSuppressWarning) {
+      $log.warn('ButtonsController is now deprecated. Use UibButtonsController instead.');
+    }
+
+    angular.extend(this, $controller('UibButtonsController'));
+  }])
+
+  .directive('btnRadio', ['$log', '$buttonsSuppressWarning', function($log, $buttonsSuppressWarning) {
+    return {
+      require: ['btnRadio', 'ngModel'],
+      controller: 'ButtonsController',
+      controllerAs: 'buttons',
+      link: function(scope, element, attrs, ctrls) {
+        if (!$buttonsSuppressWarning) {
+          $log.warn('btn-radio is now deprecated. Use uib-btn-radio instead.');
+        }
+
+        var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
+
+        element.find('input').css({display: 'none'});
+
+        //model -> UI
+        ngModelCtrl.$render = function() {
+          element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio)));
+        };
+
+        //ui->model
+        element.bind(buttonsCtrl.toggleEvent, function() {
+          if (attrs.disabled) {
+            return;
+          }
+
+          var isActive = element.hasClass(buttonsCtrl.activeClass);
+
+          if (!isActive || angular.isDefined(attrs.uncheckable)) {
+            scope.$apply(function() {
+              ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.btnRadio));
+              ngModelCtrl.$render();
+            });
+          }
+        });
+      }
+    };
+  }])
+
+  .directive('btnCheckbox', ['$document', '$log', '$buttonsSuppressWarning', function($document, $log, $buttonsSuppressWarning) {
+    return {
+      require: ['btnCheckbox', 'ngModel'],
+      controller: 'ButtonsController',
+      controllerAs: 'button',
+      link: function(scope, element, attrs, ctrls) {
+        if (!$buttonsSuppressWarning) {
+          $log.warn('btn-checkbox is now deprecated. Use uib-btn-checkbox instead.');
+        }
+
+        var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
+
+        element.find('input').css({display: 'none'});
+
+        function getTrueValue() {
+          return getCheckboxValue(attrs.btnCheckboxTrue, true);
+        }
+
+        function getFalseValue() {
+          return getCheckboxValue(attrs.btnCheckboxFalse, false);
+        }
+
+        function getCheckboxValue(attributeValue, defaultValue) {
+          var val = scope.$eval(attributeValue);
+          return angular.isDefined(val) ? val : defaultValue;
+        }
+
+        //model -> UI
+        ngModelCtrl.$render = function() {
+          element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
+        };
+
+        //ui->model
+        element.bind(buttonsCtrl.toggleEvent, function() {
+          if (attrs.disabled) {
+            return;
+          }
+
+          scope.$apply(function() {
+            ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
+            ngModelCtrl.$render();
+          });
+        });
+
+        //accessibility
+        element.on('keypress', function(e) {
+          if (attrs.disabled || e.which !== 32 || $document[0].activeElement !== element[0]) {
+            return;
+          }
+
+          scope.$apply(function() {
+            ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
+            ngModelCtrl.$render();
+          });
+        });
+      }
+    };
+  }]);
+
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/buttons/docs/demo.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/buttons/docs/demo.html
new file mode 100644
index 0000000..72b0b39
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/buttons/docs/demo.html
@@ -0,0 +1,27 @@
+<div ng-controller="ButtonsCtrl">
+    <h4>Single toggle</h4>
+    <pre>{{singleModel}}</pre>
+    <button type="button" class="btn btn-primary" ng-model="singleModel" uib-btn-checkbox btn-checkbox-true="1" btn-checkbox-false="0">
+        Single Toggle
+    </button>
+    <h4>Checkbox</h4>
+    <pre>Model: {{checkModel}}</pre>
+    <pre>Results: {{checkResults}}</pre>
+    <div class="btn-group">
+        <label class="btn btn-primary" ng-model="checkModel.left" uib-btn-checkbox>Left</label>
+        <label class="btn btn-primary" ng-model="checkModel.middle" uib-btn-checkbox>Middle</label>
+        <label class="btn btn-primary" ng-model="checkModel.right" uib-btn-checkbox>Right</label>
+    </div>
+    <h4>Radio &amp; Uncheckable Radio</h4>
+    <pre>{{radioModel || 'null'}}</pre>
+    <div class="btn-group">
+        <label class="btn btn-primary" ng-model="radioModel" uib-btn-radio="'Left'">Left</label>
+        <label class="btn btn-primary" ng-model="radioModel" uib-btn-radio="'Middle'">Middle</label>
+        <label class="btn btn-primary" ng-model="radioModel" uib-btn-radio="'Right'">Right</label>
+    </div>
+    <div class="btn-group">
+        <label class="btn btn-success" ng-model="radioModel" uib-btn-radio="'Left'" uncheckable>Left</label>
+        <label class="btn btn-success" ng-model="radioModel" uib-btn-radio="'Middle'" uncheckable>Middle</label>
+        <label class="btn btn-success" ng-model="radioModel" uib-btn-radio="'Right'" uncheckable>Right</label>
+    </div>
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/buttons/docs/demo.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/buttons/docs/demo.js
new file mode 100644
index 0000000..3d5760f
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/buttons/docs/demo.js
@@ -0,0 +1,22 @@
+angular.module('ui.bootstrap.demo').controller('ButtonsCtrl', function ($scope) {
+  $scope.singleModel = 1;
+
+  $scope.radioModel = 'Middle';
+
+  $scope.checkModel = {
+    left: false,
+    middle: true,
+    right: false
+  };
+
+  $scope.checkResults = [];
+
+  $scope.$watchCollection('checkModel', function () {
+    $scope.checkResults = [];
+    angular.forEach($scope.checkModel, function (value, key) {
+      if (value) {
+        $scope.checkResults.push(key);
+      }
+    });
+  });
+});
\ No newline at end of file
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/buttons/docs/readme.md b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/buttons/docs/readme.md
new file mode 100644
index 0000000..9367d50
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/buttons/docs/readme.md
@@ -0,0 +1,36 @@
+With the buttons directive, we can make a group of buttons behave like a set of checkboxes (`uib-btn-checkbox`) or behave like a set of radio buttons (`uib-btn-radio`).
+
+### uib-btn-checkbox settings
+
+* `ng-model` -
+  Model where we set the checkbox status. By default `true` or `false`.
+  
+* `btn-checkbox-true`
+  _(Default: `true`)_ -
+  Sets the value for the checked status.
+
+* `btn-checkbox-false`
+  _(Default: `false`)_ -
+  Sets the value for the unchecked status.
+
+### uib-btn-radio settings
+
+* `uib-btn-radio` -
+  Value to assign to the `ng-model` if we check this radio button.
+
+* `ng-model` -
+  Model where we set the radio status. All radio buttons in a group should use the same `ng-model`.
+  
+* `uncheckable`
+  _(Boolean attribute)_ -
+  Whether a radio button can be unchecked or not.
+  
+### Default settings `uibButtonConfig`
+
+* `activeClass`
+  _(Default: `active`)_ -
+  Class to apply to the checked buttons.
+  
+* `toggleEvent`
+  _(Default: `click`)_ -
+  Event used to toggle the buttons.
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/buttons/test/buttons.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/buttons/test/buttons.spec.js
new file mode 100644
index 0000000..5cc81c5
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/buttons/test/buttons.spec.js
@@ -0,0 +1,371 @@
+describe('buttons', function() {
+
+  var $scope, $compile;
+
+  beforeEach(module('ui.bootstrap.buttons'));
+  beforeEach(inject(function(_$rootScope_, _$compile_) {
+    $scope = _$rootScope_;
+    $compile = _$compile_;
+  }));
+
+  describe('checkbox', function() {
+    var compileButton = function(markup, scope) {
+      var el = $compile(markup)(scope);
+      scope.$digest();
+      return el;
+    };
+
+    it('should expose the controller to the view', inject(function($templateCache) {
+      var btn = compileButton('<button ng-model="model" uib-btn-checkbox>{{button.text}}</button>', $scope);
+      var ctrl = btn.controller('uibBtnCheckbox');
+      expect(ctrl).toBeDefined();
+
+      ctrl.text = 'foo';
+      $scope.$digest();
+
+      expect(btn.html()).toBe('foo');
+    }));
+
+    //model -> UI
+    it('should work correctly with default model values', function() {
+      $scope.model = false;
+      var btn = compileButton('<button ng-model="model" uib-btn-checkbox>click</button>', $scope);
+      expect(btn).not.toHaveClass('active');
+
+      $scope.model = true;
+      $scope.$digest();
+      expect(btn).toHaveClass('active');
+    });
+
+    it('should bind custom model values', function() {
+      $scope.model = 1;
+      var btn = compileButton('<button ng-model="model" uib-btn-checkbox btn-checkbox-true="1" btn-checkbox-false="0">click</button>', $scope);
+      expect(btn).toHaveClass('active');
+
+      $scope.model = 0;
+      $scope.$digest();
+      expect(btn).not.toHaveClass('active');
+    });
+
+    //UI-> model
+    it('should toggle default model values on click', function() {
+      $scope.model = false;
+      var btn = compileButton('<button ng-model="model" uib-btn-checkbox>click</button>', $scope);
+
+      btn.click();
+      expect($scope.model).toEqual(true);
+      expect(btn).toHaveClass('active');
+
+      btn.click();
+      expect($scope.model).toEqual(false);
+      expect(btn).not.toHaveClass('active');
+    });
+
+    it('should toggle custom model values on click', function() {
+      $scope.model = 0;
+      var btn = compileButton('<button ng-model="model" uib-btn-checkbox btn-checkbox-true="1" btn-checkbox-false="0">click</button>', $scope);
+
+      btn.click();
+      expect($scope.model).toEqual(1);
+      expect(btn).toHaveClass('active');
+
+      btn.click();
+      expect($scope.model).toEqual(0);
+      expect(btn).not.toHaveClass('active');
+    });
+
+    it('should monitor true / false value changes - issue 666', function() {
+
+      $scope.model = 1;
+      $scope.trueVal = 1;
+      var btn = compileButton('<button ng-model="model" uib-btn-checkbox btn-checkbox-true="trueVal">click</button>', $scope);
+
+      expect(btn).toHaveClass('active');
+      expect($scope.model).toEqual(1);
+
+      $scope.model = 2;
+      $scope.trueVal = 2;
+      $scope.$digest();
+
+      expect(btn).toHaveClass('active');
+      expect($scope.model).toEqual(2);
+    });
+
+    it('should not toggle when disabled - issue 4013', function() {
+      $scope.model = 1;
+      $scope.falseVal = 0;
+      var btn = compileButton('<button disabled ng-model="model" uib-btn-checkbox btn-checkbox-true="falseVal">click</button>', $scope);
+
+      expect(btn).not.toHaveClass('active');
+      expect($scope.model).toEqual(1);
+
+      btn.click();
+
+      expect(btn).not.toHaveClass('active');
+
+      $scope.$digest();
+
+      expect(btn).not.toHaveClass('active');
+    });
+
+    describe('setting buttonConfig', function() {
+      var uibButtonConfig, originalActiveClass, originalToggleEvent;
+
+      beforeEach(inject(function(_uibButtonConfig_) {
+        uibButtonConfig = _uibButtonConfig_;
+        originalActiveClass = uibButtonConfig.activeClass;
+        originalToggleEvent = uibButtonConfig.toggleEvent;
+        uibButtonConfig.activeClass = false;
+        uibButtonConfig.toggleEvent = false;
+      }));
+
+      afterEach(function() {
+        // return it to the original value
+        uibButtonConfig.activeClass = originalActiveClass;
+        uibButtonConfig.toggleEvent = originalToggleEvent;
+      });
+
+      it('should use default config when buttonConfig.activeClass and buttonConfig.toggleEvent is false', function() {
+        $scope.model = false;
+        var btn = compileButton('<button ng-model="model" uib-btn-checkbox>click</button>', $scope);
+        expect(btn).not.toHaveClass('active');
+
+        $scope.model = true;
+        $scope.$digest();
+        expect(btn).toHaveClass('active');
+      });
+
+      it('should be able to use a different active class', function() {
+        uibButtonConfig.activeClass = 'foo';
+        $scope.model = false;
+        var btn = compileButton('<button ng-model="model" uib-btn-checkbox>click</button>', $scope);
+        expect(btn).not.toHaveClass('foo');
+
+        $scope.model = true;
+        $scope.$digest();
+        expect(btn).toHaveClass('foo');
+      });
+
+      it('should be able to use a different toggle event', function() {
+        uibButtonConfig.toggleEvent = 'mouseenter';
+        $scope.model = false;
+        var btn = compileButton('<button ng-model="model" uib-btn-checkbox>click</button>', $scope);
+        expect(btn).not.toHaveClass('active');
+
+        btn.trigger('mouseenter');
+
+        $scope.$digest();
+        expect(btn).toHaveClass('active');
+      });
+    });
+
+  });
+
+  describe('radio', function() {
+
+    var compileButtons = function(markup, scope) {
+      var el = $compile('<div>'+markup+'</div>')(scope);
+      scope.$digest();
+      return el.find('button');
+    };
+
+    it('should expose the controller to the view', inject(function($templateCache) {
+      var btn = compileButtons('<button ng-model="model" uib-btn-radio="1">{{buttons.text}}</button>', $scope);
+      var ctrl = btn.controller('uibBtnRadio');
+      expect(ctrl).toBeDefined();
+
+      ctrl.text = 'foo';
+      $scope.$digest();
+
+      expect(btn.html()).toBe('foo');
+    }));
+
+    //model -> UI
+    it('should set active class based on model', function() {
+      var btns = compileButtons('<button ng-model="model" uib-btn-radio="1">click1</button><button ng-model="model" uib-btn-radio="2">click2</button>', $scope);
+      expect(btns.eq(0)).not.toHaveClass('active');
+      expect(btns.eq(1)).not.toHaveClass('active');
+
+      $scope.model = 2;
+      $scope.$digest();
+      expect(btns.eq(0)).not.toHaveClass('active');
+      expect(btns.eq(1)).toHaveClass('active');
+    });
+
+    //UI->model
+    it('should set active class via click', function() {
+      var btns = compileButtons('<button ng-model="model" uib-btn-radio="1">click1</button><button ng-model="model" uib-btn-radio="2">click2</button>', $scope);
+      expect($scope.model).toBeUndefined();
+
+      btns.eq(0).click();
+      expect($scope.model).toEqual(1);
+      expect(btns.eq(0)).toHaveClass('active');
+      expect(btns.eq(1)).not.toHaveClass('active');
+
+      btns.eq(1).click();
+      expect($scope.model).toEqual(2);
+      expect(btns.eq(1)).toHaveClass('active');
+      expect(btns.eq(0)).not.toHaveClass('active');
+    });
+
+    it('should watch uib-btn-radio values and update state accordingly', function() {
+      $scope.values = ['value1', 'value2'];
+
+      var btns = compileButtons('<button ng-model="model" uib-btn-radio="values[0]">click1</button><button ng-model="model" uib-btn-radio="values[1]">click2</button>', $scope);
+      expect(btns.eq(0)).not.toHaveClass('active');
+      expect(btns.eq(1)).not.toHaveClass('active');
+
+      $scope.model = 'value2';
+      $scope.$digest();
+      expect(btns.eq(0)).not.toHaveClass('active');
+      expect(btns.eq(1)).toHaveClass('active');
+
+      $scope.values[1] = 'value3';
+      $scope.model = 'value3';
+      $scope.$digest();
+      expect(btns.eq(0)).not.toHaveClass('active');
+      expect(btns.eq(1)).toHaveClass('active');
+    });
+
+    it('should do nothing when clicking an active radio', function() {
+      $scope.model = 1;
+      var btns = compileButtons('<button ng-model="model" uib-btn-radio="1">click1</button><button ng-model="model" uib-btn-radio="2">click2</button>', $scope);
+      expect(btns.eq(0)).toHaveClass('active');
+      expect(btns.eq(1)).not.toHaveClass('active');
+
+      btns.eq(0).click();
+      $scope.$digest();
+      expect(btns.eq(0)).toHaveClass('active');
+      expect(btns.eq(1)).not.toHaveClass('active');
+    });
+
+    it('should not toggle when disabled - issue 4013', function() {
+      $scope.model = 1;
+      var btns = compileButtons('<button ng-model="model" uib-btn-radio="1">click1</button><button disabled ng-model="model" uib-btn-radio="2">click2</button>', $scope);
+
+      expect(btns.eq(0)).toHaveClass('active');
+      expect(btns.eq(1)).not.toHaveClass('active');
+
+      btns.eq(1).click();
+
+      expect(btns.eq(0)).toHaveClass('active');
+      expect(btns.eq(1)).not.toHaveClass('active');
+
+      $scope.$digest();
+
+      expect(btns.eq(0)).toHaveClass('active');
+      expect(btns.eq(1)).not.toHaveClass('active');
+    });
+
+    it('should handle string values in uib-btn-radio value', function() {
+      $scope.model = 'Two';
+      var btns = compileButtons('<button ng-model="model" uib-btn-radio="\'One\'">click1</button><button ng-model="model" uib-btn-radio="\'Two\'">click2</button>', $scope);
+
+      expect(btns.eq(0)).not.toHaveClass('active');
+      expect(btns.eq(1)).toHaveClass('active');
+
+      btns.eq(0).click();
+      expect(btns.eq(0)).toHaveClass('active');
+      expect(btns.eq(1)).not.toHaveClass('active');
+      expect($scope.model).toEqual('One');
+
+      $scope.$digest();
+
+      expect(btns.eq(0)).toHaveClass('active');
+      expect(btns.eq(1)).not.toHaveClass('active');
+      expect($scope.model).toEqual('One');
+    });
+
+    describe('uncheckable', function() {
+      //model -> UI
+      it('should set active class based on model', function() {
+        var btns = compileButtons('<button ng-model="model" uib-btn-radio="1" uncheckable>click1</button><button ng-model="model" uib-btn-radio="2" uncheckable>click2</button>', $scope);
+        expect(btns.eq(0)).not.toHaveClass('active');
+        expect(btns.eq(1)).not.toHaveClass('active');
+
+        $scope.model = 2;
+        $scope.$digest();
+        expect(btns.eq(0)).not.toHaveClass('active');
+        expect(btns.eq(1)).toHaveClass('active');
+      });
+
+      //UI->model
+      it('should unset active class via click', function() {
+        var btns = compileButtons('<button ng-model="model" uib-btn-radio="1" uncheckable>click1</button><button ng-model="model" uib-btn-radio="2" uncheckable>click2</button>', $scope);
+        expect($scope.model).toBeUndefined();
+
+        btns.eq(0).click();
+        expect($scope.model).toEqual(1);
+        expect(btns.eq(0)).toHaveClass('active');
+        expect(btns.eq(1)).not.toHaveClass('active');
+
+        btns.eq(0).click();
+        expect($scope.model).toBeNull();
+        expect(btns.eq(1)).not.toHaveClass('active');
+        expect(btns.eq(0)).not.toHaveClass('active');
+      });
+
+      it('should watch uib-btn-radio values and update state', function() {
+        $scope.values = ['value1', 'value2'];
+
+        var btns = compileButtons('<button ng-model="model" uib-btn-radio="values[0]" uncheckable>click1</button><button ng-model="model" uib-btn-radio="values[1]" uncheckable>click2</button>', $scope);
+        expect(btns.eq(0)).not.toHaveClass('active');
+        expect(btns.eq(1)).not.toHaveClass('active');
+
+        $scope.model = 'value2';
+        $scope.$digest();
+        expect(btns.eq(0)).not.toHaveClass('active');
+        expect(btns.eq(1)).toHaveClass('active');
+
+        $scope.model = undefined;
+        $scope.$digest();
+        expect(btns.eq(0)).not.toHaveClass('active');
+        expect(btns.eq(1)).not.toHaveClass('active');
+      });
+    });
+  });
+});
+
+/* Deprecation tests below */
+
+describe('buttons deprecation', function() {
+  beforeEach(module('ui.bootstrap.buttons'));
+
+  it('should suppress warning', function() {
+    module(function($provide) {
+      $provide.value('$buttonsSuppressWarning', true);
+    });
+
+    inject(function($compile, $log, $rootScope) {
+      spyOn($log, 'warn');
+
+      var element = $compile('<button ng-model="model" btn-checkbox>click</button>')($rootScope);
+      $rootScope.$digest();
+
+      expect($log.warn.calls.count()).toBe(0);
+
+      element = $compile('<button ng-model="model" btn-radio="1">click1</button><button ng-model="model" btn-radio="2">click2</button>')($rootScope);
+      $rootScope.$digest();
+
+      expect($log.warn.calls.count()).toBe(0);
+    });
+  });
+
+  it('should give warning by default', inject(function($compile, $log, $rootScope) {
+    spyOn($log, 'warn');
+
+    var element = $compile('<button ng-model="model" btn-checkbox>click</button>')($rootScope);
+    $rootScope.$digest();
+
+    element = $compile('<button ng-model="model" btn-radio="1">click1</button><button ng-model="model" btn-radio="2">click2</button>')($rootScope);
+    $rootScope.$digest();
+
+    expect($log.warn.calls.count()).toBe(6);
+    expect($log.warn.calls.argsFor(0)).toEqual(['ButtonsController is now deprecated. Use UibButtonsController instead.']);
+    expect($log.warn.calls.argsFor(1)).toEqual(['btn-checkbox is now deprecated. Use uib-btn-checkbox instead.']);
+    expect($log.warn.calls.argsFor(2)).toEqual(['ButtonsController is now deprecated. Use UibButtonsController instead.']);
+    expect($log.warn.calls.argsFor(3)).toEqual(['btn-radio is now deprecated. Use uib-btn-radio instead.']);
+    expect($log.warn.calls.argsFor(4)).toEqual(['ButtonsController is now deprecated. Use UibButtonsController instead.']);
+    expect($log.warn.calls.argsFor(5)).toEqual(['btn-radio is now deprecated. Use uib-btn-radio instead.']);
+  }));
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/carousel/carousel.css b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/carousel/carousel.css
new file mode 100644
index 0000000..061b304
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/carousel/carousel.css
@@ -0,0 +1,4 @@
+.ng-animate.item:not(.left):not(.right) {
+  -webkit-transition: 0s ease-in-out left;
+  transition: 0s ease-in-out left
+}
\ No newline at end of file
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/carousel/carousel.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/carousel/carousel.js
new file mode 100644
index 0000000..a3cb2aa
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/carousel/carousel.js
@@ -0,0 +1,496 @@
+/**
+ * @ngdoc overview
+ * @name ui.bootstrap.carousel
+ *
+ * @description
+ * AngularJS version of an image carousel.
+ *
+ */
+angular.module('ui.bootstrap.carousel', [])
+
+.controller('UibCarouselController', ['$scope', '$element', '$interval', '$animate', function($scope, $element, $interval, $animate) {
+  var self = this,
+    slides = self.slides = $scope.slides = [],
+    NEW_ANIMATE = angular.version.minor >= 4,
+    NO_TRANSITION = 'uib-noTransition',
+    SLIDE_DIRECTION = 'uib-slideDirection',
+    currentIndex = -1,
+    currentInterval, isPlaying;
+  self.currentSlide = null;
+
+  var destroyed = false;
+  /* direction: "prev" or "next" */
+  self.select = $scope.select = function(nextSlide, direction) {
+    var nextIndex = $scope.indexOfSlide(nextSlide);
+    //Decide direction if it's not given
+    if (direction === undefined) {
+      direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
+    }
+    //Prevent this user-triggered transition from occurring if there is already one in progress
+    if (nextSlide && nextSlide !== self.currentSlide && !$scope.$currentTransition) {
+      goNext(nextSlide, nextIndex, direction);
+    }
+  };
+
+  function goNext(slide, index, direction) {
+    // Scope has been destroyed, stop here.
+    if (destroyed) { return; }
+
+    angular.extend(slide, {direction: direction, active: true});
+    angular.extend(self.currentSlide || {}, {direction: direction, active: false});
+    if ($animate.enabled() && !$scope.noTransition && !$scope.$currentTransition &&
+      slide.$element && self.slides.length > 1) {
+      slide.$element.data(SLIDE_DIRECTION, slide.direction);
+      if (self.currentSlide && self.currentSlide.$element) {
+        self.currentSlide.$element.data(SLIDE_DIRECTION, slide.direction);
+      }
+
+      $scope.$currentTransition = true;
+      if (NEW_ANIMATE) {
+        $animate.on('addClass', slide.$element, function(element, phase) {
+          if (phase === 'close') {
+            $scope.$currentTransition = null;
+            $animate.off('addClass', element);
+          }
+        });
+      } else {
+        slide.$element.one('$animate:close', function closeFn() {
+          $scope.$currentTransition = null;
+        });
+      }
+    }
+
+    self.currentSlide = slide;
+    currentIndex = index;
+
+    //every time you change slides, reset the timer
+    restartTimer();
+  }
+
+  $scope.$on('$destroy', function() {
+    destroyed = true;
+  });
+
+  function getSlideByIndex(index) {
+    if (angular.isUndefined(slides[index].index)) {
+      return slides[index];
+    }
+    var i, len = slides.length;
+    for (i = 0; i < slides.length; ++i) {
+      if (slides[i].index == index) {
+        return slides[i];
+      }
+    }
+  }
+
+  self.getCurrentIndex = function() {
+    if (self.currentSlide && angular.isDefined(self.currentSlide.index)) {
+      return +self.currentSlide.index;
+    }
+    return currentIndex;
+  };
+
+  /* Allow outside people to call indexOf on slides array */
+  $scope.indexOfSlide = function(slide) {
+    return angular.isDefined(slide.index) ? +slide.index : slides.indexOf(slide);
+  };
+
+  $scope.next = function() {
+    var newIndex = (self.getCurrentIndex() + 1) % slides.length;
+
+    if (newIndex === 0 && $scope.noWrap()) {
+      $scope.pause();
+      return;
+    }
+
+    return self.select(getSlideByIndex(newIndex), 'next');
+  };
+
+  $scope.prev = function() {
+    var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1;
+
+    if ($scope.noWrap() && newIndex === slides.length - 1) {
+      $scope.pause();
+      return;
+    }
+
+    return self.select(getSlideByIndex(newIndex), 'prev');
+  };
+
+  $scope.isActive = function(slide) {
+     return self.currentSlide === slide;
+  };
+
+  $scope.$watch('interval', restartTimer);
+  $scope.$watchCollection('slides', resetTransition);
+  $scope.$on('$destroy', resetTimer);
+
+  function restartTimer() {
+    resetTimer();
+    var interval = +$scope.interval;
+    if (!isNaN(interval) && interval > 0) {
+      currentInterval = $interval(timerFn, interval);
+    }
+  }
+
+  function resetTimer() {
+    if (currentInterval) {
+      $interval.cancel(currentInterval);
+      currentInterval = null;
+    }
+  }
+
+  function timerFn() {
+    var interval = +$scope.interval;
+    if (isPlaying && !isNaN(interval) && interval > 0 && slides.length) {
+      $scope.next();
+    } else {
+      $scope.pause();
+    }
+  }
+
+  function resetTransition(slides) {
+    if (!slides.length) {
+      $scope.$currentTransition = null;
+    }
+  }
+
+  $scope.play = function() {
+    if (!isPlaying) {
+      isPlaying = true;
+      restartTimer();
+    }
+  };
+  $scope.pause = function() {
+    if (!$scope.noPause) {
+      isPlaying = false;
+      resetTimer();
+    }
+  };
+
+  self.addSlide = function(slide, element) {
+    slide.$element = element;
+    slides.push(slide);
+    //if this is the first slide or the slide is set to active, select it
+    if (slides.length === 1 || slide.active) {
+      self.select(slides[slides.length - 1]);
+      if (slides.length === 1) {
+        $scope.play();
+      }
+    } else {
+      slide.active = false;
+    }
+  };
+
+  self.removeSlide = function(slide) {
+    if (angular.isDefined(slide.index)) {
+      slides.sort(function(a, b) {
+        return +a.index > +b.index;
+      });
+    }
+    //get the index of the slide inside the carousel
+    var index = slides.indexOf(slide);
+    slides.splice(index, 1);
+    if (slides.length > 0 && slide.active) {
+      if (index >= slides.length) {
+        self.select(slides[index - 1]);
+      } else {
+        self.select(slides[index]);
+      }
+    } else if (currentIndex > index) {
+      currentIndex--;
+    }
+
+    //clean the currentSlide when no more slide
+    if (slides.length === 0) {
+      self.currentSlide = null;
+    }
+  };
+
+  $scope.$watch('noTransition', function(noTransition) {
+    $element.data(NO_TRANSITION, noTransition);
+  });
+
+}])
+
+/**
+ * @ngdoc directive
+ * @name ui.bootstrap.carousel.directive:carousel
+ * @restrict EA
+ *
+ * @description
+ * Carousel is the outer container for a set of image 'slides' to showcase.
+ *
+ * @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide.
+ * @param {boolean=} noTransition Whether to disable transitions on the carousel.
+ * @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover).
+ *
+ * @example
+<example module="ui.bootstrap">
+  <file name="index.html">
+    <uib-carousel>
+      <uib-slide>
+        <img src="http://placekitten.com/150/150" style="margin:auto;">
+        <div class="carousel-caption">
+          <p>Beautiful!</p>
+        </div>
+      </uib-slide>
+      <uib-slide>
+        <img src="http://placekitten.com/100/150" style="margin:auto;">
+        <div class="carousel-caption">
+          <p>D'aww!</p>
+        </div>
+      </uib-slide>
+    </uib-carousel>
+  </file>
+  <file name="demo.css">
+    .carousel-indicators {
+      top: auto;
+      bottom: 15px;
+    }
+  </file>
+</example>
+ */
+.directive('uibCarousel', [function() {
+  return {
+    transclude: true,
+    replace: true,
+    controller: 'UibCarouselController',
+    controllerAs: 'carousel',
+    require: 'carousel',
+    templateUrl: function(element, attrs) {
+      return attrs.templateUrl || 'template/carousel/carousel.html';
+    },
+    scope: {
+      interval: '=',
+      noTransition: '=',
+      noPause: '=',
+      noWrap: '&'
+    }
+  };
+}])
+
+/**
+ * @ngdoc directive
+ * @name ui.bootstrap.carousel.directive:slide
+ * @restrict EA
+ *
+ * @description
+ * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}.  Must be placed as a child of a carousel element.
+ *
+ * @param {boolean=} active Model binding, whether or not this slide is currently active.
+ * @param {number=} index The index of the slide. The slides will be sorted by this parameter.
+ *
+ * @example
+<example module="ui.bootstrap">
+  <file name="index.html">
+<div ng-controller="CarouselDemoCtrl">
+  <uib-carousel>
+    <uib-slide ng-repeat="slide in slides" active="slide.active" index="$index">
+      <img ng-src="{{slide.image}}" style="margin:auto;">
+      <div class="carousel-caption">
+        <h4>Slide {{$index}}</h4>
+        <p>{{slide.text}}</p>
+      </div>
+    </uib-slide>
+  </uib-carousel>
+  Interval, in milliseconds: <input type="number" ng-model="myInterval">
+  <br />Enter a negative number to stop the interval.
+</div>
+  </file>
+  <file name="script.js">
+function CarouselDemoCtrl($scope) {
+  $scope.myInterval = 5000;
+}
+  </file>
+  <file name="demo.css">
+    .carousel-indicators {
+      top: auto;
+      bottom: 15px;
+    }
+  </file>
+</example>
+*/
+
+.directive('uibSlide', function() {
+  return {
+    require: '^uibCarousel',
+    restrict: 'EA',
+    transclude: true,
+    replace: true,
+    templateUrl: function(element, attrs) {
+      return attrs.templateUrl || 'template/carousel/slide.html';
+    },
+    scope: {
+      active: '=?',
+      actual: '=?',
+      index: '=?'
+    },
+    link: function (scope, element, attrs, carouselCtrl) {
+      carouselCtrl.addSlide(scope, element);
+      //when the scope is destroyed then remove the slide from the current slides array
+      scope.$on('$destroy', function() {
+        carouselCtrl.removeSlide(scope);
+      });
+
+      scope.$watch('active', function(active) {
+        if (active) {
+          carouselCtrl.select(scope);
+        }
+      });
+    }
+  };
+})
+
+.animation('.item', [
+         '$injector', '$animate',
+function ($injector, $animate) {
+  var NO_TRANSITION = 'uib-noTransition',
+    SLIDE_DIRECTION = 'uib-slideDirection',
+    $animateCss = null;
+
+  if ($injector.has('$animateCss')) {
+    $animateCss = $injector.get('$animateCss');
+  }
+
+  function removeClass(element, className, callback) {
+    element.removeClass(className);
+    if (callback) {
+      callback();
+    }
+  }
+
+  return {
+    beforeAddClass: function(element, className, done) {
+      // Due to transclusion, noTransition property is on parent's scope
+      if (className == 'active' && element.parent() && element.parent().parent() &&
+          !element.parent().parent().data(NO_TRANSITION)) {
+        var stopped = false;
+        var direction = element.data(SLIDE_DIRECTION);
+        var directionClass = direction == 'next' ? 'left' : 'right';
+        var removeClassFn = removeClass.bind(this, element,
+          directionClass + ' ' + direction, done);
+        element.addClass(direction);
+
+        if ($animateCss) {
+          $animateCss(element, {addClass: directionClass})
+            .start()
+            .done(removeClassFn);
+        } else {
+          $animate.addClass(element, directionClass).then(function () {
+            if (!stopped) {
+              removeClassFn();
+            }
+            done();
+          });
+        }
+
+        return function () {
+          stopped = true;
+        };
+      }
+      done();
+    },
+    beforeRemoveClass: function (element, className, done) {
+      // Due to transclusion, noTransition property is on parent's scope
+      if (className === 'active' && element.parent() && element.parent().parent() &&
+          !element.parent().parent().data(NO_TRANSITION)) {
+        var stopped = false;
+        var direction = element.data(SLIDE_DIRECTION);
+        var directionClass = direction == 'next' ? 'left' : 'right';
+        var removeClassFn = removeClass.bind(this, element, directionClass, done);
+
+        if ($animateCss) {
+          $animateCss(element, {addClass: directionClass})
+            .start()
+            .done(removeClassFn);
+        } else {
+          $animate.addClass(element, directionClass).then(function() {
+            if (!stopped) {
+              removeClassFn();
+            }
+            done();
+          });
+        }
+        return function() {
+          stopped = true;
+        };
+      }
+      done();
+    }
+  };
+}]);
+
+/* deprecated carousel below */
+
+angular.module('ui.bootstrap.carousel')
+
+.value('$carouselSuppressWarning', false)
+
+.controller('CarouselController', ['$scope', '$element', '$controller', '$log', '$carouselSuppressWarning', function($scope, $element, $controller, $log, $carouselSuppressWarning) {
+  if (!$carouselSuppressWarning) {
+    $log.warn('CarouselController is now deprecated. Use UibCarouselController instead.');
+  }
+
+  angular.extend(this, $controller('UibCarouselController', {
+    $scope: $scope,
+    $element: $element
+  }));
+}])
+
+.directive('carousel', ['$log', '$carouselSuppressWarning', function($log, $carouselSuppressWarning) {
+  return {
+    transclude: true,
+    replace: true,
+    controller: 'CarouselController',
+    controllerAs: 'carousel',
+    require: 'carousel',
+    templateUrl: function(element, attrs) {
+      return attrs.templateUrl || 'template/carousel/carousel.html';
+    },
+    scope: {
+      interval: '=',
+      noTransition: '=',
+      noPause: '=',
+      noWrap: '&'
+    },
+    link: function() {
+      if (!$carouselSuppressWarning) {
+        $log.warn('carousel is now deprecated. Use uib-carousel instead.');
+      }
+    }
+  };
+}])
+
+.directive('slide', ['$log', '$carouselSuppressWarning', function($log, $carouselSuppressWarning) {
+  return {
+    require: '^carousel',
+    transclude: true,
+    replace: true,
+    templateUrl: function(element, attrs) {
+      return attrs.templateUrl || 'template/carousel/slide.html';
+    },
+    scope: {
+      active: '=?',
+      actual: '=?',
+      index: '=?'
+    },
+    link: function (scope, element, attrs, carouselCtrl) {
+      if (!$carouselSuppressWarning) {
+        $log.warn('slide is now deprecated. Use uib-slide instead.');
+      }
+
+      carouselCtrl.addSlide(scope, element);
+      //when the scope is destroyed then remove the slide from the current slides array
+      scope.$on('$destroy', function() {
+        carouselCtrl.removeSlide(scope);
+      });
+
+      scope.$watch('active', function(active) {
+        if (active) {
+          carouselCtrl.select(scope);
+        }
+      });
+    }
+  };
+}]);
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/carousel/docs/README.md b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/carousel/docs/README.md
new file mode 100644
index 0000000..0fe1eba
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/carousel/docs/README.md
@@ -0,0 +1,11 @@
+Carousel creates a carousel similar to bootstrap's image carousel.
+
+The carousel also offers support for touchscreen devices in the form of swiping. To enable swiping, load the `ngTouch` module as a dependency.
+
+Use a `<uib-carousel>` element with `<uib-slide>` elements inside it.  It will automatically cycle through the slides at a given rate, and a current-index variable will be kept in sync with the currently visible slide.
+
+Use the `no-wrap` attribute on a `<uib-carousel>` element to control the looping of slides; setting `no-wrap` to an expression which evaluates to a truthy value will prevent looping.
+
+Use the `template-url` attribute on a `<uib-carousel>` or `<uib-slide>` element to specify the url of a custom template to override the default templates.
+
+Use the `actual` attribute on a `<uib-slide>` element to bind the slide model (or any object of interest) onto the slide directive's `$scope`, which makes it available for customization in the carousel template.
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/carousel/docs/demo.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/carousel/docs/demo.html
new file mode 100644
index 0000000..0383722
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/carousel/docs/demo.html
@@ -0,0 +1,28 @@
+<div ng-controller="CarouselDemoCtrl">
+  <div style="height: 305px">
+    <uib-carousel interval="myInterval" no-wrap="noWrapSlides">
+      <uib-slide ng-repeat="slide in slides" active="slide.active">
+        <img ng-src="{{slide.image}}" style="margin:auto;">
+        <div class="carousel-caption">
+          <h4>Slide {{$index}}</h4>
+          <p>{{slide.text}}</p>
+        </div>
+      </uib-slide>
+    </uib-carousel>
+  </div>
+  <div class="row">
+    <div class="col-md-6">
+      <button type="button" class="btn btn-info" ng-click="addSlide()">Add Slide</button>
+      <div class="checkbox">
+        <label>
+          <input type="checkbox" ng-model="noWrapSlides">
+          Disable Slide Looping
+        </label>
+      </div>
+    </div>
+    <div class="col-md-6">
+      Interval, in milliseconds: <input type="number" class="form-control" ng-model="myInterval">
+      <br />Enter a negative number or 0 to stop the interval.
+    </div>
+  </div>
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/carousel/docs/demo.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/carousel/docs/demo.js
new file mode 100644
index 0000000..9b6607c
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/carousel/docs/demo.js
@@ -0,0 +1,16 @@
+angular.module('ui.bootstrap.demo').controller('CarouselDemoCtrl', function ($scope) {
+  $scope.myInterval = 5000;
+  $scope.noWrapSlides = false;
+  var slides = $scope.slides = [];
+  $scope.addSlide = function() {
+    var newWidth = 600 + slides.length + 1;
+    slides.push({
+      image: '//placekitten.com/' + newWidth + '/300',
+      text: ['More','Extra','Lots of','Surplus'][slides.length % 4] + ' ' +
+        ['Cats', 'Kittys', 'Felines', 'Cutes'][slides.length % 4]
+    });
+  };
+  for (var i=0; i<4; i++) {
+    $scope.addSlide();
+  }
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/carousel/test/carousel.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/carousel/test/carousel.spec.js
new file mode 100644
index 0000000..adb9c76
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/carousel/test/carousel.spec.js
@@ -0,0 +1,594 @@
+describe('carousel', function() {
+  beforeEach(module('ui.bootstrap.carousel', function($compileProvider, $provide) {
+    angular.forEach(['ngSwipeLeft', 'ngSwipeRight'], makeMock);
+    function makeMock(name) {
+      $provide.value(name + 'Directive', []); //remove existing directive if it exists
+      $compileProvider.directive(name, function() {
+        return function(scope, element, attr) {
+          element.on(name, function() {
+            scope.$apply(attr[name]);
+          });
+        };
+      });
+    }
+  }));
+  beforeEach(module('template/carousel/carousel.html', 'template/carousel/slide.html'));
+
+  var $rootScope, $compile, $controller, $interval, $templateCache;
+  beforeEach(inject(function(_$rootScope_, _$compile_, _$controller_, _$interval_, _$templateCache_) {
+    $rootScope = _$rootScope_;
+    $compile = _$compile_;
+    $controller = _$controller_;
+    $interval = _$interval_;
+    $templateCache = _$templateCache_;
+  }));
+
+  describe('basics', function() {
+    var elm, scope;
+    beforeEach(function() {
+      scope = $rootScope.$new();
+      scope.slides = [
+        {active:false,content:'one'},
+        {active:false,content:'two'},
+        {active:false,content:'three'}
+      ];
+      elm = $compile(
+        '<uib-carousel interval="interval" no-transition="true" no-pause="nopause">' +
+          '<uib-slide ng-repeat="slide in slides" active="slide.active">' +
+            '{{slide.content}}' +
+          '</uib-slide>' +
+        '</uib-carousel>'
+      )(scope);
+      scope.interval = 5000;
+      scope.nopause = undefined;
+      scope.$apply();
+    });
+
+    function testSlideActive(slideIndex) {
+      for (var i = 0; i < scope.slides.length; i++) {
+        if (i == slideIndex) {
+          expect(scope.slides[i].active).toBe(true);
+        } else {
+          expect(scope.slides[i].active).not.toBe(true);
+        }
+      }
+    }
+
+    it('should allow overriding of the carousel template', function() {
+      $templateCache.put('foo/bar.html', '<div>foo</div>');
+
+      elm = $compile('<uib-carousel template-url="foo/bar.html"></uib-carousel>')(scope);
+      $rootScope.$digest();
+
+      expect(elm.html()).toBe('foo');
+    });
+
+    it('should allow overriding of the slide template', function() {
+      $templateCache.put('foo/bar.html', '<div class="slide">bar</div>');
+
+      elm = $compile(
+        '<uib-carousel interval="interval" no-transition="true" no-pause="nopause">' +
+          '<uib-slide template-url="foo/bar.html"></uib-slide>' + 
+        '</uib-carousel>'
+      )(scope);
+      $rootScope.$digest();
+
+      var slide = elm.find('.slide');
+      expect(slide.html()).toBe('bar');
+    });
+
+    it('should set the selected slide to active = true', function() {
+      expect(scope.slides[0].content).toBe('one');
+      testSlideActive(0);
+      scope.$apply('slides[1].active=true');
+      testSlideActive(1);
+    });
+
+    it('should create clickable prev nav button', function() {
+      var navPrev = elm.find('a.left');
+      var navNext = elm.find('a.right');
+
+      expect(navPrev.length).toBe(1);
+      expect(navNext.length).toBe(1);
+    });
+
+    it('should display clickable slide indicators', function () {
+      var indicators = elm.find('ol.carousel-indicators > li');
+      expect(indicators.length).toBe(3);
+    });
+
+    it('should stop cycling slides forward when noWrap is truthy', function () {
+      elm = $compile(
+          '<uib-carousel interval="interval" no-wrap="noWrap">' +
+            '<uib-slide ng-repeat="slide in slides" active="slide.active">' +
+              '{{slide.content}}' +
+            '</uib-slide>' +
+          '</uib-carousel>'
+        )(scope);
+
+      scope.noWrap = true;
+      scope.$apply();
+
+      scope = elm.isolateScope();
+      spyOn(scope, 'pause');
+
+      for (var i = 0; i < scope.slides.length - 1; ++i) {
+        scope.next();
+      }
+      testSlideActive(scope.slides.length - 1);
+      scope.next();
+      testSlideActive(scope.slides.length - 1);
+      expect(scope.pause).toHaveBeenCalled();
+    });
+
+    it('should stop cycling slides backward when noWrap is truthy', function () {
+      elm = $compile(
+          '<uib-carousel interval="interval" no-wrap="noWrap">' +
+            '<uib-slide ng-repeat="slide in slides" active="slide.active">' +
+              '{{slide.content}}' +
+            '</uib-slide>' +
+          '</uib-carousel>'
+        )(scope);
+
+      scope.noWrap = true;
+      scope.$apply();
+
+      scope = elm.isolateScope();
+      spyOn(scope, 'pause');
+
+      testSlideActive(0);
+      scope.prev();
+      testSlideActive(0);
+      expect(scope.pause).toHaveBeenCalled();
+    });
+
+    it('should hide navigation when only one slide', function () {
+      scope.slides = [{active:false,content:'one'}];
+      scope.$apply();
+      elm = $compile(
+          '<uib-carousel interval="interval" no-transition="true">' +
+            '<uib-slide ng-repeat="slide in slides" active="slide.active">' +
+              '{{slide.content}}' +
+            '</uib-slide>' +
+          '</uib-carousel>'
+        )(scope);
+      var indicators = elm.find('ol.carousel-indicators > li');
+      expect(indicators.length).toBe(0);
+
+      var navNext = elm.find('a.right');
+      expect(navNext.length).toBe(0);
+
+      var navPrev = elm.find('a.left');
+      expect(navPrev.length).toBe(0);
+    });
+
+    it('should show navigation when there are 3 slides', function () {
+      var indicators = elm.find('ol.carousel-indicators > li');
+      expect(indicators.length).not.toBe(0);
+
+      var navNext = elm.find('a.right');
+      expect(navNext.length).not.toBe(0);
+
+      var navPrev = elm.find('a.left');
+      expect(navPrev.length).not.toBe(0);
+    });
+
+    it('should go to next when clicking next button', function() {
+      var navNext = elm.find('a.right');
+      testSlideActive(0);
+      navNext.click();
+      testSlideActive(1);
+      navNext.click();
+      testSlideActive(2);
+      navNext.click();
+      testSlideActive(0);
+    });
+
+    it('should go to prev when clicking prev button', function() {
+      var navPrev = elm.find('a.left');
+      testSlideActive(0);
+      navPrev.click();
+      testSlideActive(2);
+      navPrev.click();
+      testSlideActive(1);
+      navPrev.click();
+      testSlideActive(0);
+    });
+
+    describe('swiping', function() {
+      it('should go next on swipeLeft', function() {
+        testSlideActive(0);
+        elm.triggerHandler('ngSwipeLeft');
+        testSlideActive(1);
+      });
+
+      it('should go prev on swipeRight', function() {
+        testSlideActive(0);
+        elm.triggerHandler('ngSwipeRight');
+        testSlideActive(2);
+      });
+    });
+
+    it('should select a slide when clicking on slide indicators', function () {
+      var indicators = elm.find('ol.carousel-indicators > li');
+      indicators.eq(1).click();
+      testSlideActive(1);
+    });
+
+    it('shouldnt go forward if interval is NaN or negative or has no slides', function() {
+      testSlideActive(0);
+      var previousInterval = scope.interval;
+      scope.$apply('interval = -1');
+      $interval.flush(previousInterval);
+      testSlideActive(0);
+      scope.$apply('interval = 1000');
+      $interval.flush(1000);
+      testSlideActive(1);
+      scope.$apply('interval = false');
+      $interval.flush(1000);
+      testSlideActive(1);
+      scope.$apply('interval = 1000');
+      $interval.flush(1000);
+      testSlideActive(2);
+      scope.$apply('slides = []');
+      $interval.flush(1000);
+      testSlideActive(2);
+    });
+
+    it('should bind the content to slides', function() {
+      var contents = elm.find('div.item');
+
+      expect(contents.length).toBe(3);
+      expect(contents.eq(0).text()).toBe('one');
+      expect(contents.eq(1).text()).toBe('two');
+      expect(contents.eq(2).text()).toBe('three');
+
+      scope.$apply(function() {
+        scope.slides[0].content = 'what';
+        scope.slides[1].content = 'no';
+        scope.slides[2].content = 'maybe';
+      });
+
+      expect(contents.eq(0).text()).toBe('what');
+      expect(contents.eq(1).text()).toBe('no');
+      expect(contents.eq(2).text()).toBe('maybe');
+    });
+
+    it('should be playing by default and cycle through slides', function() {
+      testSlideActive(0);
+      $interval.flush(scope.interval);
+      testSlideActive(1);
+      $interval.flush(scope.interval);
+      testSlideActive(2);
+      $interval.flush(scope.interval);
+      testSlideActive(0);
+    });
+
+    it('should pause and play on mouseover', function() {
+      testSlideActive(0);
+      $interval.flush(scope.interval);
+      testSlideActive(1);
+      elm.trigger('mouseenter');
+      testSlideActive(1);
+      $interval.flush(scope.interval);
+      testSlideActive(1);
+      elm.trigger('mouseleave');
+      $interval.flush(scope.interval);
+      testSlideActive(2);
+    });
+
+    it('should not pause on mouseover if noPause', function() {
+      scope.$apply('nopause = true');
+      testSlideActive(0);
+      elm.trigger('mouseenter');
+      $interval.flush(scope.interval);
+      testSlideActive(1);
+      elm.trigger('mouseleave');
+      $interval.flush(scope.interval);
+      testSlideActive(2);
+    });
+
+    it('should remove slide from dom and change active slide', function() {
+      scope.$apply('slides[2].active = true');
+      testSlideActive(2);
+      scope.$apply('slides.splice(0,1)');
+      expect(elm.find('div.item').length).toBe(2);
+      testSlideActive(1);
+      $interval.flush(scope.interval);
+      testSlideActive(0);
+      scope.$apply('slides.splice(1,1)');
+      expect(elm.find('div.item').length).toBe(1);
+      testSlideActive(0);
+    });
+
+    it('should change dom when you reassign ng-repeat slides array', function() {
+      scope.slides = [{content:'new1'},{content:'new2'},{content:'new3'}];
+      scope.$apply();
+      var contents = elm.find('div.item');
+      expect(contents.length).toBe(3);
+      expect(contents.eq(0).text()).toBe('new1');
+      expect(contents.eq(1).text()).toBe('new2');
+      expect(contents.eq(2).text()).toBe('new3');
+    });
+
+    it('should not change if next is clicked while transitioning', function() {
+      var carouselScope = elm.children().scope();
+      var next = elm.find('a.right');
+
+      testSlideActive(0);
+      carouselScope.$currentTransition = true;
+      next.click();
+
+      testSlideActive(0);
+
+      carouselScope.$currentTransition = null;
+      next.click();
+      testSlideActive(1);
+    });
+
+    it('issue 1414 - should not continue running timers after scope is destroyed', function() {
+      testSlideActive(0);
+      $interval.flush(scope.interval);
+      testSlideActive(1);
+      $interval.flush(scope.interval);
+      testSlideActive(2);
+      $interval.flush(scope.interval);
+      testSlideActive(0);
+      spyOn($interval, 'cancel').and.callThrough();
+      scope.$destroy();
+      expect($interval.cancel).toHaveBeenCalled();
+    });
+
+    it('issue 4390 - should reset the currentTransition if there are no slides', function() {
+      var carouselScope = elm.children().scope();
+      var next = elm.find('a.right');
+      scope.slides = [{content:'new1'},{content:'new2'},{content:'new3'}];
+      scope.$apply();
+
+      testSlideActive(0);
+      carouselScope.$currentTransition = true;
+
+      scope.slides = [];
+      scope.$apply();
+
+      expect(carouselScope.$currentTransition).toBe(null);
+    });
+
+    describe('slide order', function() {
+      beforeEach(function() {
+        scope.slides = [
+          {active:false,content:'one', id:1},
+          {active:false,content:'two', id:2},
+          {active:false,content:'three', id:3}
+        ];
+        elm = $compile(
+          '<uib-carousel interval="interval" no-transition="true" no-pause="nopause">' +
+            '<uib-slide ng-repeat="slide in slides | orderBy: \'id\' " active="slide.active" index="$index">' +
+              '{{slide.content}}' +
+            '</uib-slide>' +
+          '</uib-carousel>'
+        )(scope);
+        scope.$apply();
+        scope.slides[0].id = 3;
+        scope.slides[1].id = 1;
+        scope.slides[2].id = 2;
+        scope.$apply();
+      });
+
+      it('should change dom when an order of the slides was changed', function() {
+        testSlideActive(0);
+        var contents = elm.find('div.item');
+        expect(contents.length).toBe(3);
+        expect(contents.eq(0).text()).toBe('two');
+        expect(contents.eq(1).text()).toBe('three');
+        expect(contents.eq(2).text()).toBe('one');
+      });
+
+      it('should select next after order change', function() {
+        testSlideActive(0);
+        var next = elm.find('a.right');
+        next.click();
+        testSlideActive(1);
+      });
+
+      it('should select prev after order change', function() {
+        testSlideActive(0);
+        var prev = elm.find('a.left');
+        prev.click();
+        testSlideActive(2);
+      });
+
+      it('should add slide in the specified position', function() {
+        testSlideActive(0);
+        scope.slides[2].id = 4;
+        scope.slides.push({active:false,content:'four', id:2});
+        scope.$apply();
+        var contents = elm.find('div.item');
+        expect(contents.length).toBe(4);
+        expect(contents.eq(0).text()).toBe('two');
+        expect(contents.eq(1).text()).toBe('four');
+        expect(contents.eq(2).text()).toBe('one');
+        expect(contents.eq(3).text()).toBe('three');
+      });
+
+      it('should remove slide after order change', function() {
+        testSlideActive(0);
+        scope.slides.splice(1, 1);
+        scope.$apply();
+        var contents = elm.find('div.item');
+        expect(contents.length).toBe(2);
+        expect(contents.eq(0).text()).toBe('three');
+        expect(contents.eq(1).text()).toBe('one');
+      });
+
+    });
+
+  });
+
+  describe('controller', function() {
+    var scope, ctrl;
+    //create an array of slides and add to the scope
+    var slides = [{'content':1},{'content':2},{'content':3},{'content':4}];
+
+    beforeEach(function() {
+      scope = $rootScope.$new();
+      ctrl = $controller('UibCarouselController', {$scope: scope, $element: angular.element('<div></div>')});
+      for(var i = 0;i < slides.length;i++){
+        ctrl.addSlide(slides[i]);
+      }
+    });
+
+    describe('addSlide', function() {
+      it('should set first slide to active = true and the rest to false', function() {
+        angular.forEach(ctrl.slides, function(slide, i) {
+          if (i !== 0) {
+            expect(slide.active).not.toBe(true);
+          } else {
+            expect(slide.active).toBe(true);
+          }
+        });
+      });
+
+      it('should add new slide and change active to true if active is true on the added slide', function() {
+        var newSlide = {active: true};
+        expect(ctrl.slides.length).toBe(4);
+        ctrl.addSlide(newSlide);
+        expect(ctrl.slides.length).toBe(5);
+        expect(ctrl.slides[4].active).toBe(true);
+        expect(ctrl.slides[0].active).toBe(false);
+      });
+
+      it('should add a new slide and not change the active slide', function() {
+        var newSlide = {active: false};
+        expect(ctrl.slides.length).toBe(4);
+        ctrl.addSlide(newSlide);
+        expect(ctrl.slides.length).toBe(5);
+        expect(ctrl.slides[4].active).toBe(false);
+        expect(ctrl.slides[0].active).toBe(true);
+      });
+
+      it('should remove slide and change active slide if needed', function() {
+        expect(ctrl.slides.length).toBe(4);
+        ctrl.removeSlide(ctrl.slides[0]);
+        expect(ctrl.slides.length).toBe(3);
+        expect(ctrl.currentSlide).toBe(ctrl.slides[0]);
+        ctrl.select(ctrl.slides[2]);
+        ctrl.removeSlide(ctrl.slides[2]);
+        expect(ctrl.slides.length).toBe(2);
+        expect(ctrl.currentSlide).toBe(ctrl.slides[1]);
+        ctrl.removeSlide(ctrl.slides[0]);
+        expect(ctrl.slides.length).toBe(1);
+        expect(ctrl.currentSlide).toBe(ctrl.slides[0]);
+      });
+
+      it('issue 1414 - should not continue running timers after scope is destroyed', function() {
+        spyOn(scope, 'next').and.callThrough();
+        scope.interval = 2000;
+        scope.$digest();
+
+        $interval.flush(scope.interval);
+        expect(scope.next.calls.count()).toBe(1);
+
+        scope.$destroy();
+
+        $interval.flush(scope.interval);
+        expect(scope.next.calls.count()).toBe(1);
+      });
+    });
+
+    it('should be exposed in the template', inject(function($templateCache) {
+      $templateCache.put('template/carousel/carousel.html', '<div>{{carousel.text}}</div>');
+
+      var scope = $rootScope.$new();
+      var elm = $compile('<uib-carousel interval="bar" no-transition="false" no-pause="true"></uib-carousel>')(scope);
+      $rootScope.$digest();
+
+      var ctrl = elm.controller('uibCarousel');
+
+      expect(ctrl).toBeDefined();
+
+      ctrl.text = 'foo';
+      $rootScope.$digest();
+
+      expect(elm.html()).toBe('foo');
+    }));
+  });
+
+  it('should expose a custom model in the carousel slide', function() {
+    var scope = $rootScope.$new();
+    scope.slides = [
+      {active:false,content:'one'},
+      {active:false,content:'two'},
+      {active:false,content:'three'}
+    ];
+    var elm = $compile(
+      '<uib-carousel interval="interval" no-transition="true" no-pause="nopause">' +
+        '<uib-slide ng-repeat="slide in slides" active="slide.active" actual="slide">' +
+          '{{slide.content}}' +
+        '</uib-slide>' +
+      '</uib-carousel>'
+    )(scope);
+    $rootScope.$digest();
+
+    var ctrl = elm.controller('uibCarousel');
+
+    expect(angular.equals(ctrl.slides.map(function(slide) {
+      return slide.actual;
+    }), scope.slides)).toBe(true);
+  });
+});
+
+describe('carousel deprecation', function() {
+  beforeEach(module('ui.bootstrap.carousel'));
+  beforeEach(module('template/carousel/carousel.html', 'template/carousel/slide.html'));
+
+  it('should suppress warning', function() {
+    module(function($provide) {
+      $provide.value('$carouselSuppressWarning', true);
+    });
+
+    inject(function($compile, $log, $rootScope) {
+      spyOn($log, 'warn');
+
+      var element = '<carousel interval="interval" no-transition="true" no-pause="nopause">' +
+          '<slide ng-repeat="slide in slides" active="slide.active">' +
+            '{{slide.content}}' +
+          '</slide>' +
+        '</carousel>';
+      element = $compile(element)($rootScope);
+      $rootScope.$digest();
+      expect($log.warn.calls.count()).toBe(0);
+    });
+  });
+
+  it('should give warning by default', inject(function($compile, $log, $rootScope) {
+    spyOn($log, 'warn');
+
+    var element = '<carousel interval="interval" no-transition="true" no-pause="nopause">' +
+        '<slide ng-repeat="slide in slides" active="slide.active">' +
+          '{{slide.content}}' +
+        '</slide>' +
+      '</carousel>';
+    element = $compile(element)($rootScope);
+    $rootScope.$digest();
+
+    expect($log.warn.calls.count()).toBe(2);
+    expect($log.warn.calls.argsFor(0)).toEqual(['CarouselController is now deprecated. Use UibCarouselController instead.']);
+    expect($log.warn.calls.argsFor(1)).toEqual(['carousel is now deprecated. Use uib-carousel instead.']);
+  }));
+  
+   it('should give warning by default for slider', inject(function($compile, $log, $rootScope) {
+    spyOn($log, 'warn');
+
+    var element = '<carousel interval="interval" no-transition="true" no-pause="nopause">' +
+        '<slide></slide>' + 
+      '</carousel>';
+    element = $compile(element)($rootScope);
+    $rootScope.$digest();
+
+    expect($log.warn.calls.count()).toBe(3);
+    expect($log.warn.calls.argsFor(0)).toEqual(['CarouselController is now deprecated. Use UibCarouselController instead.']);
+    expect($log.warn.calls.argsFor(1)).toEqual(['slide is now deprecated. Use uib-slide instead.']);
+    expect($log.warn.calls.argsFor(2)).toEqual(['carousel is now deprecated. Use uib-carousel instead.']);
+  }));
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/collapse/collapse.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/collapse/collapse.js
new file mode 100644
index 0000000..c01e647
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/collapse/collapse.js
@@ -0,0 +1,159 @@
+angular.module('ui.bootstrap.collapse', [])
+
+  .directive('uibCollapse', ['$animate', '$injector', function($animate, $injector) {
+    var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null;
+    return {
+      link: function(scope, element, attrs) {
+        function expand() {
+          element.removeClass('collapse')
+            .addClass('collapsing')
+            .attr('aria-expanded', true)
+            .attr('aria-hidden', false);
+
+          if ($animateCss) {
+            $animateCss(element, {
+              addClass: 'in',
+              easing: 'ease',
+              to: { height: element[0].scrollHeight + 'px' }
+            }).start().finally(expandDone);
+          } else {
+            $animate.addClass(element, 'in', {
+              to: { height: element[0].scrollHeight + 'px' }
+            }).then(expandDone);
+          }
+        }
+
+        function expandDone() {
+          element.removeClass('collapsing')
+            .addClass('collapse')
+            .css({height: 'auto'});
+        }
+
+        function collapse() {
+          if (!element.hasClass('collapse') && !element.hasClass('in')) {
+            return collapseDone();
+          }
+
+          element
+            // IMPORTANT: The height must be set before adding "collapsing" class.
+            // Otherwise, the browser attempts to animate from height 0 (in
+            // collapsing class) to the given height here.
+            .css({height: element[0].scrollHeight + 'px'})
+            // initially all panel collapse have the collapse class, this removal
+            // prevents the animation from jumping to collapsed state
+            .removeClass('collapse')
+            .addClass('collapsing')
+            .attr('aria-expanded', false)
+            .attr('aria-hidden', true);
+
+          if ($animateCss) {
+            $animateCss(element, {
+              removeClass: 'in',
+              to: {height: '0'}
+            }).start().finally(collapseDone);
+          } else {
+            $animate.removeClass(element, 'in', {
+              to: {height: '0'}
+            }).then(collapseDone);
+          }
+        }
+
+        function collapseDone() {
+          element.css({height: '0'}); // Required so that collapse works when animation is disabled
+          element.removeClass('collapsing')
+            .addClass('collapse');
+        }
+
+        scope.$watch(attrs.uibCollapse, function(shouldCollapse) {
+          if (shouldCollapse) {
+            collapse();
+          } else {
+            expand();
+          }
+        });
+      }
+    };
+  }]);
+
+/* Deprecated collapse below */
+
+angular.module('ui.bootstrap.collapse')
+
+  .value('$collapseSuppressWarning', false)
+
+  .directive('collapse', ['$animate', '$injector', '$log', '$collapseSuppressWarning', function($animate, $injector, $log, $collapseSuppressWarning) {
+    var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null;
+    return {
+      link: function(scope, element, attrs) {
+        if (!$collapseSuppressWarning) {
+          $log.warn('collapse is now deprecated. Use uib-collapse instead.');
+        }
+
+        function expand() {
+          element.removeClass('collapse')
+            .addClass('collapsing')
+            .attr('aria-expanded', true)
+            .attr('aria-hidden', false);
+
+          if ($animateCss) {
+            $animateCss(element, {
+              easing: 'ease',
+              to: { height: element[0].scrollHeight + 'px' }
+            }).start().done(expandDone);
+          } else {
+            $animate.animate(element, {}, {
+              height: element[0].scrollHeight + 'px'
+            }).then(expandDone);
+          }
+        }
+
+        function expandDone() {
+          element.removeClass('collapsing')
+            .addClass('collapse in')
+            .css({height: 'auto'});
+        }
+
+        function collapse() {
+          if (!element.hasClass('collapse') && !element.hasClass('in')) {
+            return collapseDone();
+          }
+
+          element
+            // IMPORTANT: The height must be set before adding "collapsing" class.
+            // Otherwise, the browser attempts to animate from height 0 (in
+            // collapsing class) to the given height here.
+            .css({height: element[0].scrollHeight + 'px'})
+            // initially all panel collapse have the collapse class, this removal
+            // prevents the animation from jumping to collapsed state
+            .removeClass('collapse in')
+            .addClass('collapsing')
+            .attr('aria-expanded', false)
+            .attr('aria-hidden', true);
+
+          if ($animateCss) {
+            $animateCss(element, {
+              to: {height: '0'}
+            }).start().done(collapseDone);
+          } else {
+            $animate.animate(element, {}, {
+              height: '0'
+            }).then(collapseDone);
+          }
+        }
+
+        function collapseDone() {
+          element.css({height: '0'}); // Required so that collapse works when animation is disabled
+          element.removeClass('collapsing')
+            .addClass('collapse');
+        }
+
+        scope.$watch(attrs.collapse, function(shouldCollapse) {
+          if (shouldCollapse) {
+            collapse();
+          } else {
+            expand();
+          }
+        });
+      }
+    };
+  }]);
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/collapse/docs/demo.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/collapse/docs/demo.html
new file mode 100644
index 0000000..462bda3
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/collapse/docs/demo.html
@@ -0,0 +1,7 @@
+<div ng-controller="CollapseDemoCtrl">
+	<button type="button" class="btn btn-default" ng-click="isCollapsed = !isCollapsed">Toggle collapse</button>
+	<hr>
+	<div uib-collapse="isCollapsed">
+		<div class="well well-lg">Some content</div>
+	</div>
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/collapse/docs/demo.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/collapse/docs/demo.js
new file mode 100644
index 0000000..897eeca
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/collapse/docs/demo.js
@@ -0,0 +1,3 @@
+angular.module('ui.bootstrap.demo').controller('CollapseDemoCtrl', function ($scope) {
+  $scope.isCollapsed = false;
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/collapse/docs/readme.md b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/collapse/docs/readme.md
new file mode 100644
index 0000000..b2bd63c
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/collapse/docs/readme.md
@@ -0,0 +1,8 @@
+**uib-collapse** provides a simple way to hide and show an element with a css transition
+
+### uib-collapse settings
+
+* `uib-collapse`
+  <i class="glyphicon glyphicon-eye-open"></i>
+  _(Default: `false`)_ -
+  Whether the element should be collapsed or not.
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/collapse/test/collapse.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/collapse/test/collapse.spec.js
new file mode 100644
index 0000000..5083904
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/collapse/test/collapse.spec.js
@@ -0,0 +1,165 @@
+describe('collapse directive', function() {
+  var element, scope, $compile, $animate;
+
+  beforeEach(module('ui.bootstrap.collapse'));
+  beforeEach(module('ngAnimateMock'));
+  beforeEach(inject(function(_$rootScope_, _$compile_, _$animate_) {
+    scope = _$rootScope_;
+    $compile = _$compile_;
+    $animate = _$animate_;
+  }));
+
+  beforeEach(function() {
+    element = $compile('<div uib-collapse="isCollapsed">Some Content</div>')(scope);
+    angular.element(document.body).append(element);
+  });
+
+  afterEach(function() {
+    element.remove();
+  });
+
+  it('should be hidden on initialization if isCollapsed = true', function() {
+    scope.isCollapsed = true;
+    scope.$digest();
+    expect(element.height()).toBe(0);
+  });
+
+  it('should collapse if isCollapsed = true on subsequent use', function() {
+    scope.isCollapsed = false;
+    scope.$digest();
+    $animate.flush();
+    scope.isCollapsed = true;
+    scope.$digest();
+    $animate.flush();
+    expect(element.height()).toBe(0);
+  });
+
+  it('should be shown on initialization if isCollapsed = false', function() {
+    scope.isCollapsed = false;
+    scope.$digest();
+    $animate.flush();
+    expect(element.height()).not.toBe(0);
+  });
+
+  it('should expand if isCollapsed = false on subsequent use', function() {
+    scope.isCollapsed = false;
+    scope.$digest();
+    $animate.flush();
+    scope.isCollapsed = true;
+    scope.$digest();
+    $animate.flush();
+    scope.isCollapsed = false;
+    scope.$digest();
+    $animate.flush();
+    expect(element.height()).not.toBe(0);
+  });
+
+  it('should expand if isCollapsed = true on subsequent uses', function() {
+    scope.isCollapsed = false;
+    scope.$digest();
+    $animate.flush();
+    scope.isCollapsed = true;
+    scope.$digest();
+    $animate.flush();
+    scope.isCollapsed = false;
+    scope.$digest();
+    $animate.flush();
+    scope.isCollapsed = true;
+    scope.$digest();
+    $animate.flush();
+    expect(element.height()).toBe(0);
+  });
+
+  it('should change aria-expanded attribute', function() {
+    scope.isCollapsed = false;
+    scope.$digest();
+    $animate.flush();
+    expect(element.attr('aria-expanded')).toBe('true');
+
+    scope.isCollapsed = true;
+    scope.$digest();
+    $animate.flush();
+    expect(element.attr('aria-expanded')).toBe('false');
+  });
+
+  it('should change aria-hidden attribute', function() {
+    scope.isCollapsed = false;
+    scope.$digest();
+    $animate.flush();
+    expect(element.attr('aria-hidden')).toBe('false');
+
+    scope.isCollapsed = true;
+    scope.$digest();
+    $animate.flush();
+    expect(element.attr('aria-hidden')).toBe('true');
+  });
+
+  describe('dynamic content', function() {
+    var element;
+
+    beforeEach(function() {
+      element = angular.element('<div uib-collapse="isCollapsed"><p>Initial content</p><div ng-show="exp">Additional content</div></div>');
+      $compile(element)(scope);
+      angular.element(document.body).append(element);
+    });
+
+    afterEach(function() {
+      element.remove();
+    });
+
+    it('should grow accordingly when content size inside collapse increases', function() {
+      scope.exp = false;
+      scope.isCollapsed = false;
+      scope.$digest();
+      $animate.flush();
+      var collapseHeight = element.height();
+      scope.exp = true;
+      scope.$digest();
+      expect(element.height()).toBeGreaterThan(collapseHeight);
+    });
+
+    it('should shrink accordingly when content size inside collapse decreases', function() {
+      scope.exp = true;
+      scope.isCollapsed = false;
+      scope.$digest();
+      $animate.flush();
+      var collapseHeight = element.height();
+      scope.exp = false;
+      scope.$digest();
+      expect(element.height()).toBeLessThan(collapseHeight);
+    });
+  });
+});
+
+/* Deprecation tests below */
+
+describe('collapse deprecation', function() {
+  beforeEach(module('ui.bootstrap.collapse'));
+  beforeEach(module('ngAnimateMock'));
+
+  it('should suppress warning', function() {
+    module(function($provide) {
+      $provide.value('$collapseSuppressWarning', true);
+    });
+
+    inject(function($compile, $log, $rootScope) {
+      spyOn($log, 'warn');
+
+      var element = $compile('<div collapse="isCollapsed">Some Content</div>')($rootScope);
+      $rootScope.$digest();
+
+      expect($log.warn.calls.count()).toBe(0);
+    });
+  });
+
+  it('should give warning by default', inject(function($compile, $log, $rootScope) {
+    spyOn($log, 'warn');
+
+    var element = $compile('<div collapse="isCollapsed">Some Content</div>')($rootScope);
+    $rootScope.$digest();
+
+    expect($log.warn.calls.count()).toBe(1);
+    expect($log.warn.calls.argsFor(0)).toEqual(['collapse is now deprecated. Use uib-collapse instead.']);
+
+  }));
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/dateparser/dateparser.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/dateparser/dateparser.js
new file mode 100644
index 0000000..0b4249f
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/dateparser/dateparser.js
@@ -0,0 +1,233 @@
+angular.module('ui.bootstrap.dateparser', [])
+
+.service('uibDateParser', ['$log', '$locale', 'orderByFilter', function($log, $locale, orderByFilter) {
+  // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js
+  var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
+
+  var localeId;
+  var formatCodeToRegex;
+
+  this.init = function() {
+    localeId = $locale.id;
+
+    this.parsers = {};
+
+    formatCodeToRegex = {
+      'yyyy': {
+        regex: '\\d{4}',
+        apply: function(value) { this.year = +value; }
+      },
+      'yy': {
+        regex: '\\d{2}',
+        apply: function(value) { this.year = +value + 2000; }
+      },
+      'y': {
+        regex: '\\d{1,4}',
+        apply: function(value) { this.year = +value; }
+      },
+      'MMMM': {
+        regex: $locale.DATETIME_FORMATS.MONTH.join('|'),
+        apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); }
+      },
+      'MMM': {
+        regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
+        apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); }
+      },
+      'MM': {
+        regex: '0[1-9]|1[0-2]',
+        apply: function(value) { this.month = value - 1; }
+      },
+      'M': {
+        regex: '[1-9]|1[0-2]',
+        apply: function(value) { this.month = value - 1; }
+      },
+      'dd': {
+        regex: '[0-2][0-9]{1}|3[0-1]{1}',
+        apply: function(value) { this.date = +value; }
+      },
+      'd': {
+        regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
+        apply: function(value) { this.date = +value; }
+      },
+      'EEEE': {
+        regex: $locale.DATETIME_FORMATS.DAY.join('|')
+      },
+      'EEE': {
+        regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|')
+      },
+      'HH': {
+        regex: '(?:0|1)[0-9]|2[0-3]',
+        apply: function(value) { this.hours = +value; }
+      },
+      'hh': {
+        regex: '0[0-9]|1[0-2]',
+        apply: function(value) { this.hours = +value; }
+      },
+      'H': {
+        regex: '1?[0-9]|2[0-3]',
+        apply: function(value) { this.hours = +value; }
+      },
+      'h': {
+        regex: '[0-9]|1[0-2]',
+        apply: function(value) { this.hours = +value; }
+      },
+      'mm': {
+        regex: '[0-5][0-9]',
+        apply: function(value) { this.minutes = +value; }
+      },
+      'm': {
+        regex: '[0-9]|[1-5][0-9]',
+        apply: function(value) { this.minutes = +value; }
+      },
+      'sss': {
+        regex: '[0-9][0-9][0-9]',
+        apply: function(value) { this.milliseconds = +value; }
+      },
+      'ss': {
+        regex: '[0-5][0-9]',
+        apply: function(value) { this.seconds = +value; }
+      },
+      's': {
+        regex: '[0-9]|[1-5][0-9]',
+        apply: function(value) { this.seconds = +value; }
+      },
+      'a': {
+        regex: $locale.DATETIME_FORMATS.AMPMS.join('|'),
+        apply: function(value) {
+          if (this.hours === 12) {
+            this.hours = 0;
+          }
+
+          if (value === 'PM') {
+            this.hours += 12;
+          }
+        }
+      }
+    };
+  };
+
+  this.init();
+
+  function createParser(format) {
+    var map = [], regex = format.split('');
+
+    angular.forEach(formatCodeToRegex, function(data, code) {
+      var index = format.indexOf(code);
+
+      if (index > -1) {
+        format = format.split('');
+
+        regex[index] = '(' + data.regex + ')';
+        format[index] = '$'; // Custom symbol to define consumed part of format
+        for (var i = index + 1, n = index + code.length; i < n; i++) {
+          regex[i] = '';
+          format[i] = '$';
+        }
+        format = format.join('');
+
+        map.push({ index: index, apply: data.apply });
+      }
+    });
+
+    return {
+      regex: new RegExp('^' + regex.join('') + '$'),
+      map: orderByFilter(map, 'index')
+    };
+  }
+
+  this.parse = function(input, format, baseDate) {
+    if (!angular.isString(input) || !format) {
+      return input;
+    }
+
+    format = $locale.DATETIME_FORMATS[format] || format;
+    format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&');
+
+    if ($locale.id !== localeId) {
+      this.init();
+    }
+
+    if (!this.parsers[format]) {
+      this.parsers[format] = createParser(format);
+    }
+
+    var parser = this.parsers[format],
+        regex = parser.regex,
+        map = parser.map,
+        results = input.match(regex);
+
+    if (results && results.length) {
+      var fields, dt;
+      if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) {
+        fields = {
+          year: baseDate.getFullYear(),
+          month: baseDate.getMonth(),
+          date: baseDate.getDate(),
+          hours: baseDate.getHours(),
+          minutes: baseDate.getMinutes(),
+          seconds: baseDate.getSeconds(),
+          milliseconds: baseDate.getMilliseconds()
+        };
+      } else {
+        if (baseDate) {
+          $log.warn('dateparser:', 'baseDate is not a valid date');
+        }
+        fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 };
+      }
+
+      for (var i = 1, n = results.length; i < n; i++) {
+        var mapper = map[i-1];
+        if (mapper.apply) {
+          mapper.apply.call(fields, results[i]);
+        }
+      }
+
+      if (isValid(fields.year, fields.month, fields.date)) {
+        if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) {
+          dt = new Date(baseDate);
+          dt.setFullYear(fields.year, fields.month, fields.date,
+            fields.hours, fields.minutes, fields.seconds,
+            fields.milliseconds || 0);
+        } else {
+          dt = new Date(fields.year, fields.month, fields.date,
+            fields.hours, fields.minutes, fields.seconds,
+            fields.milliseconds || 0);
+        }
+      }
+
+      return dt;
+    }
+  };
+
+  // Check if date is valid for specific month (and year for February).
+  // Month: 0 = Jan, 1 = Feb, etc
+  function isValid(year, month, date) {
+    if (date < 1) {
+      return false;
+    }
+
+    if (month === 1 && date > 28) {
+      return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0);
+    }
+
+    if (month === 3 || month === 5 || month === 8 || month === 10) {
+      return date < 31;
+    }
+
+    return true;
+  }
+}]);
+
+/* Deprecated dateparser below */
+
+angular.module('ui.bootstrap.dateparser')
+
+.value('$dateParserSuppressWarning', false)
+
+.service('dateParser', ['$log', '$dateParserSuppressWarning', 'uibDateParser', function($log, $dateParserSuppressWarning, uibDateParser) {
+  if (!$dateParserSuppressWarning) {
+    $log.warn('dateParser is now deprecated. Use uibDateParser instead.');
+  }
+
+  angular.extend(this, uibDateParser);
+}]);
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/dateparser/test/dateparser.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/dateparser/test/dateparser.spec.js
new file mode 100644
index 0000000..fb8897c
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/dateparser/test/dateparser.spec.js
@@ -0,0 +1,257 @@
+describe('date parser', function() {
+  var dateParser;
+
+  beforeEach(module('ui.bootstrap.dateparser'));
+  beforeEach(inject(function (uibDateParser) {
+    dateParser = uibDateParser;
+  }));
+
+  function expectParse(input, format, date) {
+    expect(dateParser.parse(input, format)).toEqual(date);
+  }
+
+  describe('with custom formats', function() {
+    it('should work correctly for `dd`, `MM`, `yyyy`', function() {
+      expectParse('17.11.2013', 'dd.MM.yyyy', new Date(2013, 10, 17, 0));
+      expectParse('31.12.2013', 'dd.MM.yyyy', new Date(2013, 11, 31, 0));
+      expectParse('08-03-1991', 'dd-MM-yyyy', new Date(1991, 2, 8, 0));
+      expectParse('03/05/1980', 'MM/dd/yyyy', new Date(1980, 2, 5, 0));
+      expectParse('10.01/1983', 'dd.MM/yyyy', new Date(1983, 0, 10, 0));
+      expectParse('11-09-1980', 'MM-dd-yyyy', new Date(1980, 10, 9, 0));
+      expectParse('2011/02/05', 'yyyy/MM/dd', new Date(2011, 1, 5, 0));
+    });
+
+    it('should work correctly for `yy`', function() {
+      expectParse('17.11.13', 'dd.MM.yy', new Date(2013, 10, 17, 0));
+      expectParse('02-05-11', 'dd-MM-yy', new Date(2011, 4, 2, 0));
+      expectParse('02/05/80', 'MM/dd/yy', new Date(2080, 1, 5, 0));
+      expectParse('55/02/05', 'yy/MM/dd', new Date(2055, 1, 5, 0));
+      expectParse('11-08-13', 'dd-MM-yy', new Date(2013, 7, 11, 0));
+    });
+
+    it('should work correctly for `M`', function() {
+      expectParse('8/11/2013', 'M/dd/yyyy', new Date(2013, 7, 11, 0));
+      expectParse('07.11.05', 'dd.M.yy', new Date(2005, 10, 7, 0));
+      expectParse('02-5-11', 'dd-M-yy', new Date(2011, 4, 2, 0));
+      expectParse('2/05/1980', 'M/dd/yyyy', new Date(1980, 1, 5, 0));
+      expectParse('1955/2/05', 'yyyy/M/dd', new Date(1955, 1, 5, 0));
+      expectParse('02-5-11', 'dd-M-yy', new Date(2011, 4, 2, 0));
+    });
+
+    it('should work correctly for `MMM`', function() {
+      expectParse('30.Sep.10', 'dd.MMM.yy', new Date(2010, 8, 30, 0));
+      expectParse('02-May-11', 'dd-MMM-yy', new Date(2011, 4, 2, 0));
+      expectParse('Feb/05/1980', 'MMM/dd/yyyy', new Date(1980, 1, 5, 0));
+      expectParse('1955/Feb/05', 'yyyy/MMM/dd', new Date(1955, 1, 5, 0));
+    });
+
+    it('should work correctly for `MMMM`', function() {
+      expectParse('17.November.13', 'dd.MMMM.yy', new Date(2013, 10, 17, 0));
+      expectParse('05-March-1980', 'dd-MMMM-yyyy', new Date(1980, 2, 5, 0));
+      expectParse('February/05/1980', 'MMMM/dd/yyyy', new Date(1980, 1, 5, 0));
+      expectParse('1949/December/20', 'yyyy/MMMM/dd', new Date(1949, 11, 20, 0));
+    });
+
+    it('should work correctly for `d`', function() {
+      expectParse('17.November.13', 'd.MMMM.yy', new Date(2013, 10, 17, 0));
+      expectParse('8-March-1991', 'd-MMMM-yyyy', new Date(1991, 2, 8, 0));
+      expectParse('February/5/1980', 'MMMM/d/yyyy', new Date(1980, 1, 5, 0));
+      expectParse('1955/February/5', 'yyyy/MMMM/d', new Date(1955, 1, 5, 0));
+      expectParse('11-08-13', 'd-MM-yy', new Date(2013, 7, 11, 0));
+    });
+
+    it('should work correctly for `HH`', function() {
+      expectParse('22.March.15.22', 'd.MMMM.yy.HH', new Date(2015, 2, 22, 22));
+      expectParse('8-March-1991-11', 'd-MMMM-yyyy-HH', new Date(1991, 2, 8, 11));
+      expectParse('February/5/1980/00', 'MMMM/d/yyyy/HH', new Date(1980, 1, 5, 0));
+      expectParse('1955/February/5 03', 'yyyy/MMMM/d HH', new Date(1955, 1, 5, 3));
+      expectParse('11-08-13 23', 'd-MM-yy HH', new Date(2013, 7, 11, 23));
+    });
+
+    it('should work correctly for `hh`', function() {
+      expectParse('22.March.15.22', 'd.MMMM.yy.hh', undefined);
+      expectParse('22.March.15.12', 'd.MMMM.yy.hh', new Date(2015, 2, 22, 12));
+      expectParse('8-March-1991-11', 'd-MMMM-yyyy-hh', new Date(1991, 2, 8, 11));
+      expectParse('February/5/1980/00', 'MMMM/d/yyyy/hh', new Date(1980, 1, 5, 0));
+      expectParse('1955/February/5 03', 'yyyy/MMMM/d hh', new Date(1955, 1, 5, 3));
+      expectParse('11-08-13 23', 'd-MM-yy hh', undefined);
+      expectParse('11-08-13 09', 'd-MM-yy hh', new Date(2013, 7, 11, 9));
+    });
+
+    it('should work correctly for `H`', function() {
+      expectParse('22.March.15.22', 'd.MMMM.yy.H', new Date(2015, 2, 22, 22));
+      expectParse('8-March-1991-11', 'd-MMMM-yyyy-H', new Date(1991, 2, 8, 11));
+      expectParse('February/5/1980/0', 'MMMM/d/yyyy/H', new Date(1980, 1, 5, 0));
+      expectParse('1955/February/5 3', 'yyyy/MMMM/d H', new Date(1955, 1, 5, 3));
+      expectParse('11-08-13 23', 'd-MM-yy H', new Date(2013, 7, 11, 23));
+    });
+
+    it('should work correctly for `h`', function() {
+      expectParse('22.March.15.12', 'd.MMMM.yy.h', new Date(2015, 2, 22, 12));
+      expectParse('8-March-1991-11', 'd-MMMM-yyyy-h', new Date(1991, 2, 8, 11));
+      expectParse('February/5/1980/0', 'MMMM/d/yyyy/h', new Date(1980, 1, 5, 0));
+      expectParse('1955/February/5 3', 'yyyy/MMMM/d h', new Date(1955, 1, 5, 3));
+      expectParse('11-08-13 3', 'd-MM-yy h', new Date(2013, 7, 11, 3));
+    });
+
+    it('should work correctly for `mm`', function() {
+      expectParse('22.March.15.22', 'd.MMMM.yy.mm', new Date(2015, 2, 22, 0, 22));
+      expectParse('8-March-1991-59', 'd-MMMM-yyyy-mm', new Date(1991, 2, 8, 0, 59));
+      expectParse('February/5/1980/00', 'MMMM/d/yyyy/mm', new Date(1980, 1, 5, 0, 0));
+      expectParse('1955/February/5 03', 'yyyy/MMMM/d mm', new Date(1955, 1, 5, 0, 3));
+      expectParse('11-08-13 46', 'd-MM-yy mm', new Date(2013, 7, 11, 0, 46));
+      expectParse('22.March.15.22:33', 'd.MMMM.yy.HH:mm', new Date(2015, 2, 22, 22, 33));
+      expectParse('22.March.15.2:01', 'd.MMMM.yy.H:mm', new Date(2015, 2, 22, 2, 1));
+    });
+
+    it('should work correctly for `m`', function() {
+      expectParse('22.March.15.22', 'd.MMMM.yy.m', new Date(2015, 2, 22, 0, 22));
+      expectParse('8-March-1991-59', 'd-MMMM-yyyy-m', new Date(1991, 2, 8, 0, 59));
+      expectParse('February/5/1980/0', 'MMMM/d/yyyy/m', new Date(1980, 1, 5, 0, 0));
+      expectParse('1955/February/5 3', 'yyyy/MMMM/d m', new Date(1955, 1, 5, 0, 3));
+      expectParse('11-08-13 46', 'd-MM-yy m', new Date(2013, 7, 11, 0, 46));
+      expectParse('22.March.15.22:3', 'd.MMMM.yy.HH:m', new Date(2015, 2, 22, 22, 3));
+      expectParse('22.March.15.2:1', 'd.MMMM.yy.H:m', new Date(2015, 2, 22, 2, 1));
+    });
+
+    it('should work correctly for `sss`', function() {
+      expectParse('22.March.15.123', 'd.MMMM.yy.sss', new Date(2015, 2, 22, 0, 0, 0, 123));
+      expectParse('8-March-1991-059', 'd-MMMM-yyyy-sss', new Date(1991, 2, 8, 0, 0, 0, 59));
+      expectParse('February/5/1980/000', 'MMMM/d/yyyy/sss', new Date(1980, 1, 5, 0, 0, 0));
+      expectParse('1955/February/5 003', 'yyyy/MMMM/d sss', new Date(1955, 1, 5, 0, 0, 0, 3));
+      expectParse('11-08-13 046', 'd-MM-yy sss', new Date(2013, 7, 11, 0, 0, 0, 46));
+      expectParse('22.March.15.22:33:044', 'd.MMMM.yy.HH:mm:sss', new Date(2015, 2, 22, 22, 33, 0, 44));
+      expectParse('22.March.15.0:0:001', 'd.MMMM.yy.H:m:sss', new Date(2015, 2, 22, 0, 0, 0, 1));
+    });
+
+    it('should work correctly for `ss`', function() {
+      expectParse('22.March.15.22', 'd.MMMM.yy.ss', new Date(2015, 2, 22, 0, 0, 22));
+      expectParse('8-March-1991-59', 'd-MMMM-yyyy-ss', new Date(1991, 2, 8, 0, 0, 59));
+      expectParse('February/5/1980/00', 'MMMM/d/yyyy/ss', new Date(1980, 1, 5, 0, 0, 0));
+      expectParse('1955/February/5 03', 'yyyy/MMMM/d ss', new Date(1955, 1, 5, 0, 0, 3));
+      expectParse('11-08-13 46', 'd-MM-yy ss', new Date(2013, 7, 11, 0, 0, 46));
+      expectParse('22.March.15.22:33:44', 'd.MMMM.yy.HH:mm:ss', new Date(2015, 2, 22, 22, 33, 44));
+      expectParse('22.March.15.0:0:01', 'd.MMMM.yy.H:m:ss', new Date(2015, 2, 22, 0, 0, 1));
+    });
+
+    it('should work correctly for `s`', function() {
+      expectParse('22.March.15.22', 'd.MMMM.yy.s', new Date(2015, 2, 22, 0, 0, 22));
+      expectParse('8-March-1991-59', 'd-MMMM-yyyy-s', new Date(1991, 2, 8, 0, 0, 59));
+      expectParse('February/5/1980/0', 'MMMM/d/yyyy/s', new Date(1980, 1, 5, 0, 0, 0));
+      expectParse('1955/February/5 3', 'yyyy/MMMM/d s', new Date(1955, 1, 5, 0, 0, 3));
+      expectParse('11-08-13 46', 'd-MM-yy s', new Date(2013, 7, 11, 0, 0, 46));
+      expectParse('22.March.15.22:33:4', 'd.MMMM.yy.HH:mm:s', new Date(2015, 2, 22, 22, 33, 4));
+      expectParse('22.March.15.22:3:4', 'd.MMMM.yy.HH:m:s', new Date(2015, 2, 22, 22, 3, 4));
+    });
+
+    it('should work correctly for `a`', function() {
+      expectParse('22.March.15.10AM', 'd.MMMM.yy.hha', new Date(2015, 2, 22, 10));
+      expectParse('22.March.15.10PM', 'd.MMMM.yy.hha', new Date(2015, 2, 22, 22));
+      expectParse('8-March-1991-11AM', 'd-MMMM-yyyy-hha', new Date(1991, 2, 8, 11));
+      expectParse('8-March-1991-11PM', 'd-MMMM-yyyy-hha', new Date(1991, 2, 8, 23));
+      expectParse('February/5/1980/12AM', 'MMMM/d/yyyy/hha', new Date(1980, 1, 5, 0));
+      expectParse('February/5/1980/12PM', 'MMMM/d/yyyy/hha', new Date(1980, 1, 5, 12));
+      expectParse('1955/February/5 03AM', 'yyyy/MMMM/d hha', new Date(1955, 1, 5, 3));
+      expectParse('1955/February/5 03PM', 'yyyy/MMMM/d hha', new Date(1955, 1, 5, 15));
+      expectParse('11-08-13 09AM', 'd-MM-yy hha', new Date(2013, 7, 11, 9));
+      expectParse('11-08-13 09PM', 'd-MM-yy hha', new Date(2013, 7, 11, 21));
+    });
+  });
+
+  describe('with predefined formats', function() {
+    it('should work correctly for `shortDate`', function() {
+      expectParse('9/3/10', 'shortDate', new Date(2010, 8, 3, 0));
+    });
+
+    it('should work correctly for `mediumDate`', function() {
+      expectParse('Sep 3, 2010', 'mediumDate', new Date(2010, 8, 3, 0));
+    });
+
+    it('should work correctly for `longDate`', function() {
+      expectParse('September 3, 2010', 'longDate', new Date(2010, 8, 3, 0));
+    });
+
+    it('should work correctly for `fullDate`', function() {
+      expectParse('Friday, September 3, 2010', 'fullDate', new Date(2010, 8, 3, 0));
+    });
+  });
+
+  describe('with edge case', function() {
+    it('should not work for invalid number of days in February', function() {
+      expect(dateParser.parse('29.02.2013', 'dd.MM.yyyy')).toBeUndefined();
+    });
+
+    it('should not work for 0 number of days', function() {
+      expect(dateParser.parse('00.02.2013', 'dd.MM.yyyy')).toBeUndefined();
+    });
+
+    it('should work for 29 days in February for leap years', function() {
+      expectParse('29.02.2000', 'dd.MM.yyyy', new Date(2000, 1, 29, 0));
+    });
+
+    it('should not work for 31 days for some months', function() {
+      expect(dateParser.parse('31-04-2013', 'dd-MM-yyyy')).toBeUndefined();
+      expect(dateParser.parse('November 31, 2013', 'MMMM d, yyyy')).toBeUndefined();
+    });
+
+    it('should work when base date is a string', function() {
+      expect(dateParser.parse('01-02-2034', 'dd-MM-yyyy', '05-06-2078')).toEqual(new Date(2034, 1, 1));
+    });
+
+    it('should work when base date is an invalid date', function() {
+      expect(dateParser.parse('30-12-2015', 'dd-MM-yyyy', new Date('foo'))).toEqual(new Date(2015, 11, 30));
+    });
+  });
+
+  it('should not parse non-string inputs', function() {
+    expect(dateParser.parse(123456, 'dd.MM.yyyy')).toBe(123456);
+    var date = new Date();
+    expect(dateParser.parse(date, 'dd.MM.yyyy')).toBe(date);
+  });
+
+  it('should not parse if no format is specified', function() {
+    expect(dateParser.parse('21.08.1951', '')).toBe('21.08.1951');
+  });
+
+  it('should reinitialize when locale changes', inject(function($locale) {
+    spyOn(dateParser, 'init').and.callThrough();
+    expect($locale.id).toBe('en-us');
+
+    $locale.id = 'en-uk';
+
+    dateParser.parse('22.March.15.22', 'd.MMMM.yy.s');
+
+    expect(dateParser.init).toHaveBeenCalled();
+  }));
+});
+
+/* Deprecation tests below */
+
+describe('date parser deprecation', function() {
+  beforeEach(module('ui.bootstrap.dateparser'));
+
+  it('should suppress warning', function() {
+    module(function($provide) {
+      $provide.value('$dateParserSuppressWarning', true);
+    });
+
+    inject(function($log, dateParser) {
+      spyOn($log, 'warn');
+
+      dateParser.parse('01.10.2015', 'dd.MM.yyyy');
+
+      expect($log.warn.calls.count()).toBe(0);
+    });
+  });
+
+  it('should give warning by default', inject(function($log) {
+    spyOn($log, 'warn');
+
+    inject(function(dateParser) {
+      dateParser.parse('01.10.2015', 'dd.MM.yyyy');
+
+      expect($log.warn.calls.count()).toBe(1);
+      expect($log.warn.calls.argsFor(0)).toEqual(['dateParser is now deprecated. Use uibDateParser instead.']);
+    });
+  }));
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/datepicker/datepicker.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/datepicker/datepicker.js
new file mode 100644
index 0000000..7cf8759
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/datepicker/datepicker.js
@@ -0,0 +1,1186 @@
+angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.position'])
+
+.value('$datepickerSuppressError', false)
+
+.constant('uibDatepickerConfig', {
+  formatDay: 'dd',
+  formatMonth: 'MMMM',
+  formatYear: 'yyyy',
+  formatDayHeader: 'EEE',
+  formatDayTitle: 'MMMM yyyy',
+  formatMonthTitle: 'yyyy',
+  datepickerMode: 'day',
+  minMode: 'day',
+  maxMode: 'year',
+  showWeeks: true,
+  startingDay: 0,
+  yearRange: 20,
+  minDate: null,
+  maxDate: null,
+  shortcutPropagation: false
+})
+
+.controller('UibDatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerSuppressError', function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig, $datepickerSuppressError) {
+  var self = this,
+      ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl;
+
+  // Modes chain
+  this.modes = ['day', 'month', 'year'];
+
+  // Configuration attributes
+  angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle',
+                   'showWeeks', 'startingDay', 'yearRange', 'shortcutPropagation'], function(key, index) {
+    self[key] = angular.isDefined($attrs[key]) ? (index < 6 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key];
+  });
+
+  // Watchable date attributes
+  angular.forEach(['minDate', 'maxDate'], function(key) {
+    if ($attrs[key]) {
+      $scope.$parent.$watch($parse($attrs[key]), function(value) {
+        self[key] = value ? new Date(value) : null;
+        self.refreshView();
+      });
+    } else {
+      self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null;
+    }
+  });
+
+  angular.forEach(['minMode', 'maxMode'], function(key) {
+    if ($attrs[key]) {
+      $scope.$parent.$watch($parse($attrs[key]), function(value) {
+        self[key] = angular.isDefined(value) ? value : $attrs[key];
+        $scope[key] = self[key];
+        if ((key == 'minMode' && self.modes.indexOf($scope.datepickerMode) < self.modes.indexOf(self[key])) || (key == 'maxMode' && self.modes.indexOf($scope.datepickerMode) > self.modes.indexOf(self[key]))) {
+          $scope.datepickerMode = self[key];
+        }
+      });
+    } else {
+      self[key] = datepickerConfig[key] || null;
+      $scope[key] = self[key];
+    }
+  });
+
+  $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode;
+  $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
+
+  if (angular.isDefined($attrs.initDate)) {
+    this.activeDate = $scope.$parent.$eval($attrs.initDate) || new Date();
+    $scope.$parent.$watch($attrs.initDate, function(initDate) {
+      if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
+        self.activeDate = initDate;
+        self.refreshView();
+      }
+    });
+  } else {
+    this.activeDate = new Date();
+  }
+
+  $scope.isActive = function(dateObject) {
+    if (self.compare(dateObject.date, self.activeDate) === 0) {
+      $scope.activeDateId = dateObject.uid;
+      return true;
+    }
+    return false;
+  };
+
+  this.init = function(ngModelCtrl_) {
+    ngModelCtrl = ngModelCtrl_;
+
+    ngModelCtrl.$render = function() {
+      self.render();
+    };
+  };
+
+  this.render = function() {
+    if (ngModelCtrl.$viewValue) {
+      var date = new Date(ngModelCtrl.$viewValue),
+          isValid = !isNaN(date);
+
+      if (isValid) {
+        this.activeDate = date;
+      } else if (!$datepickerSuppressError) {
+        $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
+      }
+    }
+    this.refreshView();
+  };
+
+  this.refreshView = function() {
+    if (this.element) {
+      this._refreshView();
+
+      var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
+      ngModelCtrl.$setValidity('dateDisabled', !date || (this.element && !this.isDisabled(date)));
+    }
+  };
+
+  this.createDateObject = function(date, format) {
+    var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
+    return {
+      date: date,
+      label: dateFilter(date, format),
+      selected: model && this.compare(date, model) === 0,
+      disabled: this.isDisabled(date),
+      current: this.compare(date, new Date()) === 0,
+      customClass: this.customClass(date)
+    };
+  };
+
+  this.isDisabled = function(date) {
+    return ((this.minDate && this.compare(date, this.minDate) < 0) || (this.maxDate && this.compare(date, this.maxDate) > 0) || ($attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode})));
+  };
+
+  this.customClass = function(date) {
+    return $scope.customClass({date: date, mode: $scope.datepickerMode});
+  };
+
+  // Split array into smaller arrays
+  this.split = function(arr, size) {
+    var arrays = [];
+    while (arr.length > 0) {
+      arrays.push(arr.splice(0, size));
+    }
+    return arrays;
+  };
+
+  $scope.select = function(date) {
+    if ($scope.datepickerMode === self.minMode) {
+      var dt = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : new Date(0, 0, 0, 0, 0, 0, 0);
+      dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
+      ngModelCtrl.$setViewValue(dt);
+      ngModelCtrl.$render();
+    } else {
+      self.activeDate = date;
+      $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) - 1];
+    }
+  };
+
+  $scope.move = function(direction) {
+    var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
+        month = self.activeDate.getMonth() + direction * (self.step.months || 0);
+    self.activeDate.setFullYear(year, month, 1);
+    self.refreshView();
+  };
+
+  $scope.toggleMode = function(direction) {
+    direction = direction || 1;
+
+    if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) {
+      return;
+    }
+
+    $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) + direction];
+  };
+
+  // Key event mapper
+  $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' };
+
+  var focusElement = function() {
+    self.element[0].focus();
+  };
+
+  // Listen for focus requests from popup directive
+  $scope.$on('uib:datepicker.focus', focusElement);
+
+  $scope.keydown = function(evt) {
+    var key = $scope.keys[evt.which];
+
+    if (!key || evt.shiftKey || evt.altKey) {
+      return;
+    }
+
+    evt.preventDefault();
+    if (!self.shortcutPropagation) {
+      evt.stopPropagation();
+    }
+
+    if (key === 'enter' || key === 'space') {
+      if (self.isDisabled(self.activeDate)) {
+        return; // do nothing
+      }
+      $scope.select(self.activeDate);
+    } else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
+      $scope.toggleMode(key === 'up' ? 1 : -1);
+    } else {
+      self.handleKeyDown(key, evt);
+      self.refreshView();
+    }
+  };
+}])
+
+.controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
+  var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
+
+  this.step = { months: 1 };
+  this.element = $element;
+  function getDaysInMonth(year, month) {
+    return ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0))) ? 29 : DAYS_IN_MONTH[month];
+  }
+
+  this.init = function(ctrl) {
+    angular.extend(ctrl, this);
+    scope.showWeeks = ctrl.showWeeks;
+    ctrl.refreshView();
+  };
+
+  this.getDates = function(startDate, n) {
+    var dates = new Array(n), current = new Date(startDate), i = 0, date;
+    while (i < n) {
+      date = new Date(current);
+      dates[i++] = date;
+      current.setDate(current.getDate() + 1);
+    }
+    return dates;
+  };
+
+  this._refreshView = function() {
+    var year = this.activeDate.getFullYear(),
+      month = this.activeDate.getMonth(),
+      firstDayOfMonth = new Date(this.activeDate);
+
+    firstDayOfMonth.setFullYear(year, month, 1);
+
+    var difference = this.startingDay - firstDayOfMonth.getDay(),
+      numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference,
+      firstDate = new Date(firstDayOfMonth);
+
+    if (numDisplayedFromPreviousMonth > 0) {
+      firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
+    }
+
+    // 42 is the number of days on a six-month calendar
+    var days = this.getDates(firstDate, 42);
+    for (var i = 0; i < 42; i ++) {
+      days[i] = angular.extend(this.createDateObject(days[i], this.formatDay), {
+        secondary: days[i].getMonth() !== month,
+        uid: scope.uniqueId + '-' + i
+      });
+    }
+
+    scope.labels = new Array(7);
+    for (var j = 0; j < 7; j++) {
+      scope.labels[j] = {
+        abbr: dateFilter(days[j].date, this.formatDayHeader),
+        full: dateFilter(days[j].date, 'EEEE')
+      };
+    }
+
+    scope.title = dateFilter(this.activeDate, this.formatDayTitle);
+    scope.rows = this.split(days, 7);
+
+    if (scope.showWeeks) {
+      scope.weekNumbers = [];
+      var thursdayIndex = (4 + 7 - this.startingDay) % 7,
+          numWeeks = scope.rows.length;
+      for (var curWeek = 0; curWeek < numWeeks; curWeek++) {
+        scope.weekNumbers.push(
+          getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date));
+      }
+    }
+  };
+
+  this.compare = function(date1, date2) {
+    return (new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()));
+  };
+
+  function getISO8601WeekNumber(date) {
+    var checkDate = new Date(date);
+    checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
+    var time = checkDate.getTime();
+    checkDate.setMonth(0); // Compare with Jan 1
+    checkDate.setDate(1);
+    return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
+  }
+
+  this.handleKeyDown = function(key, evt) {
+    var date = this.activeDate.getDate();
+
+    if (key === 'left') {
+      date = date - 1;   // up
+    } else if (key === 'up') {
+      date = date - 7;   // down
+    } else if (key === 'right') {
+      date = date + 1;   // down
+    } else if (key === 'down') {
+      date = date + 7;
+    } else if (key === 'pageup' || key === 'pagedown') {
+      var month = this.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1);
+      this.activeDate.setMonth(month, 1);
+      date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date);
+    } else if (key === 'home') {
+      date = 1;
+    } else if (key === 'end') {
+      date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth());
+    }
+    this.activeDate.setDate(date);
+  };
+}])
+
+.controller('UibMonthpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
+  this.step = { years: 1 };
+  this.element = $element;
+
+  this.init = function(ctrl) {
+    angular.extend(ctrl, this);
+    ctrl.refreshView();
+  };
+
+  this._refreshView = function() {
+    var months = new Array(12),
+        year = this.activeDate.getFullYear(),
+        date;
+
+    for (var i = 0; i < 12; i++) {
+      date = new Date(this.activeDate);
+      date.setFullYear(year, i, 1);
+      months[i] = angular.extend(this.createDateObject(date, this.formatMonth), {
+        uid: scope.uniqueId + '-' + i
+      });
+    }
+
+    scope.title = dateFilter(this.activeDate, this.formatMonthTitle);
+    scope.rows = this.split(months, 3);
+  };
+
+  this.compare = function(date1, date2) {
+    return new Date(date1.getFullYear(), date1.getMonth()) - new Date(date2.getFullYear(), date2.getMonth());
+  };
+
+  this.handleKeyDown = function(key, evt) {
+    var date = this.activeDate.getMonth();
+
+    if (key === 'left') {
+      date = date - 1;   // up
+    } else if (key === 'up') {
+      date = date - 3;   // down
+    } else if (key === 'right') {
+      date = date + 1;   // down
+    } else if (key === 'down') {
+      date = date + 3;
+    } else if (key === 'pageup' || key === 'pagedown') {
+      var year = this.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1);
+      this.activeDate.setFullYear(year);
+    } else if (key === 'home') {
+      date = 0;
+    } else if (key === 'end') {
+      date = 11;
+    }
+    this.activeDate.setMonth(date);
+  };
+}])
+
+.controller('UibYearpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
+  var range;
+  this.element = $element;
+
+  function getStartingYear(year) {
+    return parseInt((year - 1) / range, 10) * range + 1;
+  }
+
+  this.yearpickerInit = function() {
+    range = this.yearRange;
+    this.step = { years: range };
+  };
+
+  this._refreshView = function() {
+    var years = new Array(range), date;
+
+    for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()); i < range; i++) {
+      date = new Date(this.activeDate);
+      date.setFullYear(start + i, 0, 1);
+      years[i] = angular.extend(this.createDateObject(date, this.formatYear), {
+        uid: scope.uniqueId + '-' + i
+      });
+    }
+
+    scope.title = [years[0].label, years[range - 1].label].join(' - ');
+    scope.rows = this.split(years, 5);
+  };
+
+  this.compare = function(date1, date2) {
+    return date1.getFullYear() - date2.getFullYear();
+  };
+
+  this.handleKeyDown = function(key, evt) {
+    var date = this.activeDate.getFullYear();
+
+    if (key === 'left') {
+      date = date - 1;   // up
+    } else if (key === 'up') {
+      date = date - 5;   // down
+    } else if (key === 'right') {
+      date = date + 1;   // down
+    } else if (key === 'down') {
+      date = date + 5;
+    } else if (key === 'pageup' || key === 'pagedown') {
+      date += (key === 'pageup' ? - 1 : 1) * this.step.years;
+    } else if (key === 'home') {
+      date = getStartingYear(this.activeDate.getFullYear());
+    } else if (key === 'end') {
+      date = getStartingYear(this.activeDate.getFullYear()) + range - 1;
+    }
+    this.activeDate.setFullYear(date);
+  };
+}])
+
+.directive('uibDatepicker', function() {
+  return {
+    replace: true,
+    templateUrl: function(element, attrs) {
+      return attrs.templateUrl || 'template/datepicker/datepicker.html';
+    },
+    scope: {
+      datepickerMode: '=?',
+      dateDisabled: '&',
+      customClass: '&',
+      shortcutPropagation: '&?'
+    },
+    require: ['uibDatepicker', '^ngModel'],
+    controller: 'UibDatepickerController',
+    controllerAs: 'datepicker',
+    link: function(scope, element, attrs, ctrls) {
+      var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
+
+      datepickerCtrl.init(ngModelCtrl);
+    }
+  };
+})
+
+.directive('uibDaypicker', function() {
+  return {
+    replace: true,
+    templateUrl: function(element, attrs) {
+      return attrs.templateUrl || 'template/datepicker/day.html';
+    },
+    require: ['^?uibDatepicker', 'uibDaypicker', '^?datepicker'],
+    controller: 'UibDaypickerController',
+    link: function(scope, element, attrs, ctrls) {
+      var datepickerCtrl = ctrls[0] || ctrls[2],
+        daypickerCtrl = ctrls[1];
+
+      daypickerCtrl.init(datepickerCtrl);
+    }
+  };
+})
+
+.directive('uibMonthpicker', function() {
+  return {
+    replace: true,
+    templateUrl: function(element, attrs) {
+      return attrs.templateUrl || 'template/datepicker/month.html';
+    },
+    require: ['^?uibDatepicker', 'uibMonthpicker', '^?datepicker'],
+    controller: 'UibMonthpickerController',
+    link: function(scope, element, attrs, ctrls) {
+      var datepickerCtrl = ctrls[0] || ctrls[2],
+        monthpickerCtrl = ctrls[1];
+
+      monthpickerCtrl.init(datepickerCtrl);
+    }
+  };
+})
+
+.directive('uibYearpicker', function() {
+  return {
+    replace: true,
+    templateUrl: function(element, attrs) {
+      return attrs.templateUrl || 'template/datepicker/year.html';
+    },
+    require: ['^?uibDatepicker', 'uibYearpicker', '^?datepicker'],
+    controller: 'UibYearpickerController',
+    link: function(scope, element, attrs, ctrls) {
+      var ctrl = ctrls[0] || ctrls[2];
+      angular.extend(ctrl, ctrls[1]);
+      ctrl.yearpickerInit();
+
+      ctrl.refreshView();
+    }
+  };
+})
+
+.constant('uibDatepickerPopupConfig', {
+  datepickerPopup: 'yyyy-MM-dd',
+  datepickerPopupTemplateUrl: 'template/datepicker/popup.html',
+  datepickerTemplateUrl: 'template/datepicker/datepicker.html',
+  html5Types: {
+    date: 'yyyy-MM-dd',
+    'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss',
+    'month': 'yyyy-MM'
+  },
+  currentText: 'Today',
+  clearText: 'Clear',
+  closeText: 'Done',
+  closeOnDateSelection: true,
+  appendToBody: false,
+  showButtonBar: true,
+  onOpenFocus: true
+})
+
+.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout',
+function(scope, element, attrs, $compile, $parse, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout) {
+  var self = this;
+  var cache = {},
+    isHtml5DateInput = false;
+  var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,
+    datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl,
+    ngModel, $popup;
+
+  scope.watchData = {};
+
+  this.init = function(_ngModel_) {
+    ngModel = _ngModel_;
+    closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection;
+    appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody;
+    onOpenFocus = angular.isDefined(attrs.onOpenFocus) ? scope.$parent.$eval(attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus;
+    datepickerPopupTemplateUrl = angular.isDefined(attrs.datepickerPopupTemplateUrl) ? attrs.datepickerPopupTemplateUrl : datepickerPopupConfig.datepickerPopupTemplateUrl;
+    datepickerTemplateUrl = angular.isDefined(attrs.datepickerTemplateUrl) ? attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl;
+
+    scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar;
+
+    if (datepickerPopupConfig.html5Types[attrs.type]) {
+      dateFormat = datepickerPopupConfig.html5Types[attrs.type];
+      isHtml5DateInput = true;
+    } else {
+      dateFormat = attrs.datepickerPopup || attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup;
+      attrs.$observe('uibDatepickerPopup', function(value, oldValue) {
+          var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
+          // Invalidate the $modelValue to ensure that formatters re-run
+          // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
+          if (newDateFormat !== dateFormat) {
+            dateFormat = newDateFormat;
+            ngModel.$modelValue = null;
+
+            if (!dateFormat) {
+              throw new Error('uibDatepickerPopup must have a date format specified.');
+            }
+          }
+      });
+    }
+
+    if (!dateFormat) {
+      throw new Error('uibDatepickerPopup must have a date format specified.');
+    }
+
+    if (isHtml5DateInput && attrs.datepickerPopup) {
+      throw new Error('HTML5 date input types do not support custom formats.');
+    }
+
+    // popup element used to display calendar
+    popupEl = angular.element('<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>');
+    popupEl.attr({
+      'ng-model': 'date',
+      'ng-change': 'dateSelection(date)',
+      'template-url': datepickerPopupTemplateUrl
+    });
+
+    // datepicker element
+    datepickerEl = angular.element(popupEl.children()[0]);
+    datepickerEl.attr('template-url', datepickerTemplateUrl);
+
+    if (isHtml5DateInput) {
+      if (attrs.type === 'month') {
+        datepickerEl.attr('datepicker-mode', '"month"');
+        datepickerEl.attr('min-mode', 'month');
+      }
+    }
+
+    if (attrs.datepickerOptions) {
+      var options = scope.$parent.$eval(attrs.datepickerOptions);
+      if (options && options.initDate) {
+        scope.initDate = options.initDate;
+        datepickerEl.attr('init-date', 'initDate');
+        delete options.initDate;
+      }
+      angular.forEach(options, function(value, option) {
+        datepickerEl.attr(cameltoDash(option), value);
+      });
+    }
+
+    angular.forEach(['minMode', 'maxMode', 'minDate', 'maxDate', 'datepickerMode', 'initDate', 'shortcutPropagation'], function(key) {
+      if (attrs[key]) {
+        var getAttribute = $parse(attrs[key]);
+        scope.$parent.$watch(getAttribute, function(value) {
+          scope.watchData[key] = value;
+          if (key === 'minDate' || key === 'maxDate') {
+            cache[key] = new Date(value);
+          }
+        });
+        datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
+
+        // Propagate changes from datepicker to outside
+        if (key === 'datepickerMode') {
+          var setAttribute = getAttribute.assign;
+          scope.$watch('watchData.' + key, function(value, oldvalue) {
+            if (angular.isFunction(setAttribute) && value !== oldvalue) {
+              setAttribute(scope.$parent, value);
+            }
+          });
+        }
+      }
+    });
+    if (attrs.dateDisabled) {
+      datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })');
+    }
+
+    if (attrs.showWeeks) {
+      datepickerEl.attr('show-weeks', attrs.showWeeks);
+    }
+
+    if (attrs.customClass) {
+      datepickerEl.attr('custom-class', 'customClass({ date: date, mode: mode })');
+    }
+
+    if (!isHtml5DateInput) {
+      // Internal API to maintain the correct ng-invalid-[key] class
+      ngModel.$$parserName = 'date';
+      ngModel.$validators.date = validator;
+      ngModel.$parsers.unshift(parseDate);
+      ngModel.$formatters.push(function(value) {
+        scope.date = value;
+        return ngModel.$isEmpty(value) ? value : dateFilter(value, dateFormat);
+      });
+    } else {
+      ngModel.$formatters.push(function(value) {
+        scope.date = value;
+        return value;
+      });
+    }
+
+    // Detect changes in the view from the text box
+    ngModel.$viewChangeListeners.push(function() {
+      scope.date = dateParser.parse(ngModel.$viewValue, dateFormat, scope.date);
+    });
+
+    element.bind('keydown', inputKeydownBind);
+
+    $popup = $compile(popupEl)(scope);
+    // Prevent jQuery cache memory leak (template is now redundant after linking)
+    popupEl.remove();
+
+    if (appendToBody) {
+      $document.find('body').append($popup);
+    } else {
+      element.after($popup);
+    }
+
+    scope.$on('$destroy', function() {
+      if (scope.isOpen === true) {
+        if (!$rootScope.$$phase) {
+          scope.$apply(function() {
+            scope.isOpen = false;
+          });
+        }
+      }
+
+      $popup.remove();
+      element.unbind('keydown', inputKeydownBind);
+      $document.unbind('click', documentClickBind);
+    });
+  };
+
+  scope.getText = function(key) {
+    return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
+  };
+
+  scope.isDisabled = function(date) {
+    if (date === 'today') {
+      date = new Date();
+    }
+
+    return ((scope.watchData.minDate && scope.compare(date, cache.minDate) < 0) ||
+      (scope.watchData.maxDate && scope.compare(date, cache.maxDate) > 0));
+  };
+
+  scope.compare = function(date1, date2) {
+    return (new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()));
+  };
+
+  // Inner change
+  scope.dateSelection = function(dt) {
+    if (angular.isDefined(dt)) {
+      scope.date = dt;
+    }
+    var date = scope.date ? dateFilter(scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function
+    element.val(date);
+    ngModel.$setViewValue(date);
+
+    if (closeOnDateSelection) {
+      scope.isOpen = false;
+      element[0].focus();
+    }
+  };
+
+  scope.keydown = function(evt) {
+    if (evt.which === 27) {
+      scope.isOpen = false;
+      element[0].focus();
+    }
+  };
+
+  scope.select = function(date) {
+    if (date === 'today') {
+      var today = new Date();
+      if (angular.isDate(scope.date)) {
+        date = new Date(scope.date);
+        date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
+      } else {
+        date = new Date(today.setHours(0, 0, 0, 0));
+      }
+    }
+    scope.dateSelection(date);
+  };
+
+  scope.close = function() {
+    scope.isOpen = false;
+    element[0].focus();
+  };
+
+  scope.$watch('isOpen', function(value) {
+    if (value) {
+      scope.position = appendToBody ? $position.offset(element) : $position.position(element);
+      scope.position.top = scope.position.top + element.prop('offsetHeight');
+
+      $timeout(function() {
+        if (onOpenFocus) {
+          scope.$broadcast('uib:datepicker.focus');
+        }
+        $document.bind('click', documentClickBind);
+      }, 0, false);
+    } else {
+      $document.unbind('click', documentClickBind);
+    }
+  });
+
+  function cameltoDash(string) {
+    return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });
+  }
+
+  function parseDate(viewValue) {
+    if (angular.isNumber(viewValue)) {
+      // presumably timestamp to date object
+      viewValue = new Date(viewValue);
+    }
+
+    if (!viewValue) {
+      return null;
+    } else if (angular.isDate(viewValue) && !isNaN(viewValue)) {
+      return viewValue;
+    } else if (angular.isString(viewValue)) {
+      var date = dateParser.parse(viewValue, dateFormat, scope.date);
+      if (isNaN(date)) {
+        return undefined;
+      } else {
+        return date;
+      }
+    } else {
+      return undefined;
+    }
+  }
+
+  function validator(modelValue, viewValue) {
+    var value = modelValue || viewValue;
+
+    if (!attrs.ngRequired && !value) {
+      return true;
+    }
+
+    if (angular.isNumber(value)) {
+      value = new Date(value);
+    }
+    if (!value) {
+      return true;
+    } else if (angular.isDate(value) && !isNaN(value)) {
+      return true;
+    } else if (angular.isString(value)) {
+      var date = dateParser.parse(value, dateFormat);
+      return !isNaN(date);
+    } else {
+      return false;
+    }
+  }
+
+  function documentClickBind(event) {
+    var popup = $popup[0];
+    var dpContainsTarget = element[0].contains(event.target);
+    // The popup node may not be an element node
+    // In some browsers (IE) only element nodes have the 'contains' function
+    var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target);
+    if (scope.isOpen && !(dpContainsTarget || popupContainsTarget)) {
+      scope.$apply(function() {
+        scope.isOpen = false;
+      });
+    }
+  }
+
+  function inputKeydownBind(evt) {
+    if (evt.which === 27 && scope.isOpen) {
+      evt.preventDefault();
+      evt.stopPropagation();
+      scope.$apply(function() {
+        scope.isOpen = false;
+      });
+      element[0].focus();
+    } else if (evt.which === 40 && !scope.isOpen) {
+      evt.preventDefault();
+      evt.stopPropagation();
+      scope.$apply(function() {
+        scope.isOpen = true;
+      });
+    }
+  }
+}])
+
+.directive('uibDatepickerPopup', function() {
+  return {
+    require: ['ngModel', 'uibDatepickerPopup'],
+    controller: 'UibDatepickerPopupController',
+    scope: {
+      isOpen: '=?',
+      currentText: '@',
+      clearText: '@',
+      closeText: '@',
+      dateDisabled: '&',
+      customClass: '&'
+    },
+    link: function(scope, element, attrs, ctrls) {
+      var ngModel = ctrls[0],
+        ctrl = ctrls[1];
+
+      ctrl.init(ngModel);
+    }
+  };
+})
+
+.directive('uibDatepickerPopupWrap', function() {
+  return {
+    replace: true,
+    transclude: true,
+    templateUrl: function(element, attrs) {
+      return attrs.templateUrl || 'template/datepicker/popup.html';
+    }
+  };
+});
+
+/* Deprecated datepicker below */
+
+angular.module('ui.bootstrap.datepicker')
+
+.value('$datepickerSuppressWarning', false)
+
+.controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerSuppressError', '$datepickerSuppressWarning', function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig, $datepickerSuppressError, $datepickerSuppressWarning) {
+  if (!$datepickerSuppressWarning) {
+    $log.warn('DatepickerController is now deprecated. Use UibDatepickerController instead.');
+  }
+
+  var self = this,
+    ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl;
+
+  this.modes = ['day', 'month', 'year'];
+
+  angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle',
+    'showWeeks', 'startingDay', 'yearRange', 'shortcutPropagation'], function(key, index) {
+    self[key] = angular.isDefined($attrs[key]) ? (index < 6 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key];
+  });
+
+  angular.forEach(['minDate', 'maxDate'], function(key) {
+    if ($attrs[key]) {
+      $scope.$parent.$watch($parse($attrs[key]), function(value) {
+        self[key] = value ? new Date(value) : null;
+        self.refreshView();
+      });
+    } else {
+      self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null;
+    }
+  });
+
+  angular.forEach(['minMode', 'maxMode'], function(key) {
+    if ($attrs[key]) {
+      $scope.$parent.$watch($parse($attrs[key]), function(value) {
+        self[key] = angular.isDefined(value) ? value : $attrs[key];
+        $scope[key] = self[key];
+        if ((key == 'minMode' && self.modes.indexOf($scope.datepickerMode) < self.modes.indexOf(self[key])) || (key == 'maxMode' && self.modes.indexOf($scope.datepickerMode) > self.modes.indexOf(self[key]))) {
+          $scope.datepickerMode = self[key];
+        }
+      });
+    } else {
+      self[key] = datepickerConfig[key] || null;
+      $scope[key] = self[key];
+    }
+  });
+
+  $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode;
+  $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
+
+  if (angular.isDefined($attrs.initDate)) {
+    this.activeDate = $scope.$parent.$eval($attrs.initDate) || new Date();
+    $scope.$parent.$watch($attrs.initDate, function(initDate) {
+      if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
+        self.activeDate = initDate;
+        self.refreshView();
+      }
+    });
+  } else {
+    this.activeDate = new Date();
+  }
+
+  $scope.isActive = function(dateObject) {
+    if (self.compare(dateObject.date, self.activeDate) === 0) {
+      $scope.activeDateId = dateObject.uid;
+      return true;
+    }
+    return false;
+  };
+
+  this.init = function(ngModelCtrl_) {
+    ngModelCtrl = ngModelCtrl_;
+
+    ngModelCtrl.$render = function() {
+      self.render();
+    };
+  };
+
+  this.render = function() {
+    if (ngModelCtrl.$viewValue) {
+      var date = new Date(ngModelCtrl.$viewValue),
+        isValid = !isNaN(date);
+
+      if (isValid) {
+        this.activeDate = date;
+      } else if (!$datepickerSuppressError) {
+        $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
+      }
+    }
+    this.refreshView();
+  };
+
+  this.refreshView = function() {
+    if (this.element) {
+      this._refreshView();
+
+      var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
+      ngModelCtrl.$setValidity('dateDisabled', !date || (this.element && !this.isDisabled(date)));
+    }
+  };
+
+  this.createDateObject = function(date, format) {
+    var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
+    return {
+      date: date,
+      label: dateFilter(date, format),
+      selected: model && this.compare(date, model) === 0,
+      disabled: this.isDisabled(date),
+      current: this.compare(date, new Date()) === 0,
+      customClass: this.customClass(date)
+    };
+  };
+
+  this.isDisabled = function(date) {
+    return ((this.minDate && this.compare(date, this.minDate) < 0) || (this.maxDate && this.compare(date, this.maxDate) > 0) || ($attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode})));
+  };
+
+  this.customClass = function(date) {
+    return $scope.customClass({date: date, mode: $scope.datepickerMode});
+  };
+
+  // Split array into smaller arrays
+  this.split = function(arr, size) {
+    var arrays = [];
+    while (arr.length > 0) {
+      arrays.push(arr.splice(0, size));
+    }
+    return arrays;
+  };
+
+  this.fixTimeZone = function(date) {
+    var hours = date.getHours();
+    date.setHours(hours === 23 ? hours + 2 : 0);
+  };
+
+  $scope.select = function(date) {
+    if ($scope.datepickerMode === self.minMode) {
+      var dt = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : new Date(0, 0, 0, 0, 0, 0, 0);
+      dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
+      ngModelCtrl.$setViewValue(dt);
+      ngModelCtrl.$render();
+    } else {
+      self.activeDate = date;
+      $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) - 1];
+    }
+  };
+
+  $scope.move = function(direction) {
+    var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
+      month = self.activeDate.getMonth() + direction * (self.step.months || 0);
+    self.activeDate.setFullYear(year, month, 1);
+    self.refreshView();
+  };
+
+  $scope.toggleMode = function(direction) {
+    direction = direction || 1;
+
+    if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) {
+      return;
+    }
+
+    $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) + direction];
+  };
+
+  // Key event mapper
+  $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' };
+
+  var focusElement = function() {
+    self.element[0].focus();
+  };
+
+  $scope.$on('uib:datepicker.focus', focusElement);
+
+  $scope.keydown = function(evt) {
+    var key = $scope.keys[evt.which];
+
+    if (!key || evt.shiftKey || evt.altKey) {
+      return;
+    }
+
+    evt.preventDefault();
+    if (!self.shortcutPropagation) {
+      evt.stopPropagation();
+    }
+
+    if (key === 'enter' || key === 'space') {
+      if (self.isDisabled(self.activeDate)) {
+        return; // do nothing
+      }
+      $scope.select(self.activeDate);
+    } else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
+      $scope.toggleMode(key === 'up' ? 1 : -1);
+    } else {
+      self.handleKeyDown(key, evt);
+      self.refreshView();
+    }
+  };
+}])
+
+.directive('datepicker', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) {
+  return {
+    replace: true,
+    templateUrl: function(element, attrs) {
+      return attrs.templateUrl || 'template/datepicker/datepicker.html';
+    },
+    scope: {
+      datepickerMode: '=?',
+      dateDisabled: '&',
+      customClass: '&',
+      shortcutPropagation: '&?'
+    },
+    require: ['datepicker', '^ngModel'],
+    controller: 'DatepickerController',
+    controllerAs: 'datepicker',
+    link: function(scope, element, attrs, ctrls) {
+      if (!$datepickerSuppressWarning) {
+        $log.warn('datepicker is now deprecated. Use uib-datepicker instead.');
+      }
+
+      var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
+
+      datepickerCtrl.init(ngModelCtrl);
+    }
+  };
+}])
+
+.directive('daypicker', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) {
+  return {
+    replace: true,
+    templateUrl: 'template/datepicker/day.html',
+    require: ['^datepicker', 'daypicker'],
+    controller: 'UibDaypickerController',
+    link: function(scope, element, attrs, ctrls) {
+      if (!$datepickerSuppressWarning) {
+        $log.warn('daypicker is now deprecated. Use uib-daypicker instead.');
+      }
+
+      var datepickerCtrl = ctrls[0],
+        daypickerCtrl = ctrls[1];
+
+      daypickerCtrl.init(datepickerCtrl);
+    }
+  };
+}])
+
+.directive('monthpicker', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) {
+  return {
+    replace: true,
+    templateUrl: 'template/datepicker/month.html',
+    require: ['^datepicker', 'monthpicker'],
+    controller: 'UibMonthpickerController',
+    link: function(scope, element, attrs, ctrls) {
+      if (!$datepickerSuppressWarning) {
+        $log.warn('monthpicker is now deprecated. Use uib-monthpicker instead.');
+      }
+
+      var datepickerCtrl = ctrls[0],
+        monthpickerCtrl = ctrls[1];
+
+      monthpickerCtrl.init(datepickerCtrl);
+    }
+  };
+}])
+
+.directive('yearpicker', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) {
+  return {
+    replace: true,
+    templateUrl: 'template/datepicker/year.html',
+    require: ['^datepicker', 'yearpicker'],
+    controller: 'UibYearpickerController',
+    link: function(scope, element, attrs, ctrls) {
+      if (!$datepickerSuppressWarning) {
+        $log.warn('yearpicker is now deprecated. Use uib-yearpicker instead.');
+      }
+
+      var ctrl = ctrls[0];
+      angular.extend(ctrl, ctrls[1]);
+      ctrl.yearpickerInit();
+
+      ctrl.refreshView();
+    }
+  };
+}])
+
+.directive('datepickerPopup', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) {
+  return {
+    require: ['ngModel', 'datepickerPopup'],
+    controller: 'UibDatepickerPopupController',
+    scope: {
+      isOpen: '=?',
+      currentText: '@',
+      clearText: '@',
+      closeText: '@',
+      dateDisabled: '&',
+      customClass: '&'
+    },
+    link: function(scope, element, attrs, ctrls) {
+      if (!$datepickerSuppressWarning) {
+        $log.warn('datepicker-popup is now deprecated. Use uib-datepicker-popup instead.');
+      }
+
+      var ngModel = ctrls[0],
+        ctrl = ctrls[1];
+
+      ctrl.init(ngModel);
+    }
+  };
+}])
+
+.directive('datepickerPopupWrap', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) {
+  return {
+    replace: true,
+    transclude: true,
+    templateUrl: function(element, attrs) {
+      return attrs.templateUrl || 'template/datepicker/popup.html';
+    },
+    link: function() {
+      if (!$datepickerSuppressWarning) {
+        $log.warn('datepicker-popup-wrap is now deprecated. Use uib-datepicker-popup-wrap instead.');
+      }
+    }
+  };
+}]);
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/datepicker/docs/demo.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/datepicker/docs/demo.html
new file mode 100644
index 0000000..8a057a3
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/datepicker/docs/demo.html
@@ -0,0 +1,52 @@
+<style>
+  .full button span {
+    background-color: limegreen;
+    border-radius: 32px;
+    color: black;
+  }
+  .partially button span {
+    background-color: orange;
+    border-radius: 32px;
+    color: black;
+  }
+</style>
+<div ng-controller="DatepickerDemoCtrl">
+    <pre>Selected date is: <em>{{dt | date:'fullDate' }}</em></pre>
+
+    <h4>Inline</h4>
+    <div style="display:inline-block; min-height:290px;">
+      <uib-datepicker ng-model="dt" min-date="minDate" show-weeks="true" class="well well-sm" custom-class="getDayClass(date, mode)"></uib-datepicker>
+    </div>
+
+    <h4>Popup</h4>
+    <div class="row">
+        <div class="col-md-6">
+            <p class="input-group">
+              <input type="text" class="form-control" uib-datepicker-popup="{{format}}" ng-model="dt" is-open="status.opened" min-date="minDate" max-date="maxDate" datepicker-options="dateOptions" date-disabled="disabled(date, mode)" ng-required="true" close-text="Close" />
+              <span class="input-group-btn">
+                <button type="button" class="btn btn-default" ng-click="open($event)"><i class="glyphicon glyphicon-calendar"></i></button>
+              </span>
+            </p>
+        </div>
+
+        <div class="col-md-6">
+            <p class="input-group">
+              <input type="date" class="form-control" uib-datepicker-popup ng-model="dt" is-open="status.opened" min-date="minDate" max-date="maxDate" datepicker-options="dateOptions" date-disabled="disabled(date, mode)" ng-required="true" close-text="Close" />
+              <span class="input-group-btn">
+                <button type="button" class="btn btn-default" ng-click="open($event)"><i class="glyphicon glyphicon-calendar"></i></button>
+              </span>
+            </p>
+        </div>
+    </div>
+    <div class="row">
+        <div class="col-md-6">
+            <label>Format:</label> <select class="form-control" ng-model="format" ng-options="f for f in formats"><option></option></select>
+        </div>
+    </div>
+
+    <hr />
+    <button type="button" class="btn btn-sm btn-info" ng-click="today()">Today</button>
+    <button type="button" class="btn btn-sm btn-default" ng-click="setDate(2009, 7, 24)">2009-08-24</button>
+    <button type="button" class="btn btn-sm btn-danger" ng-click="clear()">Clear</button>
+    <button type="button" class="btn btn-sm btn-default" ng-click="toggleMin()" uib-tooltip="After today restriction">Min date</button>
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/datepicker/docs/demo.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/datepicker/docs/demo.js
new file mode 100644
index 0000000..419cb3d
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/datepicker/docs/demo.js
@@ -0,0 +1,73 @@
+angular.module('ui.bootstrap.demo').controller('DatepickerDemoCtrl', function ($scope) {
+  $scope.today = function() {
+    $scope.dt = new Date();
+  };
+  $scope.today();
+
+  $scope.clear = function () {
+    $scope.dt = null;
+  };
+
+  // Disable weekend selection
+  $scope.disabled = function(date, mode) {
+    return ( mode === 'day' && ( date.getDay() === 0 || date.getDay() === 6 ) );
+  };
+
+  $scope.toggleMin = function() {
+    $scope.minDate = $scope.minDate ? null : new Date();
+  };
+  $scope.toggleMin();
+  $scope.maxDate = new Date(2020, 5, 22);
+
+  $scope.open = function($event) {
+    $scope.status.opened = true;
+  };
+
+  $scope.setDate = function(year, month, day) {
+    $scope.dt = new Date(year, month, day);
+  };
+
+  $scope.dateOptions = {
+    formatYear: 'yy',
+    startingDay: 1
+  };
+
+  $scope.formats = ['dd-MMMM-yyyy', 'yyyy/MM/dd', 'dd.MM.yyyy', 'shortDate'];
+  $scope.format = $scope.formats[0];
+
+  $scope.status = {
+    opened: false
+  };
+
+  var tomorrow = new Date();
+  tomorrow.setDate(tomorrow.getDate() + 1);
+  var afterTomorrow = new Date();
+  afterTomorrow.setDate(tomorrow.getDate() + 2);
+  $scope.events =
+    [
+      {
+        date: tomorrow,
+        status: 'full'
+      },
+      {
+        date: afterTomorrow,
+        status: 'partially'
+      }
+    ];
+
+  $scope.getDayClass = function(date, mode) {
+    if (mode === 'day') {
+      var dayToCheck = new Date(date).setHours(0,0,0,0);
+
+      for (var i=0;i<$scope.events.length;i++){
+        var currentDay = new Date($scope.events[i].date).setHours(0,0,0,0);
+
+        if (dayToCheck === currentDay) {
+          return $scope.events[i].status;
+        }
+      }
+    }
+
+    return '';
+  };
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/datepicker/docs/readme.md b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/datepicker/docs/readme.md
new file mode 100644
index 0000000..956032a
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/datepicker/docs/readme.md
@@ -0,0 +1,157 @@
+A clean, flexible, and fully customizable date picker.
+
+User can navigate through months and years.
+The datepicker shows dates that come from other than the main month being displayed. These other dates are also selectable.
+
+Everything is formatted using the [date filter](http://docs.angularjs.org/api/ng.filter:date) and thus is also localized.
+
+### Datepicker Settings ###
+
+All settings can be provided as attributes in the `uib-datepicker` or globally configured through the `uibDatepickerConfig`.
+
+ * `ng-model` <i class="glyphicon glyphicon-eye-open"></i>
+ 	:
+ 	The date object.
+
+ * `datepicker-mode` <i class="glyphicon glyphicon-eye-open"></i>
+   _(Defaults: 'day')_ :
+   Current mode of the datepicker _(day|month|year)_. Can be used to initialize datepicker to specific mode.
+
+ * `min-date` <i class="glyphicon glyphicon-eye-open"></i>
+ 	_(Default: null)_ :
+ 	Defines the minimum available date.
+
+ * `max-date` <i class="glyphicon glyphicon-eye-open"></i>
+ 	_(Default: null)_ :
+ 	Defines the maximum available date.
+
+ * `date-disabled (date, mode)`
+ 	_(Default: null)_ :
+ 	An optional expression to disable visible options based on passing date and current mode _(day|month|year)_.
+ 	
+ * `custom-class (date, mode)`
+ 	_(Default: null)_ :
+ 	An optional expression to add classes based on passing date and current mode _(day|month|year)_. 	
+
+ * `show-weeks`
+ 	_(Defaults: true)_ :
+ 	Whether to display week numbers.
+
+ * `starting-day`
+ 	_(Defaults: 0)_ :
+ 	Starting day of the week from 0-6 (0=Sunday, ..., 6=Saturday).
+
+ * `init-date`
+ 	:
+ 	The initial date view when no model value is specified.
+
+ * `min-mode`
+   _(Defaults: 'day')_ :
+   Set a lower limit for mode.
+
+ * `max-mode`
+   _(Defaults: 'year')_ :
+   Set an upper limit for mode.
+
+ * `format-day`
+ 	_(Default: 'dd')_ :
+ 	Format of day in month.
+
+ * `format-month`
+ 	_(Default: 'MMMM')_ :
+ 	Format of month in year.
+
+ * `format-year`
+ 	_(Default: 'yyyy')_ :
+ 	Format of year in year range.
+
+ * `format-day-header`
+ 	_(Default: 'EEE')_ :
+ 	Format of day in week header.
+
+ * `format-day-title`
+ 	_(Default: 'MMMM yyyy')_ :
+ 	Format of title when selecting day.
+
+ * `format-month-title`
+ 	_(Default: 'yyyy')_ :
+ 	Format of title when selecting month.
+
+ * `year-range`
+ 	_(Default: 20)_ :
+ 	Number of years displayed in year selection. 
+
+ * `shortcut-propagation`
+  _(Default: false)_ :
+  An option to disable or enable shortcut's event propagation.
+
+ * `template-url`
+  _(Default: 'template/datepicker/datepicker.html')_ :
+  Allows overriding of default template of the datepicker
+
+
+### Popup Settings ###
+
+Options for datepicker can be passed as JSON using the `datepicker-options` attribute.
+Specific settings for the `uib-datepicker-popup`, that can globally configured through the `uibDatepickerPopupConfig`, are:
+
+ * `uib-datepicker-popup`
+ 	_(Default: 'yyyy-MM-dd')_ :
+ 	The format for displayed dates.
+
+ * `show-button-bar`
+ 	_(Default: true)_ :
+ 	Whether to display a button bar underneath the datepicker.
+
+ * `current-text`
+ 	_(Default: 'Today')_ :
+ 	The text to display for the current day button.
+
+ * `clear-text`
+ 	_(Default: 'Clear')_ :
+ 	The text to display for the clear button.
+
+ * `close-text`
+ 	_(Default: 'Done')_ :
+ 	The text to display for the close button.
+
+ * `close-on-date-selection`
+ 	_(Default: true)_ :
+ 	Whether to close calendar when a date is chosen.
+
+ * `datepicker-popup-template-url`
+  _(Default: 'template/datepicker/popup.html')_ :
+  Allows overriding of default template of the popup
+
+ * `datepicker-template-url`
+  _(Default: 'template/datepicker/datepicker.html')_ :
+  Allows overriding of default template of the datepicker used in popup
+
+ * `datepicker-append-to-body`
+  _(Default: false)_:
+  Append the datepicker popup element to `body`, rather than inserting after `datepicker-popup`. For global configuration, use `uibDatepickerPopupConfig.appendToBody`.
+
+ * `is-open` <i class="glyphicon glyphicon-eye-open"></i>
+  _(Default: false)_:
+  Whether to show the datepicker.
+
+ * `on-open-focus`
+  _(Default: true)_:
+  Whether to focus the datepicker popup upon opening.
+
+### Keyboard Support ###
+
+Depending on datepicker's current mode, the date may refer either to day, month or year. Accordingly, the term view refers either to a month, year or year range.
+
+ * `Left`: Move focus to the previous date. Will move to the last date of the previous view, if the current date is the first date of a view.
+ * `Right`: Move focus to the next date. Will move to the first date of the following view, if the current date is the last date of a view.
+ * `Up`: Move focus to the same column of the previous row. Will wrap to the appropriate row in the previous view.
+ * `Down`: Move focus to the same column of the following row. Will wrap to the appropriate row in the following view.
+ * `PgUp`: Move focus to the same date of the previous view. If that date does not exist, focus is placed on the last date of the month.
+ * `PgDn`: Move focus to the same date of the following view. If that date does not exist, focus is placed on the last date of the month.
+ * `Home`: Move to the first date of the view.
+ * `End`: Move to the last date of the view.
+ * `Enter`/`Space`: Select date.
+ * `Ctrl`+`Up`: Move to an upper mode.
+ * `Ctrl`+`Down`: Move to a lower mode.
+ * `Esc`: Will close popup, and move focus to the input.
\ No newline at end of file
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/datepicker/test/datepicker.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/datepicker/test/datepicker.spec.js
new file mode 100644
index 0000000..6ab0ce8
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/datepicker/test/datepicker.spec.js
@@ -0,0 +1,2515 @@
+describe('datepicker directive', function() {
+  var $rootScope, $compile, $templateCache, element;
+  beforeEach(module('ui.bootstrap.datepicker'));
+  beforeEach(module('template/datepicker/datepicker.html'));
+  beforeEach(module('template/datepicker/day.html'));
+  beforeEach(module('template/datepicker/month.html'));
+  beforeEach(module('template/datepicker/year.html'));
+  beforeEach(module('template/datepicker/popup.html'));
+  beforeEach(module(function($compileProvider) {
+    $compileProvider.directive('dateModel', function() {
+      return {
+        restrict: 'A',
+        require: 'ngModel',
+        link: function(scope, element, attrs, modelController) {
+          modelController.$formatters.push(function(object) {
+            return new Date(object.date);
+          });
+
+          modelController.$parsers.push(function(date) {
+            return {
+              type: 'date',
+              date: date.toUTCString()
+            };
+          });
+        }
+      };
+    });
+  }));
+
+  function getTitleButton() {
+    return element.find('th').eq(1).find('button').first();
+  }
+
+  function getTitle() {
+    return getTitleButton().text();
+  }
+
+  function clickTitleButton() {
+    getTitleButton().click();
+  }
+
+  function clickPreviousButton(times) {
+    var el = element.find('th').eq(0).find('button').eq(0);
+    for (var i = 0, n = times || 1; i < n; i++) {
+      el.click();
+    }
+  }
+
+  function clickNextButton() {
+    element.find('th').eq(2).find('button').eq(0).click();
+  }
+
+  function getLabelsRow() {
+    return element.find('thead').find('tr').eq(1);
+  }
+
+  function getLabels(dayMode) {
+    var els = getLabelsRow().find('th'),
+        labels = [];
+    for (var i = dayMode ? 1 : 0, n = els.length; i < n; i++) {
+      labels.push(els.eq(i).text());
+    }
+    return labels;
+  }
+
+  function getWeeks() {
+    var rows = element.find('tbody').find('tr'),
+        weeks = [];
+    for (var i = 0, n = rows.length; i < n; i++) {
+      weeks.push(rows.eq(i).find('td').eq(0).first().text());
+    }
+    return weeks;
+  }
+
+  function getOptions(dayMode) {
+    var tr = element.find('tbody').find('tr');
+    var rows = [];
+
+    for (var j = 0, numRows = tr.length; j < numRows; j++) {
+      var cols = tr.eq(j).find('td'), days = [];
+      for (var i = dayMode ? 1 : 0, n = cols.length; i < n; i++) {
+        days.push(cols.eq(i).find('button').text());
+      }
+      rows.push(days);
+    }
+    return rows;
+  }
+
+  function clickOption(index) {
+    getAllOptionsEl().eq(index).click();
+  }
+
+  function getAllOptionsEl(dayMode) {
+    return element.find('tbody').find('button');
+  }
+
+  function selectedElementIndex() {
+    var buttons = getAllOptionsEl();
+    for (var i = 0; i < buttons.length; i++) {
+      if (angular.element(buttons[i]).hasClass('btn-info')) {
+        return i;
+      }
+    }
+  }
+
+  function expectSelectedElement(index) {
+    var buttons = getAllOptionsEl();
+    angular.forEach( buttons, function(button, idx) {
+      expect(angular.element(button).hasClass('btn-info')).toBe(idx === index);
+    });
+  }
+
+  function triggerKeyDown(element, key, ctrl) {
+    var keyCodes = {
+      'enter': 13,
+      'space': 32,
+      'pageup': 33,
+      'pagedown': 34,
+      'end': 35,
+      'home': 36,
+      'left': 37,
+      'up': 38,
+      'right': 39,
+      'down': 40,
+      'esc': 27
+    };
+    var e = $.Event('keydown');
+    e.which = keyCodes[key];
+    if (ctrl) {
+      e.ctrlKey = true;
+    }
+    element.trigger(e);
+  }
+
+  describe('$datepickerSuppressError', function() {
+    var $compile,
+        $log,
+        $scope;
+
+    it('should not suppress log error message for ng-model date error by default', function() {
+      inject(function(_$log_, _$rootScope_, _$compile_) {
+        $log = _$log_;
+        $scope = _$rootScope_.$new();
+        $compile = _$compile_;
+      });
+
+      spyOn($log, 'error');
+      element = $compile('<uib-datepicker ng-model="locals.date"></uib-datepicker>')($scope);
+
+      $scope.locals = {
+        date: 'lalala'
+      };
+      $scope.$digest();
+      expect($log.error).toHaveBeenCalled();
+    });
+
+    it('should not suppress log error message for ng-model date error when false', function() {
+      module(function($provide) {
+        $provide.value('$datepickerSuppressError', false);
+      });
+
+      inject(function(_$log_, _$rootScope_, _$compile_) {
+        $log = _$log_;
+        $scope = _$rootScope_.$new();
+        $compile = _$compile_;
+      });
+
+      spyOn($log, 'error');
+      element = $compile('<uib-datepicker ng-model="locals.date"></uib-datepicker>')($scope);
+
+      $scope.locals = {
+        date: 'lalala'
+      };
+      $scope.$digest();
+      expect($log.error).toHaveBeenCalled();
+    });
+
+    it('should suppress log error message for ng-model date error when true', function() {
+      module(function($provide) {
+        $provide.value('$datepickerSuppressError', true);
+      });
+
+      inject(function(_$log_, _$rootScope_, _$compile_) {
+        $log = _$log_;
+        $scope = _$rootScope_.$new();
+        $compile = _$compile_;
+      });
+      spyOn($log, 'error');
+
+      element = $compile('<uib-datepicker ng-model="locals.date"></uib-datepicker>')($scope);
+
+      $scope.locals = {
+        date: 'lalala'
+      };
+      $scope.$digest();
+      expect($log.error).not.toHaveBeenCalled();
+    });
+  });
+
+  describe('', function() {
+    beforeEach(inject(function(_$compile_, _$rootScope_, _$templateCache_) {
+      $compile = _$compile_;
+      $rootScope = _$rootScope_;
+      $rootScope.date = new Date('September 30, 2010 15:30:00');
+      $templateCache = _$templateCache_;
+    }));
+
+
+    describe('', function() {
+      beforeEach(function() {
+        element = $compile('<uib-datepicker ng-model="date"></uib-datepicker>')($rootScope);
+        $rootScope.$digest();
+      });
+
+      it('is has a `<table>` element', function() {
+        expect(element.find('table').length).toBe(1);
+      });
+
+      it('shows the correct title', function() {
+        expect(getTitle()).toBe('September 2010');
+      });
+
+      it('shows the label row & the correct day labels', function() {
+        expect(getLabelsRow().css('display')).not.toBe('none');
+        expect(getLabels(true)).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);
+      });
+
+      it('renders the calendar days correctly', function() {
+        expect(getOptions(true)).toEqual([
+          ['29', '30', '31', '01', '02', '03', '04'],
+          ['05', '06', '07', '08', '09', '10', '11'],
+          ['12', '13', '14', '15', '16', '17', '18'],
+          ['19', '20', '21', '22', '23', '24', '25'],
+          ['26', '27', '28', '29', '30', '01', '02'],
+          ['03', '04', '05', '06', '07', '08', '09']
+        ]);
+      });
+
+      it('renders the week numbers based on ISO 8601', function() {
+        expect(getWeeks()).toEqual(['35', '36', '37', '38', '39', '40']);
+      });
+
+      it('value is correct', function() {
+        expect($rootScope.date).toEqual(new Date('September 30, 2010 15:30:00'));
+      });
+
+      it('has `selected` only the correct day', function() {
+        expectSelectedElement(32);
+      });
+
+      it('has no `selected` day when model is cleared', function() {
+        $rootScope.date = null;
+        $rootScope.$digest();
+
+        expect($rootScope.date).toBe(null);
+        expectSelectedElement(null);
+      });
+
+      it('does not change current view when model is cleared', function() {
+        $rootScope.date = null;
+        $rootScope.$digest();
+
+        expect($rootScope.date).toBe(null);
+        expect(getTitle()).toBe('September 2010');
+      });
+
+      it('`disables` visible dates from other months', function() {
+        var buttons = getAllOptionsEl();
+        angular.forEach(buttons, function(button, index) {
+          expect(angular.element(button).find('span').hasClass('text-muted')).toBe( index < 3 || index > 32 );
+        });
+      });
+
+      it('updates the model when a day is clicked', function() {
+        clickOption(17);
+        expect($rootScope.date).toEqual(new Date('September 15, 2010 15:30:00'));
+      });
+
+      it('moves to the previous month & renders correctly when `previous` button is clicked', function() {
+        clickPreviousButton();
+
+        expect(getTitle()).toBe('August 2010');
+        expect(getLabels(true)).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);
+        expect(getOptions(true)).toEqual([
+          ['01', '02', '03', '04', '05', '06', '07'],
+          ['08', '09', '10', '11', '12', '13', '14'],
+          ['15', '16', '17', '18', '19', '20', '21'],
+          ['22', '23', '24', '25', '26', '27', '28'],
+          ['29', '30', '31', '01', '02', '03', '04'],
+          ['05', '06', '07', '08', '09', '10', '11']
+        ]);
+
+        expectSelectedElement(null, null);
+      });
+
+      it('updates the model only when a day is clicked in the `previous` month', function() {
+        clickPreviousButton();
+        expect($rootScope.date).toEqual(new Date('September 30, 2010 15:30:00'));
+
+        clickOption(17);
+        expect($rootScope.date).toEqual(new Date('August 18, 2010 15:30:00'));
+      });
+
+      it('moves to the next month & renders correctly when `next` button is clicked', function() {
+        clickNextButton();
+
+        expect(getTitle()).toBe('October 2010');
+        expect(getLabels(true)).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);
+        expect(getOptions(true)).toEqual([
+          ['26', '27', '28', '29', '30', '01', '02'],
+          ['03', '04', '05', '06', '07', '08', '09'],
+          ['10', '11', '12', '13', '14', '15', '16'],
+          ['17', '18', '19', '20', '21', '22', '23'],
+          ['24', '25', '26', '27', '28', '29', '30'],
+          ['31', '01', '02', '03', '04', '05', '06']
+        ]);
+
+        expectSelectedElement(4);
+      });
+
+      it('updates the model only when a day is clicked in the `next` month', function() {
+        clickNextButton();
+        expect($rootScope.date).toEqual(new Date('September 30, 2010 15:30:00'));
+
+        clickOption(17);
+        expect($rootScope.date).toEqual(new Date('October 13, 2010 15:30:00'));
+      });
+
+      it('updates the calendar when a day of another month is selected', function() {
+        clickOption(33);
+        expect($rootScope.date).toEqual(new Date('October 01, 2010 15:30:00'));
+        expect(getTitle()).toBe('October 2010');
+        expect(getLabels(true)).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);
+        expect(getOptions(true)).toEqual([
+          ['26', '27', '28', '29', '30', '01', '02'],
+          ['03', '04', '05', '06', '07', '08', '09'],
+          ['10', '11', '12', '13', '14', '15', '16'],
+          ['17', '18', '19', '20', '21', '22', '23'],
+          ['24', '25', '26', '27', '28', '29', '30'],
+          ['31', '01', '02', '03', '04', '05', '06']
+        ]);
+
+        expectSelectedElement(5);
+      });
+
+      // issue #1697
+      it('should not "jump" months', function() {
+        $rootScope.date = new Date('January 30, 2014');
+        $rootScope.$digest();
+        clickNextButton();
+        expect(getTitle()).toBe('February 2014');
+        clickPreviousButton();
+        expect(getTitle()).toBe('January 2014');
+      });
+
+      it('should support custom templates', function() {
+        $templateCache.put('foo/bar.html', '<div>baz</div>');
+
+        element = $compile('<uib-datepicker ng-model="date" template-url="foo/bar.html"></uib-datepicker>')($rootScope);
+        $rootScope.$digest();
+
+        expect(element.html()).toBe('baz');
+      });
+
+      it('should support custom day, month and year templates', function() {
+        $templateCache.put('foo/day.html', '<div>day</div>');
+        $templateCache.put('foo/month.html', '<div>month</div>');
+        $templateCache.put('foo/year.html', '<div>year</div>');
+
+        $templateCache.put('foo/bar.html', '<div>' +
+          '<uib-daypicker template-url="foo/day.html"></uib-daypicker>' +
+          '<uib-monthpicker template-url="foo/month.html"></uib-monthpicker>' +
+          '<uib-yearpicker template-url="foo/year.html"></uib-yearpicker>' +
+        '</div>');
+
+        element = $compile('<uib-datepicker ng-model="date" template-url="foo/bar.html"></uib-datepicker>')($rootScope);
+        $rootScope.$digest();
+
+        var expectedHtml = '<div template-url="foo/day.html">day</div><div template-url="foo/month.html">month</div><div template-url="foo/year.html">year</div>';
+
+        expect(element.html()).toBe(expectedHtml);
+      });
+
+      it('should expose the controller in the template', function() {
+        $templateCache.put('template/datepicker/datepicker.html', '<div>{{datepicker.text}}</div>');
+
+        element = $compile('<uib-datepicker ng-model="date"></uib-datepicker>')($rootScope);
+        $rootScope.$digest();
+
+        var ctrl = element.controller('uib-datepicker');
+        expect(ctrl).toBeDefined();
+        expect(element.html()).toBe('');
+
+        ctrl.text = 'baz';
+        $rootScope.$digest();
+
+        expect(element.html()).toBe('baz');
+      });
+
+      describe('when `model` changes', function() {
+        function testCalendar() {
+          expect(getTitle()).toBe('November 2005');
+          expect(getOptions(true)).toEqual([
+            ['30', '31', '01', '02', '03', '04', '05'],
+            ['06', '07', '08', '09', '10', '11', '12'],
+            ['13', '14', '15', '16', '17', '18', '19'],
+            ['20', '21', '22', '23', '24', '25', '26'],
+            ['27', '28', '29', '30', '01', '02', '03'],
+            ['04', '05', '06', '07', '08', '09', '10']
+          ]);
+
+          expectSelectedElement(8);
+        }
+
+        describe('to a Date object', function() {
+          it('updates', function() {
+            $rootScope.date = new Date('November 7, 2005 23:30:00');
+            $rootScope.$digest();
+            testCalendar();
+            expect(angular.isDate($rootScope.date)).toBe(true);
+          });
+
+          it('to a date that is invalid, it doesn\`t update', function() {
+            $rootScope.date = new Date('pizza');
+            $rootScope.$digest();
+            expect(getTitle()).toBe('September 2010');
+            expect(angular.isDate($rootScope.date)).toBe(true);
+            expect(isNaN($rootScope.date)).toBe(true);
+          });
+        });
+
+        describe('not to a Date object', function() {
+          it('to a Number, it updates calendar', function() {
+            $rootScope.date = parseInt((new Date('November 7, 2005 23:30:00')).getTime(), 10);
+            $rootScope.$digest();
+            testCalendar();
+            expect(angular.isNumber($rootScope.date)).toBe(true);
+          });
+
+          it('to a string that can be parsed by Date, it updates calendar', function() {
+            $rootScope.date = 'November 7, 2005 23:30:00';
+            $rootScope.$digest();
+            testCalendar();
+            expect(angular.isString($rootScope.date)).toBe(true);
+          });
+
+          it('to a string that cannot be parsed by Date, it doesn\'t update', function() {
+            $rootScope.date = 'pizza';
+            $rootScope.$digest();
+            expect(getTitle()).toBe('September 2010');
+            expect($rootScope.date).toBe('pizza');
+          });
+        });
+      });
+
+      it('does not loop between after max mode', function() {
+        expect(getTitle()).toBe('September 2010');
+
+        clickTitleButton();
+        expect(getTitle()).toBe('2010');
+
+        clickTitleButton();
+        expect(getTitle()).toBe('2001 - 2020');
+
+        clickTitleButton();
+        expect(getTitle()).toBe('2001 - 2020');
+      });
+
+      describe('month selection mode', function() {
+        beforeEach(function() {
+          clickTitleButton();
+        });
+
+        it('shows the year as title', function() {
+          expect(getTitle()).toBe('2010');
+        });
+
+        it('shows months as options', function() {
+          expect(getOptions()).toEqual([
+            ['January', 'February', 'March'],
+            ['April', 'May', 'June'],
+            ['July', 'August', 'September'],
+            ['October', 'November', 'December']
+          ]);
+        });
+
+        it('does not change the model', function() {
+          expect($rootScope.date).toEqual(new Date('September 30, 2010 15:30:00'));
+        });
+
+        it('has `selected` only the correct month', function() {
+          expectSelectedElement(8);
+        });
+
+        it('moves to the previous year when `previous` button is clicked', function() {
+          clickPreviousButton();
+
+          expect(getTitle()).toBe('2009');
+          expect(getOptions()).toEqual([
+            ['January', 'February', 'March'],
+            ['April', 'May', 'June'],
+            ['July', 'August', 'September'],
+            ['October', 'November', 'December']
+          ]);
+
+          expectSelectedElement(null);
+        });
+
+        it('moves to the next year when `next` button is clicked', function() {
+          clickNextButton();
+
+          expect(getTitle()).toBe('2011');
+          expect(getOptions()).toEqual([
+            ['January', 'February', 'March'],
+            ['April', 'May', 'June'],
+            ['July', 'August', 'September'],
+            ['October', 'November', 'December']
+          ]);
+
+          expectSelectedElement(null);
+        });
+
+        it('renders correctly when a month is clicked', function() {
+          clickPreviousButton(5);
+          expect(getTitle()).toBe('2005');
+
+          clickOption(10);
+          expect($rootScope.date).toEqual(new Date('September 30, 2010 15:30:00'));
+          expect(getTitle()).toBe('November 2005');
+          expect(getOptions(true)).toEqual([
+            ['30', '31', '01', '02', '03', '04', '05'],
+            ['06', '07', '08', '09', '10', '11', '12'],
+            ['13', '14', '15', '16', '17', '18', '19'],
+            ['20', '21', '22', '23', '24', '25', '26'],
+            ['27', '28', '29', '30', '01', '02', '03'],
+            ['04', '05', '06', '07', '08', '09', '10']
+          ]);
+
+          clickOption(17);
+          expect($rootScope.date).toEqual(new Date('November 16, 2005 15:30:00'));
+        });
+      });
+
+      describe('year selection mode', function() {
+        beforeEach(function() {
+          clickTitleButton();
+          clickTitleButton();
+        });
+
+        it('shows the year range as title', function() {
+          expect(getTitle()).toBe('2001 - 2020');
+        });
+
+        it('shows years as options', function() {
+          expect(getOptions()).toEqual([
+            ['2001', '2002', '2003', '2004', '2005'],
+            ['2006', '2007', '2008', '2009', '2010'],
+            ['2011', '2012', '2013', '2014', '2015'],
+            ['2016', '2017', '2018', '2019', '2020']
+          ]);
+        });
+
+        it('does not change the model', function() {
+          expect($rootScope.date).toEqual(new Date('September 30, 2010 15:30:00'));
+        });
+
+        it('has `selected` only the selected year', function() {
+          expectSelectedElement(9);
+        });
+
+        it('moves to the previous year set when `previous` button is clicked', function() {
+          clickPreviousButton();
+
+          expect(getTitle()).toBe('1981 - 2000');
+          expect(getOptions()).toEqual([
+            ['1981', '1982', '1983', '1984', '1985'],
+            ['1986', '1987', '1988', '1989', '1990'],
+            ['1991', '1992', '1993', '1994', '1995'],
+            ['1996', '1997', '1998', '1999', '2000']
+          ]);
+          expectSelectedElement(null);
+        });
+
+        it('moves to the next year set when `next` button is clicked', function() {
+          clickNextButton();
+
+          expect(getTitle()).toBe('2021 - 2040');
+          expect(getOptions()).toEqual([
+            ['2021', '2022', '2023', '2024', '2025'],
+            ['2026', '2027', '2028', '2029', '2030'],
+            ['2031', '2032', '2033', '2034', '2035'],
+            ['2036', '2037', '2038', '2039', '2040']
+          ]);
+
+          expectSelectedElement(null);
+        });
+      });
+
+      describe('keyboard navigation', function() {
+        function getActiveLabel() {
+          return element.find('.active').eq(0).text();
+        }
+
+        describe('day mode', function() {
+          it('will be able to activate previous day', function() {
+            triggerKeyDown(element, 'left');
+            expect(getActiveLabel()).toBe('29');
+          });
+
+          it('will be able to select with enter', function() {
+            triggerKeyDown(element, 'left');
+            triggerKeyDown(element, 'enter');
+            expect($rootScope.date).toEqual(new Date('September 29, 2010 15:30:00'));
+          });
+
+          it('will be able to select with space', function() {
+            triggerKeyDown(element, 'left');
+            triggerKeyDown(element, 'space');
+            expect($rootScope.date).toEqual(new Date('September 29, 2010 15:30:00'));
+          });
+
+          it('will be able to activate next day', function() {
+            triggerKeyDown(element, 'right');
+            expect(getActiveLabel()).toBe('01');
+            expect(getTitle()).toBe('October 2010');
+          });
+
+          it('will be able to activate same day in previous week', function() {
+            triggerKeyDown(element, 'up');
+            expect(getActiveLabel()).toBe('23');
+          });
+
+          it('will be able to activate same day in next week', function() {
+            triggerKeyDown(element, 'down');
+            expect(getActiveLabel()).toBe('07');
+            expect(getTitle()).toBe('October 2010');
+          });
+
+          it('will be able to activate same date in previous month', function() {
+            triggerKeyDown(element, 'pageup');
+            expect(getActiveLabel()).toBe('30');
+            expect(getTitle()).toBe('August 2010');
+          });
+
+          it('will be able to activate same date in next month', function() {
+            triggerKeyDown(element, 'pagedown');
+            expect(getActiveLabel()).toBe('30');
+            expect(getTitle()).toBe('October 2010');
+          });
+
+          it('will be able to activate first day of the month', function() {
+            triggerKeyDown(element, 'home');
+            expect(getActiveLabel()).toBe('01');
+            expect(getTitle()).toBe('September 2010');
+          });
+
+          it('will be able to activate last day of the month', function() {
+            $rootScope.date = new Date('September 1, 2010 15:30:00');
+            $rootScope.$digest();
+
+            triggerKeyDown(element, 'end');
+            expect(getActiveLabel()).toBe('30');
+            expect(getTitle()).toBe('September 2010');
+          });
+
+          it('will be able to move to month mode', function() {
+            triggerKeyDown(element, 'up', true);
+            expect(getActiveLabel()).toBe('September');
+            expect(getTitle()).toBe('2010');
+          });
+
+          it('will not respond when trying to move to lower mode', function() {
+            triggerKeyDown(element, 'down', true);
+            expect(getActiveLabel()).toBe('30');
+            expect(getTitle()).toBe('September 2010');
+          });
+        });
+
+        describe('month mode', function() {
+          beforeEach(function() {
+            triggerKeyDown(element, 'up', true);
+          });
+
+          it('will be able to activate previous month', function() {
+            triggerKeyDown(element, 'left');
+            expect(getActiveLabel()).toBe('August');
+          });
+
+          it('will be able to activate next month', function() {
+            triggerKeyDown(element, 'right');
+            expect(getActiveLabel()).toBe('October');
+          });
+
+          it('will be able to activate same month in previous row', function() {
+            triggerKeyDown(element, 'up');
+            expect(getActiveLabel()).toBe('June');
+          });
+
+          it('will be able to activate same month in next row', function() {
+            triggerKeyDown(element, 'down');
+            expect(getActiveLabel()).toBe('December');
+          });
+
+          it('will be able to activate same date in previous year', function() {
+            triggerKeyDown(element, 'pageup');
+            expect(getActiveLabel()).toBe('September');
+            expect(getTitle()).toBe('2009');
+          });
+
+          it('will be able to activate same date in next year', function() {
+            triggerKeyDown(element, 'pagedown');
+            expect(getActiveLabel()).toBe('September');
+            expect(getTitle()).toBe('2011');
+          });
+
+          it('will be able to activate first month of the year', function() {
+            triggerKeyDown(element, 'home');
+            expect(getActiveLabel()).toBe('January');
+            expect(getTitle()).toBe('2010');
+          });
+
+          it('will be able to activate last month of the year', function() {
+            triggerKeyDown(element, 'end');
+            expect(getActiveLabel()).toBe('December');
+            expect(getTitle()).toBe('2010');
+          });
+
+          it('will be able to move to year mode', function() {
+            triggerKeyDown(element, 'up', true);
+            expect(getActiveLabel()).toBe('2010');
+            expect(getTitle()).toBe('2001 - 2020');
+          });
+
+          it('will be able to move to day mode', function() {
+            triggerKeyDown(element, 'down', true);
+            expect(getActiveLabel()).toBe('30');
+            expect(getTitle()).toBe('September 2010');
+          });
+
+          it('will move to day mode when selecting', function() {
+            triggerKeyDown(element, 'left', true);
+            triggerKeyDown(element, 'enter', true);
+            expect(getActiveLabel()).toBe('30');
+            expect(getTitle()).toBe('August 2010');
+            expect($rootScope.date).toEqual(new Date('September 30, 2010 15:30:00'));
+          });
+        });
+
+        describe('year mode', function() {
+          beforeEach(function() {
+            triggerKeyDown(element, 'up', true);
+            triggerKeyDown(element, 'up', true);
+          });
+
+          it('will be able to activate previous year', function() {
+            triggerKeyDown(element, 'left');
+            expect(getActiveLabel()).toBe('2009');
+          });
+
+          it('will be able to activate next year', function() {
+            triggerKeyDown(element, 'right');
+            expect(getActiveLabel()).toBe('2011');
+          });
+
+          it('will be able to activate same year in previous row', function() {
+            triggerKeyDown(element, 'up');
+            expect(getActiveLabel()).toBe('2005');
+          });
+
+          it('will be able to activate same year in next row', function() {
+            triggerKeyDown(element, 'down');
+            expect(getActiveLabel()).toBe('2015');
+          });
+
+          it('will be able to activate same date in previous view', function() {
+            triggerKeyDown(element, 'pageup');
+            expect(getActiveLabel()).toBe('1990');
+          });
+
+          it('will be able to activate same date in next view', function() {
+            triggerKeyDown(element, 'pagedown');
+            expect(getActiveLabel()).toBe('2030');
+          });
+
+          it('will be able to activate first year of the year', function() {
+            triggerKeyDown(element, 'home');
+            expect(getActiveLabel()).toBe('2001');
+          });
+
+          it('will be able to activate last year of the year', function() {
+            triggerKeyDown(element, 'end');
+            expect(getActiveLabel()).toBe('2020');
+          });
+
+          it('will not respond when trying to move to upper mode', function() {
+            triggerKeyDown(element, 'up', true);
+            expect(getTitle()).toBe('2001 - 2020');
+          });
+
+          it('will be able to move to month mode', function() {
+            triggerKeyDown(element, 'down', true);
+            expect(getActiveLabel()).toBe('September');
+            expect(getTitle()).toBe('2010');
+          });
+
+          it('will move to month mode when selecting', function() {
+            triggerKeyDown(element, 'left', true);
+            triggerKeyDown(element, 'enter', true);
+            expect(getActiveLabel()).toBe('September');
+            expect(getTitle()).toBe('2009');
+            expect($rootScope.date).toEqual(new Date('September 30, 2010 15:30:00'));
+          });
+        });
+
+        describe('`aria-activedescendant`', function() {
+          function checkActivedescendant() {
+            var activeId = element.find('table').attr('aria-activedescendant');
+            expect(element.find('#' + activeId + ' > button')).toHaveClass('active');
+          }
+
+          it('updates correctly', function() {
+            triggerKeyDown(element, 'left');
+            checkActivedescendant();
+
+            triggerKeyDown(element, 'down');
+            checkActivedescendant();
+
+            triggerKeyDown(element, 'up', true);
+            checkActivedescendant();
+
+            triggerKeyDown(element, 'up', true);
+            checkActivedescendant();
+          });
+        });
+      });
+    });
+
+    describe('attribute `starting-day`', function () {
+      beforeEach(function() {
+        $rootScope.startingDay = 1;
+        element = $compile('<uib-datepicker ng-model="date" starting-day="startingDay"></uib-datepicker>')($rootScope);
+        $rootScope.$digest();
+      });
+
+      it('shows the day labels rotated', function() {
+        expect(getLabels(true)).toEqual(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']);
+      });
+
+      it('renders the calendar days correctly', function() {
+        expect(getOptions(true)).toEqual([
+          ['30', '31', '01', '02', '03', '04', '05'],
+          ['06', '07', '08', '09', '10', '11', '12'],
+          ['13', '14', '15', '16', '17', '18', '19'],
+          ['20', '21', '22', '23', '24', '25', '26'],
+          ['27', '28', '29', '30', '01', '02', '03'],
+          ['04', '05', '06', '07', '08', '09', '10']
+        ]);
+      });
+
+      it('renders the week numbers correctly', function() {
+        expect(getWeeks()).toEqual(['35', '36', '37', '38', '39', '40']);
+      });
+    });
+
+    describe('attribute `show-weeks`', function() {
+      beforeEach(function() {
+        $rootScope.showWeeks = false;
+        element = $compile('<uib-datepicker ng-model="date" show-weeks="showWeeks"></uib-datepicker>')($rootScope);
+        $rootScope.$digest();
+      });
+
+      it('hides week numbers based on variable', function() {
+        expect(getLabelsRow().find('th').length).toEqual(7);
+        var tr = element.find('tbody').find('tr');
+        for (var i = 0; i < 5; i++) {
+          expect(tr.eq(i).find('td').length).toEqual(7);
+        }
+      });
+    });
+
+    describe('`min-date` attribute', function () {
+      beforeEach(function() {
+        $rootScope.mindate = new Date('September 12, 2010');
+        element = $compile('<uib-datepicker ng-model="date" min-date="mindate"></uib-datepicker>')($rootScope);
+        $rootScope.$digest();
+      });
+
+      it('disables appropriate days in current month', function() {
+        var buttons = getAllOptionsEl();
+        angular.forEach(buttons, function(button, index) {
+          expect(angular.element(button).prop('disabled')).toBe(index < 14);
+        });
+      });
+
+      it('disables appropriate days when min date changes', function() {
+        $rootScope.mindate = new Date('September 5, 2010');
+        $rootScope.$digest();
+
+        var buttons = getAllOptionsEl();
+        angular.forEach(buttons, function(button, index) {
+          expect(angular.element(button).prop('disabled')).toBe(index < 7);
+        });
+      });
+
+      it('invalidates when model is a disabled date', function() {
+        $rootScope.mindate = new Date('September 5, 2010');
+        $rootScope.date = new Date('September 2, 2010');
+        $rootScope.$digest();
+        expect(element.hasClass('ng-invalid')).toBeTruthy();
+        expect(element.hasClass('ng-invalid-date-disabled')).toBeTruthy();
+      });
+
+      it('disables all days in previous month', function() {
+        clickPreviousButton();
+        var buttons = getAllOptionsEl();
+        angular.forEach(buttons, function(button, index) {
+          expect(angular.element(button).prop('disabled')).toBe(true);
+        });
+      });
+
+      it('disables no days in next month', function() {
+        clickNextButton();
+        var buttons = getAllOptionsEl();
+        angular.forEach(buttons, function(button, index) {
+          expect(angular.element(button).prop('disabled')).toBe(false);
+        });
+      });
+
+      it('disables appropriate months in current year', function() {
+        clickTitleButton();
+        var buttons = getAllOptionsEl();
+        angular.forEach(buttons, function(button, index) {
+          expect(angular.element(button).prop('disabled')).toBe(index < 8);
+        });
+      });
+
+      it('disables all months in previous year', function() {
+        clickTitleButton();
+        clickPreviousButton();
+        var buttons = getAllOptionsEl();
+        angular.forEach(buttons, function(button, index) {
+          expect(angular.element(button).prop('disabled')).toBe(true);
+        });
+      });
+
+      it('disables no months in next year', function() {
+        clickTitleButton();
+        clickNextButton();
+        var buttons = getAllOptionsEl();
+        angular.forEach(buttons, function(button, index) {
+          expect(angular.element(button).prop('disabled')).toBe(false);
+        });
+      });
+
+      it('enables everything before if it is cleared', function() {
+        $rootScope.mindate = null;
+        $rootScope.date = new Date('December 20, 1949');
+        $rootScope.$digest();
+
+        clickTitleButton();
+        var buttons = getAllOptionsEl();
+        angular.forEach(buttons, function(button, index) {
+          expect(angular.element(button).prop('disabled')).toBe(false);
+        });
+      });
+
+    });
+
+    describe('`max-date` attribute', function() {
+      beforeEach(function() {
+        $rootScope.maxdate = new Date('September 25, 2010');
+        element = $compile('<uib-datepicker ng-model="date" max-date="maxdate"></uib-datepicker>')($rootScope);
+        $rootScope.$digest();
+      });
+
+      it('disables appropriate days in current month', function() {
+        var buttons = getAllOptionsEl();
+        angular.forEach(buttons, function(button, index) {
+          expect(angular.element(button).prop('disabled')).toBe(index > 27);
+        });
+      });
+
+      it('disables appropriate days when max date changes', function() {
+        $rootScope.maxdate = new Date('September 18, 2010');
+        $rootScope.$digest();
+
+        var buttons = getAllOptionsEl();
+        angular.forEach(buttons, function(button, index) {
+          expect(angular.element(button).prop('disabled')).toBe(index > 20);
+        });
+      });
+
+      it('invalidates when model is a disabled date', function() {
+        $rootScope.maxdate = new Date('September 18, 2010');
+        $rootScope.$digest();
+        expect(element.hasClass('ng-invalid')).toBeTruthy();
+        expect(element.hasClass('ng-invalid-date-disabled')).toBeTruthy();
+      });
+
+      it('disables no days in previous month', function() {
+        clickPreviousButton();
+        var buttons = getAllOptionsEl();
+        angular.forEach(buttons, function(button, index) {
+          expect(angular.element(button).prop('disabled')).toBe(false);
+        });
+      });
+
+      it('disables all days in next month', function() {
+        clickNextButton();
+        var buttons = getAllOptionsEl();
+        angular.forEach(buttons, function(button, index) {
+          expect(angular.element(button).prop('disabled')).toBe(true);
+        });
+      });
+
+      it('disables appropriate months in current year', function() {
+        clickTitleButton();
+        var buttons = getAllOptionsEl();
+        angular.forEach(buttons, function(button, index) {
+          expect(angular.element(button).prop('disabled')).toBe(index > 8);
+        });
+      });
+
+      it('disables no months in previous year', function() {
+        clickTitleButton();
+        clickPreviousButton();
+        var buttons = getAllOptionsEl();
+        angular.forEach(buttons, function(button, index) {
+          expect(angular.element(button).prop('disabled')).toBe(false);
+        });
+      });
+
+      it('disables all months in next year', function() {
+        clickTitleButton();
+        clickNextButton();
+        var buttons = getAllOptionsEl();
+        angular.forEach(buttons, function(button, index) {
+          expect(angular.element(button).prop('disabled')).toBe(true);
+        });
+      });
+
+      it('enables everything after if it is cleared', function() {
+        $rootScope.maxdate = null;
+        $rootScope.$digest();
+        var buttons = getAllOptionsEl();
+        angular.forEach(buttons, function(button, index) {
+          expect(angular.element(button).prop('disabled')).toBe(false);
+        });
+      });
+    });
+
+    describe('date-disabled expression', function () {
+      beforeEach(function() {
+        $rootScope.dateDisabledHandler = jasmine.createSpy('dateDisabledHandler');
+        element = $compile('<uib-datepicker ng-model="date" date-disabled="dateDisabledHandler(date, mode)"></uib-datepicker>')($rootScope);
+        $rootScope.$digest();
+      });
+
+      it('executes the dateDisabled expression for each visible day plus one for validation', function() {
+        expect($rootScope.dateDisabledHandler.calls.count()).toEqual(42 + 1);
+      });
+
+      it('executes the dateDisabled expression for each visible month plus one for validation', function() {
+        $rootScope.dateDisabledHandler.calls.reset();
+        clickTitleButton();
+        expect($rootScope.dateDisabledHandler.calls.count()).toEqual(12 + 1);
+      });
+
+      it('executes the dateDisabled expression for each visible year plus one for validation', function() {
+        clickTitleButton();
+        $rootScope.dateDisabledHandler.calls.reset();
+        clickTitleButton();
+        expect($rootScope.dateDisabledHandler.calls.count()).toEqual(20 + 1);
+      });
+    });
+
+    describe('custom-class expression', function() {
+      beforeEach(function() {
+        $rootScope.customClassHandler = jasmine.createSpy('customClassHandler');
+        element = $compile('<uib-datepicker ng-model="date" custom-class="customClassHandler(date, mode)"></uib-datepicker>')($rootScope);
+        $rootScope.$digest();
+      });
+
+      it('executes the customClass expression for each visible day plus one for validation', function() {
+        expect($rootScope.customClassHandler.calls.count()).toEqual(42);
+      });
+
+      it('executes the customClass expression for each visible month plus one for validation', function() {
+        $rootScope.customClassHandler.calls.reset();
+        clickTitleButton();
+        expect($rootScope.customClassHandler.calls.count()).toEqual(12);
+      });
+
+      it('executes the customClass expression for each visible year plus one for validation', function() {
+        clickTitleButton();
+        $rootScope.customClassHandler.calls.reset();
+        clickTitleButton();
+        expect($rootScope.customClassHandler.calls.count()).toEqual(20);
+      });
+    });
+
+    describe('formatting', function() {
+      beforeEach(function() {
+        $rootScope.dayTitle = 'MMMM, yy';
+        element = $compile('<uib-datepicker ng-model="date"' +
+          'format-day="d"' +
+          'format-day-header="EEEE"' +
+          'format-day-title="{{dayTitle}}"' +
+          'format-month="MMM"' +
+          'format-month-title="yy"' +
+          'format-year="yy"' +
+          'year-range="10"></uib-datepicker>')($rootScope);
+        $rootScope.$digest();
+      });
+
+      it('changes the title format in `day` mode', function() {
+        expect(getTitle()).toBe('September, 10');
+      });
+
+      it('changes the title & months format in `month` mode', function() {
+        clickTitleButton();
+
+        expect(getTitle()).toBe('10');
+        expect(getOptions()).toEqual([
+          ['Jan', 'Feb', 'Mar'],
+          ['Apr', 'May', 'Jun'],
+          ['Jul', 'Aug', 'Sep'],
+          ['Oct', 'Nov', 'Dec']
+        ]);
+      });
+
+      it('changes the title, year format & range in `year` mode', function() {
+        clickTitleButton();
+        clickTitleButton();
+
+        expect(getTitle()).toBe('01 - 10');
+        expect(getOptions()).toEqual([
+          ['01', '02', '03', '04', '05'],
+          ['06', '07', '08', '09', '10']
+        ]);
+      });
+
+      it('shows day labels', function() {
+        expect(getLabels(true)).toEqual(['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']);
+      });
+
+      it('changes the day format', function() {
+        expect(getOptions(true)).toEqual([
+          ['29', '30', '31', '1', '2', '3', '4'],
+          ['5', '6', '7', '8', '9', '10', '11'],
+          ['12', '13', '14', '15', '16', '17', '18'],
+          ['19', '20', '21', '22', '23', '24', '25'],
+          ['26', '27', '28', '29', '30', '1', '2'],
+          ['3', '4', '5', '6', '7', '8', '9']
+        ]);
+      });
+    });
+
+    describe('setting datepickerConfig', function() {
+      var originalConfig = {};
+      beforeEach(inject(function(uibDatepickerConfig) {
+        angular.extend(originalConfig, uibDatepickerConfig);
+        uibDatepickerConfig.formatDay = 'd';
+        uibDatepickerConfig.formatMonth = 'MMM';
+        uibDatepickerConfig.formatYear = 'yy';
+        uibDatepickerConfig.formatDayHeader = 'EEEE';
+        uibDatepickerConfig.formatDayTitle = 'MMM, yy';
+        uibDatepickerConfig.formatMonthTitle = 'yy';
+        uibDatepickerConfig.showWeeks = false;
+        uibDatepickerConfig.yearRange = 10;
+        uibDatepickerConfig.startingDay = 6;
+
+        element = $compile('<uib-datepicker ng-model="date"></uib-datepicker>')($rootScope);
+        $rootScope.$digest();
+      }));
+      afterEach(inject(function(uibDatepickerConfig) {
+        // return it to the original state
+        angular.extend(uibDatepickerConfig, originalConfig);
+      }));
+
+      it('changes the title format in `day` mode', function() {
+        expect(getTitle()).toBe('Sep, 10');
+      });
+
+      it('changes the title & months format in `month` mode', function() {
+        clickTitleButton();
+
+        expect(getTitle()).toBe('10');
+        expect(getOptions()).toEqual([
+          ['Jan', 'Feb', 'Mar'],
+          ['Apr', 'May', 'Jun'],
+          ['Jul', 'Aug', 'Sep'],
+          ['Oct', 'Nov', 'Dec']
+        ]);
+      });
+
+      it('changes the title, year format & range in `year` mode', function() {
+        clickTitleButton();
+        clickTitleButton();
+
+        expect(getTitle()).toBe('01 - 10');
+        expect(getOptions()).toEqual([
+          ['01', '02', '03', '04', '05'],
+          ['06', '07', '08', '09', '10']
+        ]);
+      });
+
+      it('changes the `starting-day` & day headers & format', function() {
+        expect(getLabels()).toEqual(['Saturday', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']);
+        expect(getOptions(false)).toEqual([
+          ['28', '29', '30', '31', '1', '2', '3'],
+          ['4', '5', '6', '7', '8', '9', '10'],
+          ['11', '12', '13', '14', '15', '16', '17'],
+          ['18', '19', '20', '21', '22', '23', '24'],
+          ['25', '26', '27', '28', '29', '30', '1'],
+          ['2', '3', '4', '5', '6', '7', '8']
+        ]);
+      });
+
+      it('changes initial visibility for weeks', function() {
+        expect(getLabelsRow().find('th').length).toEqual(7);
+        var tr = element.find('tbody').find('tr');
+        for (var i = 0; i < 5; i++) {
+          expect(tr.eq(i).find('td').length).toEqual(7);
+        }
+      });
+
+    });
+
+    describe('setting datepickerPopupConfig', function() {
+      var originalConfig = {};
+      beforeEach(inject(function(uibDatepickerPopupConfig) {
+        angular.extend(originalConfig, uibDatepickerPopupConfig);
+        uibDatepickerPopupConfig.datepickerPopup = 'MM-dd-yyyy';
+
+        element = $compile('<input ng-model="date" uib-datepicker-popup>')($rootScope);
+        $rootScope.$digest();
+      }));
+      afterEach(inject(function(uibDatepickerPopupConfig) {
+        // return it to the original state
+        angular.extend(uibDatepickerPopupConfig, originalConfig);
+      }));
+
+      it('changes date format', function() {
+        expect(element.val()).toEqual('09-30-2010');
+      });
+
+    });
+
+    describe('setting datepickerPopupConfig inside ng-if', function() {
+      var originalConfig = {};
+      beforeEach(inject(function (uibDatepickerPopupConfig) {
+        angular.extend(originalConfig, uibDatepickerPopupConfig);
+        uibDatepickerPopupConfig.datepickerPopup = 'MM-dd-yyyy';
+
+        element = $compile('<div><div ng-if="true"><input ng-model="date" uib-datepicker-popup></div></div>')($rootScope);
+        $rootScope.$digest();
+      }));
+      afterEach(inject(function (uibDatepickerPopupConfig) {
+        // return it to the original state
+        angular.extend(uibDatepickerPopupConfig, originalConfig);
+      }));
+
+      it('changes date format', function () {
+        expect(element.find('input').val()).toEqual('09-30-2010');
+      });
+    });
+
+    describe('as popup', function () {
+      var inputEl, dropdownEl, $document, $sniffer, $timeout;
+
+      function assignElements(wrapElement) {
+        inputEl = wrapElement.find('input');
+        dropdownEl = wrapElement.find('ul');
+        element = dropdownEl.find('table');
+      }
+
+      function changeInputValueTo(el, value) {
+        el.val(value);
+        el.trigger($sniffer.hasEvent('input') ? 'input' : 'change');
+        $rootScope.$digest();
+      }
+
+      describe('initially', function () {
+        beforeEach(inject(function(_$document_, _$sniffer_) {
+          $document = _$document_;
+          $sniffer = _$sniffer_;
+          $rootScope.isopen = true;
+          $rootScope.date = new Date('September 30, 2010 15:30:00');
+          var wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup><div>')($rootScope);
+          $rootScope.$digest();
+          assignElements(wrapElement);
+        }));
+
+        it('does not to display datepicker initially', function() {
+          expect(dropdownEl.length).toBe(0);
+        });
+
+        it('to display the correct value in input', function() {
+          expect(inputEl.val()).toBe('2010-09-30');
+        });
+      });
+
+      describe('initially opened', function() {
+        var wrapElement;
+
+        beforeEach(inject(function(_$document_, _$sniffer_, _$timeout_) {
+          $document = _$document_;
+          $sniffer = _$sniffer_;
+          $timeout = _$timeout_;
+          $rootScope.isopen = true;
+          $rootScope.date = new Date('September 30, 2010 15:30:00');
+          wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup is-open="isopen"><div>')($rootScope);
+          $rootScope.$digest();
+          assignElements(wrapElement);
+        }));
+
+        it('datepicker is displayed', function() {
+          expect(dropdownEl.length).toBe(1);
+        });
+
+        it('renders the calendar correctly', function() {
+          expect(getLabelsRow().css('display')).not.toBe('none');
+          expect(getLabels(true)).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);
+          expect(getOptions(true)).toEqual([
+            ['29', '30', '31', '01', '02', '03', '04'],
+            ['05', '06', '07', '08', '09', '10', '11'],
+            ['12', '13', '14', '15', '16', '17', '18'],
+            ['19', '20', '21', '22', '23', '24', '25'],
+            ['26', '27', '28', '29', '30', '01', '02'],
+            ['03', '04', '05', '06', '07', '08', '09']
+          ]);
+        });
+
+        it('updates the input when a day is clicked', function() {
+          clickOption(17);
+          expect(inputEl.val()).toBe('2010-09-15');
+          expect($rootScope.date).toEqual(new Date('September 15, 2010 15:30:00'));
+        });
+
+        it('should mark the input field dirty when a day is clicked', function() {
+          expect(inputEl).toHaveClass('ng-pristine');
+          clickOption(17);
+          expect(inputEl).toHaveClass('ng-dirty');
+        });
+
+        it('updates the input correctly when model changes', function() {
+          $rootScope.date = new Date('January 10, 1983 10:00:00');
+          $rootScope.$digest();
+          expect(inputEl.val()).toBe('1983-01-10');
+        });
+
+        it('closes the dropdown when a day is clicked', function() {
+          expect(dropdownEl.length).toBe(1);
+
+          clickOption(17);
+          assignElements(wrapElement);
+          expect(dropdownEl.length).toBe(0);
+        });
+
+        it('updates the model & calendar when input value changes', function() {
+          changeInputValueTo(inputEl, '2010-09-15');
+
+          expect($rootScope.date.getFullYear()).toEqual(2010);
+          expect($rootScope.date.getMonth()).toEqual(8);
+          expect($rootScope.date.getDate()).toEqual(15);
+
+          expect(getOptions(true)).toEqual([
+            ['29', '30', '31', '01', '02', '03', '04'],
+            ['05', '06', '07', '08', '09', '10', '11'],
+            ['12', '13', '14', '15', '16', '17', '18'],
+            ['19', '20', '21', '22', '23', '24', '25'],
+            ['26', '27', '28', '29', '30', '01', '02'],
+            ['03', '04', '05', '06', '07', '08', '09']
+          ]);
+          expectSelectedElement(17);
+        });
+
+        it('closes when click outside of calendar', function() {
+          expect(dropdownEl.length).toBe(1);
+
+          $timeout.flush(0);
+          $document.find('body').click();
+          assignElements(wrapElement);
+          expect(dropdownEl.length).toBe(0);
+        });
+
+        it('sets `ng-invalid` for invalid input', function() {
+          changeInputValueTo(inputEl, 'pizza');
+
+          expect(inputEl).toHaveClass('ng-invalid');
+          expect(inputEl).toHaveClass('ng-invalid-date');
+          expect($rootScope.date).toBeUndefined();
+          expect(inputEl.val()).toBe('pizza');
+        });
+
+        it('unsets `ng-invalid` for valid input', function() {
+          changeInputValueTo(inputEl, 'pizza');
+          expect(inputEl).toHaveClass('ng-invalid-date');
+
+          $rootScope.date = new Date('August 11, 2013');
+          $rootScope.$digest();
+          expect(inputEl).not.toHaveClass('ng-invalid');
+          expect(inputEl).not.toHaveClass('ng-invalid-date');
+        });
+
+        describe('focus', function () {
+          beforeEach(function() {
+            var body = $document.find('body');
+            body.append(inputEl);
+            body.append(dropdownEl);
+          });
+
+          afterEach(function() {
+            inputEl.remove();
+            dropdownEl.remove();
+          });
+
+          it('returns to the input when ESC key is pressed in the popup and closes', function() {
+            expect(dropdownEl.length).toBe(1);
+
+            dropdownEl.find('button').eq(0).focus();
+            expect(document.activeElement.tagName).toBe('BUTTON');
+
+            triggerKeyDown(dropdownEl, 'esc');
+            assignElements(wrapElement);
+            expect(dropdownEl.length).toBe(0);
+            expect(document.activeElement.tagName).toBe('INPUT');
+          });
+
+          it('returns to the input when ESC key is pressed in the input and closes', function() {
+            expect(dropdownEl.length).toBe(1);
+
+            dropdownEl.find('button').eq(0).focus();
+            expect(document.activeElement.tagName).toBe('BUTTON');
+
+            triggerKeyDown(inputEl, 'esc');
+            $rootScope.$digest();
+            assignElements(wrapElement);
+            expect(dropdownEl.length).toBe(0);
+            expect(document.activeElement.tagName).toBe('INPUT');
+          });
+
+          it('stops the ESC key from propagating if the dropdown is open, but not when closed', function() {
+            var documentKey = -1;
+            var getKey = function(evt) { documentKey = evt.which; };
+            $document.bind('keydown', getKey);
+
+            triggerKeyDown(inputEl, 'esc');
+            expect(documentKey).toBe(-1);
+
+            triggerKeyDown(inputEl, 'esc');
+            expect(documentKey).toBe(27);
+
+            $document.unbind('keydown', getKey);
+          });
+        });
+
+        describe('works with ngModelOptions', function () {
+          var $timeout;
+
+          beforeEach(inject(function(_$document_, _$sniffer_, _$timeout_) {
+            $document = _$document_;
+            $timeout = _$timeout_;
+            $rootScope.isopen = true;
+            $rootScope.date = new Date('September 30, 2010 15:30:00');
+            var wrapElement = $compile('<div><input ng-model="date" ' +
+              'ng-model-options="{ debounce: 10000 }" ' +
+              'uib-datepicker-popup is-open="isopen"><div>')($rootScope);
+            $rootScope.$digest();
+            assignElements(wrapElement);
+          }));
+
+          it('should change model and update calendar after debounce timeout', function() {
+            changeInputValueTo(inputEl, '1980-03-05');
+
+            expect($rootScope.date.getFullYear()).toEqual(2010);
+            expect($rootScope.date.getMonth()).toEqual(9 - 1);
+            expect($rootScope.date.getDate()).toEqual(30);
+
+            expect(getOptions(true)).toEqual([
+              ['29', '30', '31', '01', '02', '03', '04'],
+              ['05', '06', '07', '08', '09', '10', '11'],
+              ['12', '13', '14', '15', '16', '17', '18'],
+              ['19', '20', '21', '22', '23', '24', '25'],
+              ['26', '27', '28', '29', '30', '01', '02'],
+              ['03', '04', '05', '06', '07', '08', '09']
+            ]);
+
+            // No changes yet
+            $timeout.flush(2000);
+            expect($rootScope.date.getFullYear()).toEqual(2010);
+            expect($rootScope.date.getMonth()).toEqual(9 - 1);
+            expect($rootScope.date.getDate()).toEqual(30);
+
+            expect(getOptions(true)).toEqual([
+              ['29', '30', '31', '01', '02', '03', '04'],
+              ['05', '06', '07', '08', '09', '10', '11'],
+              ['12', '13', '14', '15', '16', '17', '18'],
+              ['19', '20', '21', '22', '23', '24', '25'],
+              ['26', '27', '28', '29', '30', '01', '02'],
+              ['03', '04', '05', '06', '07', '08', '09']
+            ]);
+
+            $timeout.flush(10000);
+            expect($rootScope.date.getFullYear()).toEqual(1980);
+            expect($rootScope.date.getMonth()).toEqual(2);
+            expect($rootScope.date.getDate()).toEqual(5);
+
+            expect(getOptions(true)).toEqual([
+              ['24', '25', '26', '27', '28', '29', '01'],
+              ['02', '03', '04', '05', '06', '07', '08'],
+              ['09', '10', '11', '12', '13', '14', '15'],
+              ['16', '17', '18', '19', '20', '21', '22'],
+              ['23', '24', '25', '26', '27', '28', '29'],
+              ['30', '31', '01', '02', '03', '04', '05']
+            ]);
+            expectSelectedElement( 10 );
+          });
+        });
+
+        describe('works with HTML5 date input types', function() {
+          var date2 = new Date('October 1, 2010 12:34:56.789');
+          beforeEach(inject(function(_$document_) {
+            $document = _$document_;
+            $rootScope.isopen = true;
+            $rootScope.date = new Date('September 30, 2010 15:30:00');
+          }));
+
+          it('works as date', function() {
+            setupInputWithType('date');
+            expect(dropdownEl.length).toBe(1);
+            expect(inputEl.val()).toBe('2010-09-30');
+
+            changeInputValueTo(inputEl, '1980-03-05');
+
+            expect($rootScope.date.getFullYear()).toEqual(1980);
+            expect($rootScope.date.getMonth()).toEqual(2);
+            expect($rootScope.date.getDate()).toEqual(5);
+
+            expect(getOptions(true)).toEqual([
+              ['24', '25', '26', '27', '28', '29', '01'],
+              ['02', '03', '04', '05', '06', '07', '08'],
+              ['09', '10', '11', '12', '13', '14', '15'],
+              ['16', '17', '18', '19', '20', '21', '22'],
+              ['23', '24', '25', '26', '27', '28', '29'],
+              ['30', '31', '01', '02', '03', '04', '05']
+            ]);
+            expect(selectedElementIndex()).toEqual(10);
+          });
+
+          it('works as datetime-local', function() {
+            setupInputWithType('datetime-local');
+            expect(inputEl.val()).toBe('2010-09-30T15:30:00.000');
+
+            changeInputValueTo(inputEl, '1980-03-05T12:34:56.000');
+
+            expect($rootScope.date.getFullYear()).toEqual(1980);
+            expect($rootScope.date.getMonth()).toEqual(2);
+            expect($rootScope.date.getDate()).toEqual(5);
+
+            expect(getOptions(true)).toEqual([
+              ['24', '25', '26', '27', '28', '29', '01'],
+              ['02', '03', '04', '05', '06', '07', '08'],
+              ['09', '10', '11', '12', '13', '14', '15'],
+              ['16', '17', '18', '19', '20', '21', '22'],
+              ['23', '24', '25', '26', '27', '28', '29'],
+              ['30', '31', '01', '02', '03', '04', '05']
+            ]);
+            expect(selectedElementIndex()).toEqual(10);
+          });
+
+          it('works as month', function() {
+            setupInputWithType('month');
+            expect(inputEl.val()).toBe('2010-09');
+
+            changeInputValueTo(inputEl, '1980-03');
+
+            expect($rootScope.date.getFullYear()).toEqual(1980);
+            expect($rootScope.date.getMonth()).toEqual(2);
+            expect($rootScope.date.getDate()).toEqual(30);
+
+            expect(getOptions()).toEqual([
+              ['January', 'February', 'March'],
+              ['April', 'May', 'June'],
+              ['July', 'August', 'September'],
+              ['October', 'November', 'December']
+            ]);
+            expect(selectedElementIndex()).toEqual(2);
+          });
+
+          function setupInputWithType(type) {
+            var wrapElement = $compile('<div><input type="' +
+              type + '" ng-model="date" uib-datepicker-popup is-open="isopen"><div>')($rootScope);
+            $rootScope.$digest();
+            assignElements(wrapElement);
+          }
+        });
+
+      });
+
+      describe('attribute `datepickerOptions`', function() {
+        describe('show-weeks', function() {
+          beforeEach(function() {
+            $rootScope.opts = {
+              'show-weeks': false
+            };
+            var wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup datepicker-options="opts" is-open="true"></div>')($rootScope);
+            $rootScope.$digest();
+            assignElements(wrapElement);
+          });
+
+          it('hides week numbers based on variable', function() {
+            expect(getLabelsRow().find('th').length).toEqual(7);
+            var tr = element.find('tbody').find('tr');
+            for (var i = 0; i < 5; i++) {
+              expect(tr.eq(i).find('td').length).toEqual(7);
+            }
+          });
+        });
+
+        describe('init-date', function(){
+          beforeEach(function() {
+            $rootScope.date = null;
+            $rootScope.opts = {
+              'initDate': new Date('November 9, 1980')
+            };
+            var wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup datepicker-options="opts" is-open="true"></div>')($rootScope);
+            $rootScope.$digest();
+            assignElements(wrapElement);
+          });
+
+          it('does not alter the model', function() {
+            expect($rootScope.date).toBe(null);
+          });
+
+          it('shows the correct title', function() {
+            expect(getTitle()).toBe('November 1980');
+          });
+        });
+      });
+
+      describe('attribute `init-date`', function() {
+        beforeEach(function() {
+          $rootScope.date = null;
+          $rootScope.initDate = new Date('November 9, 1980');
+        });
+
+        describe('when initially set', function() {
+          beforeEach(function() {
+            var wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup init-date="initDate" is-open="true"></div>')($rootScope);
+            $rootScope.$digest();
+            assignElements(wrapElement);
+          });
+
+          it('does not alter the model', function() {
+            expect($rootScope.date).toBe(null);
+          });
+
+          it('shows the correct title', function() {
+            expect(getTitle()).toBe('November 1980');
+          });
+        });
+
+        describe('when modified before date selected.', function() {
+          beforeEach(function() {
+            var wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup init-date="initDate" is-open="true"></div>')($rootScope);
+            $rootScope.$digest();
+            assignElements(wrapElement);
+
+            $rootScope.initDate = new Date('December 20, 1981');
+            $rootScope.$digest();
+          });
+
+          it('does not alter the model', function() {
+            expect($rootScope.date).toBe(null);
+          });
+
+          it('shows the correct title', function() {
+            expect(getTitle()).toBe('December 1981');
+          });
+        });
+
+        describe('when modified after date selected.', function() {
+          beforeEach(function() {
+            var wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup init-date="initDate" is-open="true"></div>')($rootScope);
+            $rootScope.$digest();
+            assignElements(wrapElement);
+            $rootScope.date = new Date('April 1, 1982');
+            $rootScope.initDate = new Date('December 20, 1981');
+            $rootScope.$digest();
+          });
+
+          it('does not alter the model', function() {
+            expect($rootScope.date).toEqual(new Date('April 1, 1982'));
+          });
+
+          it('shows the correct title', function() {
+            expect(getTitle()).toBe('April 1982');
+          });
+        });
+      });
+
+      describe('toggles programatically by `open` attribute', function() {
+        var wrapElement;
+
+        beforeEach(inject(function() {
+          $rootScope.open = true;
+          wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup is-open="open"><div>')($rootScope);
+          $rootScope.$digest();
+          assignElements(wrapElement);
+        }));
+
+        it('to display initially', function() {
+          expect(dropdownEl.length).toBe(1);
+        });
+
+        it('to close / open from scope variable', function() {
+          expect(dropdownEl.length).toBe(1);
+          $rootScope.open = false;
+          $rootScope.$digest();
+          assignElements(wrapElement);
+          expect(dropdownEl.length).toBe(0);
+
+          $rootScope.open = true;
+          $rootScope.$digest();
+          assignElements(wrapElement);
+          expect(dropdownEl.length).toBe(1);
+        });
+      });
+
+      describe('custom format', function() {
+        beforeEach(inject(function() {
+          var wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup="dd-MMMM-yyyy" is-open="true"><div>')($rootScope);
+          $rootScope.$digest();
+          assignElements(wrapElement);
+        }));
+
+        it('to display the correct value in input', function() {
+          expect(inputEl.val()).toBe('30-September-2010');
+        });
+
+        it('updates the input when a day is clicked', function() {
+          clickOption(17);
+          expect(inputEl.val()).toBe('15-September-2010');
+          expect($rootScope.date).toEqual(new Date('September 15, 2010 15:30:00'));
+        });
+
+        it('updates the input correctly when model changes', function() {
+          $rootScope.date = new Date('January 10, 1983 10:00:00');
+          $rootScope.$digest();
+          expect(inputEl.val()).toBe('10-January-1983');
+        });
+      });
+
+      describe('dynamic custom format', function() {
+        beforeEach(inject(function() {
+          $rootScope.format = 'dd-MMMM-yyyy';
+          var wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup="{{format}}" is-open="true"><div>')($rootScope);
+          $rootScope.$digest();
+          assignElements(wrapElement);
+        }));
+
+        it('to display the correct value in input', function() {
+          expect(inputEl.val()).toBe('30-September-2010');
+        });
+
+        it('updates the input when a day is clicked', function() {
+          clickOption(17);
+          expect(inputEl.val()).toBe('15-September-2010');
+          expect($rootScope.date).toEqual(new Date('September 15, 2010 15:30:00'));
+        });
+
+        it('updates the input correctly when model changes', function() {
+          $rootScope.date = new Date('August 11, 2013 09:09:00');
+          $rootScope.$digest();
+          expect(inputEl.val()).toBe('11-August-2013');
+        });
+
+        it('updates the input correctly when format changes', function() {
+          $rootScope.format = 'dd/MM/yyyy';
+          $rootScope.$digest();
+          expect(inputEl.val()).toBe('30/09/2010');
+        });
+      });
+
+      describe('european format', function() {
+        it('dd.MM.yyyy', function() {
+          var wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup="dd.MM.yyyy"><div>')($rootScope);
+          $rootScope.$digest();
+          assignElements(wrapElement);
+
+          changeInputValueTo(inputEl, '11.08.2013');
+          expect($rootScope.date.getFullYear()).toEqual(2013);
+          expect($rootScope.date.getMonth()).toEqual(7);
+          expect($rootScope.date.getDate()).toEqual(11);
+        });
+      });
+
+      describe('`close-on-date-selection` attribute', function() {
+        var wrapElement;
+        beforeEach(inject(function() {
+          $rootScope.close = false;
+          wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup close-on-date-selection="close" is-open="true"><div>')($rootScope);
+          $rootScope.$digest();
+          assignElements(wrapElement);
+        }));
+
+        it('does not close the dropdown when a day is clicked', function() {
+          clickOption(17);
+          assignElements(wrapElement);
+          expect(dropdownEl.length).toBe(1);
+        });
+      });
+
+      describe('button bar', function() {
+        var buttons, buttonBarElement;
+
+        function assignButtonBar() {
+          buttonBarElement = dropdownEl.find('li').eq(-1);
+          buttons = buttonBarElement.find('button');
+        }
+
+        describe('', function() {
+          var wrapElement;
+
+          beforeEach(inject(function() {
+            $rootScope.isopen = true;
+            wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup is-open="isopen"><div>')($rootScope);
+            $rootScope.$digest();
+            assignElements(wrapElement);
+            assignButtonBar();
+          }));
+
+          it('should exist', function() {
+            expect(dropdownEl.length).toBe(1);
+            expect(dropdownEl.find('li').length).toBe(2);
+          });
+
+          it('should have three buttons', function() {
+            expect(buttons.length).toBe(3);
+
+            expect(buttons.eq(0).text()).toBe('Today');
+            expect(buttons.eq(1).text()).toBe('Clear');
+            expect(buttons.eq(2).text()).toBe('Done');
+          });
+
+          it('should have a button to set today date without altering time part', function() {
+            var today = new Date();
+            buttons.eq(0).click();
+            expect($rootScope.date.getFullYear()).toBe(today.getFullYear());
+            expect($rootScope.date.getMonth()).toBe(today.getMonth());
+            expect($rootScope.date.getDate()).toBe(today.getDate());
+
+            expect($rootScope.date.getHours()).toBe(15);
+            expect($rootScope.date.getMinutes()).toBe(30);
+            expect($rootScope.date.getSeconds()).toBe(0);
+          });
+
+          it('should have a button to set today date if blank', function() {
+            $rootScope.date = null;
+            $rootScope.$digest();
+
+            var today = new Date();
+            buttons.eq(0).click();
+            expect($rootScope.date.getFullYear()).toBe(today.getFullYear());
+            expect($rootScope.date.getMonth()).toBe(today.getMonth());
+            expect($rootScope.date.getDate()).toBe(today.getDate());
+
+            expect($rootScope.date.getHours()).toBe(0);
+            expect($rootScope.date.getMinutes()).toBe(0);
+            expect($rootScope.date.getSeconds()).toBe(0);
+          });
+
+          it('should have a button to clear value', function() {
+            buttons.eq(1).click();
+            expect($rootScope.date).toBe(null);
+          });
+
+          it('should have a button to close calendar', function() {
+            buttons.eq(2).click();
+            assignElements(wrapElement);
+            expect(dropdownEl.length).toBe(0);
+          });
+        });
+
+        describe('customization', function() {
+          it('should change text from attributes', function() {
+            $rootScope.clearText = 'Null it!';
+            $rootScope.close = 'Close';
+            var wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup current-text="Now" clear-text="{{clearText}}" close-text="{{close}}ME" is-open="true"><div>')($rootScope);
+            $rootScope.$digest();
+            assignElements(wrapElement);
+            assignButtonBar();
+
+            expect(buttons.eq(0).text()).toBe('Now');
+            expect(buttons.eq(1).text()).toBe('Null it!');
+            expect(buttons.eq(2).text()).toBe('CloseME');
+          });
+
+          it('should disable today button if before min date', function() {
+            $rootScope.minDate = new Date().setDate(new Date().getDate() + 1);
+            var wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup min-date="minDate" is-open="true"><div>')($rootScope);
+            $rootScope.$digest();
+            assignElements(wrapElement);
+            assignButtonBar();
+
+            expect(buttons.eq(0).prop('disabled')).toBe(true);
+          });
+
+          it('should disable today button if after max date', function() {
+            $rootScope.maxDate = new Date().setDate(new Date().getDate() - 2);
+            var wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup max-date="maxDate" is-open="true"><div>')($rootScope);
+            $rootScope.$digest();
+            assignElements(wrapElement);
+            assignButtonBar();
+
+            expect(buttons.eq(0).prop('disabled')).toBe(true);
+          });
+
+          it('should remove bar', function() {
+            $rootScope.showBar = false;
+            var wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup show-button-bar="showBar" is-open="true"><div>')($rootScope);
+            $rootScope.$digest();
+            assignElements(wrapElement);
+            expect(dropdownEl.find('li').length).toBe(1);
+          });
+
+          it('should hide weeks column on popup', function() {
+            var wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup show-weeks="false" is-open="true"><div>')($rootScope);
+            $rootScope.$digest();
+            assignElements(wrapElement);
+
+            expect(getLabelsRow().find('th').length).toEqual(7);
+            var tr = element.find('tbody').find('tr');
+            for (var i = 0; i < 5; i++) {
+              expect(tr.eq(i).find('td').length).toEqual(7);
+            }
+          });
+
+          it('should show weeks column on popup', function() {
+            var wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup show-weeks="true" is-open="true"><div>')($rootScope);
+            $rootScope.$digest();
+            assignElements(wrapElement);
+
+            expect(getLabelsRow().find('th').eq(0)).not.toBeHidden();
+            var tr = element.find('tbody').find('tr');
+            for (var i = 0; i < 5; i++) {
+              expect(tr.eq(i).find('td').eq(0)).not.toBeHidden();
+            }
+          });
+        });
+
+        describe('`ng-change`', function() {
+          beforeEach(inject(function() {
+            $rootScope.changeHandler = jasmine.createSpy('changeHandler');
+            var wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup ng-change="changeHandler()" is-open="true"><div>')($rootScope);
+            $rootScope.$digest();
+            assignElements(wrapElement);
+            assignButtonBar();
+          }));
+
+          it('should be called when `today` is clicked', function() {
+            buttons.eq(0).click();
+            expect($rootScope.changeHandler).toHaveBeenCalled();
+          });
+
+          it('should be called when `clear` is clicked', function() {
+            buttons.eq(1).click();
+            expect($rootScope.changeHandler).toHaveBeenCalled();
+          });
+
+          it('should not be called when `close` is clicked', function() {
+            buttons.eq(2).click();
+            expect($rootScope.changeHandler).not.toHaveBeenCalled();
+          });
+        });
+      });
+
+      describe('use with `ng-required` directive', function() {
+        describe('`ng-required is true`', function() {
+          beforeEach(inject(function() {
+            $rootScope.date = '';
+            var wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup ng-required="true"><div>')($rootScope);
+            $rootScope.$digest();
+            assignElements(wrapElement);
+          }));
+
+          it('should be invalid initially and when no date', function() {
+            expect(inputEl.hasClass('ng-invalid')).toBeTruthy();
+          });
+
+          it('should be valid if model has been specified', function() {
+            $rootScope.date = new Date();
+            $rootScope.$digest();
+            expect(inputEl.hasClass('ng-valid')).toBeTruthy();
+          });
+
+          it('should be valid if model value is a valid timestamp', function() {
+            $rootScope.date = Date.now();
+            $rootScope.$digest();
+            expect(inputEl.hasClass('ng-valid')).toBeTruthy();
+          });
+        });
+        describe('`ng-required is false`', function() {
+          beforeEach(inject(function() {
+            $rootScope.date = '';
+            var wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup ng-required="false"><div>')($rootScope);
+            $rootScope.$digest();
+            assignElements(wrapElement);
+          }));
+
+          it('should be valid initially and when no date', function() {
+            expect(inputEl.hasClass('ng-valid')).toBeTruthy();
+          });
+        });
+      });
+
+      describe('use with `ng-change` directive', function() {
+        beforeEach(inject(function() {
+          $rootScope.changeHandler = jasmine.createSpy('changeHandler');
+          $rootScope.date = new Date('09/16/2010');
+          var wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup ng-required="true" ng-change="changeHandler()" is-open="true"><div>')($rootScope);
+          $rootScope.$digest();
+          assignElements(wrapElement);
+        }));
+
+        it('should not be called initially', function() {
+          expect($rootScope.changeHandler).not.toHaveBeenCalled();
+        });
+
+        it('should be called when a day is clicked', function() {
+          clickOption(17);
+          expect($rootScope.changeHandler).toHaveBeenCalled();
+        });
+
+        it('should not be called when model changes programatically', function() {
+          $rootScope.date = new Date();
+          $rootScope.$digest();
+          expect($rootScope.changeHandler).not.toHaveBeenCalled();
+        });
+      });
+
+      describe('with datepicker-popup-template-url', function() {
+        beforeEach(function() {
+          $rootScope.date = new Date();
+        });
+
+        afterEach(function () {
+          $document.find('body').find('.dropdown-menu').remove();
+        });
+
+        it('should allow custom templates for the popup', function() {
+          $templateCache.put('foo/bar.html', '<div>baz</div>');
+
+          var elm = angular.element('<div><input ng-model="date" uib-datepicker-popup datepicker-popup-template-url="foo/bar.html" is-open="true"></div>');
+
+          $compile(elm)($rootScope);
+          $rootScope.$digest();
+
+          expect(elm.children().eq(1).html()).toBe('baz');
+        });
+      });
+
+      describe('with datepicker-template-url', function() {
+        beforeEach(function() {
+          $rootScope.date = new Date();
+        });
+
+        afterEach(function() {
+          $document.find('body').find('.dropdown-menu').remove();
+        });
+
+        it('should allow custom templates for the datepicker', function() {
+          $templateCache.put('foo/bar.html', '<div>baz</div>');
+
+          var elm = angular.element('<div><input ng-model="date" uib-datepicker-popup datepicker-template-url="foo/bar.html" is-open="true"></div>');
+
+          $compile(elm)($rootScope);
+          $rootScope.$digest();
+
+          var datepicker = elm.find('[uib-datepicker]');
+
+          expect(datepicker.html()).toBe('baz');
+        });
+      });
+
+      describe('with an append-to-body attribute', function() {
+        beforeEach(function() {
+          $rootScope.date = new Date();
+        });
+
+        afterEach(function() {
+          $document.find('body').children().remove();
+        });
+
+        it('should append to the body', function() {
+          var $body = $document.find('body'),
+              bodyLength = $body.children().length,
+              elm = angular.element(
+                '<div><input uib-datepicker-popup ng-model="date" datepicker-append-to-body="true" is-open="true" /></div>'
+              );
+          $compile(elm)($rootScope);
+          $rootScope.$digest();
+
+          expect($body.children().length).toEqual(bodyLength + 1);
+          expect(elm.children().length).toEqual(1);
+        });
+        it('should be removed on scope destroy', function() {
+          var $body = $document.find('body'),
+              bodyLength = $body.children().length,
+              isolatedScope = $rootScope.$new(),
+              elm = angular.element(
+                '<input uib-datepicker-popup ng-model="date" datepicker-append-to-body="true" is-open="true" />'
+              );
+          $compile(elm)(isolatedScope);
+          isolatedScope.$digest();
+          expect($body.children().length).toEqual(bodyLength + 1);
+          isolatedScope.$destroy();
+          expect($body.children().length).toEqual(bodyLength);
+        });
+      });
+
+      describe('with setting datepickerConfig.showWeeks to false', function() {
+        var originalConfig = {};
+        beforeEach(inject(function(uibDatepickerConfig) {
+          angular.extend(originalConfig, uibDatepickerConfig);
+          uibDatepickerConfig.showWeeks = false;
+
+          var wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup is-open="true"><div>')($rootScope);
+          $rootScope.$digest();
+          assignElements(wrapElement);
+        }));
+        afterEach(inject(function(uibDatepickerConfig) {
+          // return it to the original state
+          angular.extend(uibDatepickerConfig, originalConfig);
+        }));
+
+        it('changes initial visibility for weeks', function() {
+          expect(getLabelsRow().find('th').length).toEqual(7);
+          var tr = element.find('tbody').find('tr');
+          for (var i = 0; i < 5; i++) {
+            expect(tr.eq(i).find('td').length).toEqual(7);
+          }
+        });
+      });
+
+      describe('`datepicker-mode`', function() {
+        beforeEach(inject(function() {
+          $rootScope.date = new Date('August 11, 2013');
+          $rootScope.mode = 'month';
+          var wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup datepicker-mode="mode" is-open="true"></div>')($rootScope);
+          $rootScope.$digest();
+          assignElements(wrapElement);
+        }));
+
+        it('shows the correct title', function() {
+          expect(getTitle()).toBe('2013');
+        });
+
+        it('updates binding', function() {
+          clickTitleButton();
+          expect($rootScope.mode).toBe('year');
+        });
+      });
+
+      describe('attribute `initDate`', function() {
+        var weekHeader, weekElement;
+        beforeEach(function() {
+          $rootScope.date = null;
+          $rootScope.initDate = new Date('November 9, 1980');
+          var wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup init-date="initDate" is-open="true"></div>')($rootScope);
+          $rootScope.$digest();
+          assignElements(wrapElement);
+        });
+
+        it('should not alter the model', function() {
+          expect($rootScope.date).toBe(null);
+        });
+
+        it('shows the correct title', function() {
+          expect(getTitle()).toBe('November 1980');
+        });
+      });
+
+      describe('attribute `onOpenFocus`', function() {
+        beforeEach(function() {
+          $rootScope.date = null;
+          $rootScope.isopen = false;
+          var wrapElement = $compile('<div><input ng-model="date" uib-datepicker-popup on-open-focus="false" is-open="isopen"></div>')($rootScope);
+          $rootScope.$digest();
+          assignElements(wrapElement);
+        });
+
+        it('should remain focused on the input', function() {
+          var focused = true;
+          expect(dropdownEl.length).toBe(0);
+
+          inputEl[0].focus();
+          inputEl.on('blur', function() {
+            focused = false;
+          });
+          $rootScope.isopen = true;
+          $rootScope.$digest();
+
+          expect(inputEl.parent().find('.dropdown-menu').length).toBe(1);
+          expect(focused).toBe(true);
+        });
+      });
+    });
+
+    describe('with empty initial state', function() {
+      beforeEach(inject(function() {
+        $rootScope.date = null;
+        element = $compile('<uib-datepicker ng-model="date"></uib-datepicker>')($rootScope);
+        $rootScope.$digest();
+      }));
+
+      it('is has a `<table>` element', function() {
+        expect(element.find('table').length).toBe(1);
+      });
+
+      it('is shows rows with days', function() {
+        expect(element.find('tbody').find('tr').length).toBeGreaterThan(3);
+      });
+
+      it('sets default 00:00:00 time for selected date', function() {
+        $rootScope.date = new Date('August 1, 2013');
+        $rootScope.$digest();
+        $rootScope.date = null;
+        $rootScope.$digest();
+
+        clickOption(14);
+        expect($rootScope.date).toEqual(new Date('August 11, 2013 00:00:00'));
+      });
+    });
+
+    describe('`init-date`', function() {
+      beforeEach(inject(function() {
+        $rootScope.date = null;
+        $rootScope.initDate = new Date('November 9, 1980');
+        element = $compile('<uib-datepicker ng-model="date" init-date="initDate"></uib-datepicker>')($rootScope);
+        $rootScope.$digest();
+      }));
+
+      it('does not alter the model', function() {
+        expect($rootScope.date).toBe(null);
+      });
+
+      it('shows the correct title', function() {
+        expect(getTitle()).toBe('November 1980');
+      });
+    });
+
+    describe('`datepicker-mode`', function() {
+      beforeEach(inject(function() {
+        $rootScope.date = new Date('August 11, 2013');
+        $rootScope.mode = 'month';
+        element = $compile('<uib-datepicker ng-model="date" datepicker-mode="mode"></uib-datepicker>')($rootScope);
+        $rootScope.$digest();
+      }));
+
+      it('shows the correct title', function() {
+        expect(getTitle()).toBe('2013');
+      });
+
+      it('updates binding', function() {
+        clickTitleButton();
+        expect($rootScope.mode).toBe('year');
+      });
+    });
+
+    describe('`min-mode`', function() {
+      beforeEach(inject(function() {
+        $rootScope.date = new Date('August 11, 2013');
+        $rootScope.mode = 'month';
+        $rootScope.minMode = 'month';
+        element = $compile('<uib-datepicker ng-model="date" min-mode="minMode" datepicker-mode="mode"></uib-datepicker>')($rootScope);
+        $rootScope.$digest();
+      }));
+
+      it('does not move below it', function() {
+        expect(getTitle()).toBe('2013');
+        clickOption( 5 );
+        expect(getTitle()).toBe('2013');
+        clickTitleButton();
+        expect(getTitle()).toBe('2001 - 2020');
+        $rootScope.minMode = 'year';
+        $rootScope.$digest();
+        clickOption( 5 );
+        expect(getTitle()).toBe('2001 - 2020');
+      });
+
+      it('updates current mode if necessary', function() {
+        expect(getTitle()).toBe('2013');
+        $rootScope.minMode = 'year';
+        $rootScope.$digest();
+        expect(getTitle()).toBe('2001 - 2020');
+      });
+    });
+
+    describe('`max-mode`', function() {
+      beforeEach(inject(function() {
+        $rootScope.date = new Date('August 11, 2013');
+        $rootScope.maxMode = 'month';
+        element = $compile('<uib-datepicker ng-model="date" max-mode="maxMode"></uib-datepicker>')($rootScope);
+        $rootScope.$digest();
+      }));
+
+      it('does not move above it', function() {
+        expect(getTitle()).toBe('August 2013');
+        clickTitleButton();
+        expect(getTitle()).toBe('2013');
+        clickTitleButton();
+        expect(getTitle()).toBe('2013');
+        clickOption( 10 );
+        expect(getTitle()).toBe('November 2013');
+        $rootScope.maxMode = 'day';
+        $rootScope.$digest();
+        clickTitleButton();
+        expect(getTitle()).toBe('November 2013');
+      });
+
+      it('disables the title button at it', function() {
+        expect(getTitleButton().prop('disabled')).toBe(false);
+        clickTitleButton();
+        expect(getTitleButton().prop('disabled')).toBe(true);
+        clickTitleButton();
+        expect(getTitleButton().prop('disabled')).toBe(true);
+        clickOption( 10 );
+        expect(getTitleButton().prop('disabled')).toBe(false);
+        $rootScope.maxMode = 'day';
+        $rootScope.$digest();
+        expect(getTitleButton().prop('disabled')).toBe(true);
+      });
+
+      it('updates current mode if necessary', function() {
+        expect(getTitle()).toBe('August 2013');
+        clickTitleButton();
+        expect(getTitle()).toBe('2013');
+        $rootScope.maxMode = 'day';
+        $rootScope.$digest();
+        expect(getTitle()).toBe('August 2013');
+      });
+    });
+
+    describe('with an ngModelController having formatters and parsers', function() {
+      beforeEach(inject(function() {
+        // Custom date object.
+        $rootScope.date = { type: 'date', date: 'April 1, 2015 00:00:00' };
+
+        // Use dateModel directive to add formatters and parsers to the
+        // ngModelController that translate the custom date object.
+        element = $compile('<uib-datepicker ng-model="date" date-model></uib-datepicker>')($rootScope);
+        $rootScope.$digest();
+      }));
+
+      it('updates the view', function() {
+        $rootScope.date = { type: 'date', date: 'April 15, 2015 00:00:00' };
+        $rootScope.$digest();
+
+        expectSelectedElement(17);
+      });
+
+      it('updates the model', function() {
+        clickOption(17);
+
+        expect($rootScope.date.type).toEqual('date');
+        expect(new Date($rootScope.date.date)).toEqual(new Date('April 15, 2015 00:00:00'));
+      });
+    });
+
+    describe('thursdays determine week count', function() {
+      beforeEach(inject(function() {
+        $rootScope.date = new Date('June 07, 2014');
+      }));
+
+      it('with the default starting day (sunday)', function() {
+        element = $compile('<uib-datepicker ng-model="date"></uib-datepicker>')($rootScope);
+        $rootScope.$digest();
+
+        expect(getWeeks()).toEqual(['23', '24', '25', '26', '27', '28']);
+      });
+
+      describe('when starting date', function() {
+        it('is monday', function() {
+          element = $compile('<uib-datepicker ng-model="date" starting-day="1"></uib-datepicker>')($rootScope);
+          $rootScope.$digest();
+
+          expect(getWeeks()).toEqual(['22', '23', '24', '25', '26', '27']);
+        });
+
+        it('is thursday', function() {
+          element = $compile('<uib-datepicker ng-model="date" starting-day="4"></uib-datepicker>')($rootScope);
+          $rootScope.$digest();
+
+          expect(getWeeks()).toEqual(['22', '23', '24', '25', '26', '27']);
+        });
+
+        it('is saturday', function() {
+          element = $compile('<uib-datepicker ng-model="date" starting-day="6"></uib-datepicker>')($rootScope);
+          $rootScope.$digest();
+
+          expect(getWeeks()).toEqual(['23', '24', '25', '26', '27', '28']);
+        });
+      });
+
+      describe('first week in january', function() {
+        it('in current year', function() {
+          $rootScope.date = new Date('January 07, 2014');
+          element = $compile('<uib-datepicker ng-model="date"></uib-datepicker>')($rootScope);
+          $rootScope.$digest();
+
+          expect(getWeeks()).toEqual(['1', '2', '3', '4', '5', '6']);
+        });
+
+        it('in last year', function() {
+          $rootScope.date = new Date('January 07, 2010');
+          element = $compile('<uib-datepicker ng-model="date"></uib-datepicker>')($rootScope);
+          $rootScope.$digest();
+
+          expect(getWeeks()).toEqual(['53', '1', '2', '3', '4', '5']);
+        });
+      });
+
+      describe('last week(s) in december', function() {
+        beforeEach(inject(function() {
+         $rootScope.date = new Date('December 07, 2014');
+        }));
+
+        it('in next year', function() {
+           element = $compile('<uib-datepicker ng-model="date"></uib-datepicker>')($rootScope);
+          $rootScope.$digest();
+
+          expect(getWeeks()).toEqual(['49', '50', '51', '52', '1', '2']);
+        });
+      });
+    });
+  });
+});
+
+/* deprecation tests below */
+
+describe('datepicker deprecation', function() {
+  beforeEach(module('ui.bootstrap.datepicker'));
+  beforeEach(module('template/datepicker/datepicker.html'));
+  beforeEach(module('template/datepicker/day.html'));
+  beforeEach(module('template/datepicker/month.html'));
+  beforeEach(module('template/datepicker/year.html'));
+  beforeEach(module('template/datepicker/popup.html'));
+
+  it('should suppress warning', function() {
+    module(function($provide) {
+      $provide.value('$datepickerSuppressWarning', true);
+    });
+
+    inject(function($compile, $log, $rootScope) {
+      spyOn($log, 'warn');
+
+      var element = $compile('<datepicker ng-model="date"></datepicker>')($rootScope);
+      $rootScope.$digest();
+      expect($log.warn.calls.count()).toBe(0);
+    });
+  });
+
+  it('should give warning by default', inject(function($log) {
+    spyOn($log, 'warn');
+
+    inject(function($compile, $templateCache, $rootScope) {
+      var datepickerTemplate =
+        '<div ng-switch="datepickerMode" role="application" ng-keydown="keydown($event)">' +
+        '<daypicker ng-switch-when="day" tabindex="0"></daypicker>' +
+        '<monthpicker ng-switch-when="month" tabindex="0"></monthpicker>' +
+        '<yearpicker ng-switch-when="year" tabindex="0"></yearpicker> ' +
+        '</div>';
+      $templateCache.put('template/datepicker/datepicker.html', datepickerTemplate);
+
+      var element = $compile('<datepicker ng-model="date"></datepicker>')($rootScope);
+      $rootScope.$digest();
+
+      expect($log.warn.calls.count()).toBe(3);
+      expect($log.warn.calls.argsFor(0)).toEqual(['DatepickerController is now deprecated. Use UibDatepickerController instead.']);
+      expect($log.warn.calls.argsFor(1)).toEqual(['datepicker is now deprecated. Use uib-datepicker instead.']);
+      expect($log.warn.calls.argsFor(2)).toEqual(['daypicker is now deprecated. Use uib-daypicker instead.']);
+
+      $log.warn.calls.reset();
+
+      element = $compile('<datepicker datepicker-mode="\'month\'" ng-model="date"></datepicker>')($rootScope);
+      $rootScope.$digest();
+
+      expect($log.warn.calls.count()).toBe(3);
+      expect($log.warn.calls.argsFor(0)).toEqual(['DatepickerController is now deprecated. Use UibDatepickerController instead.']);
+      expect($log.warn.calls.argsFor(1)).toEqual(['datepicker is now deprecated. Use uib-datepicker instead.']);
+      expect($log.warn.calls.argsFor(2)).toEqual(['monthpicker is now deprecated. Use uib-monthpicker instead.']);
+
+      $log.warn.calls.reset();
+
+      element = $compile('<datepicker datepicker-mode="\'year\'" ng-model="date"></datepicker>')($rootScope);
+      $rootScope.$digest();
+
+      expect($log.warn.calls.count()).toBe(3);
+      expect($log.warn.calls.argsFor(0)).toEqual(['DatepickerController is now deprecated. Use UibDatepickerController instead.']);
+      expect($log.warn.calls.argsFor(1)).toEqual(['datepicker is now deprecated. Use uib-datepicker instead.']);
+      expect($log.warn.calls.argsFor(2)).toEqual(['yearpicker is now deprecated. Use uib-yearpicker instead.']);
+
+      $log.warn.calls.reset();
+
+      element = $compile('<input type="date" datepicker-popup ng-model="dt" />')($rootScope);
+      $rootScope.$digest();
+
+      expect($log.warn.calls.count()).toBe(1);
+      expect($log.warn.calls.argsFor(0)).toEqual(['datepicker-popup is now deprecated. Use uib-datepicker-popup instead.']);
+    });
+  }));
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/dropdown/docs/demo.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/dropdown/docs/demo.html
new file mode 100644
index 0000000..7d6f146
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/dropdown/docs/demo.html
@@ -0,0 +1,98 @@
+
+<div ng-controller="DropdownCtrl">
+    <!-- Simple dropdown -->
+    <span uib-dropdown on-toggle="toggled(open)">
+      <a href id="simple-dropdown" uib-dropdown-toggle>
+        Click me for a dropdown, yo!
+      </a>
+      <ul class="uib-dropdown-menu" aria-labelledby="simple-dropdown">
+        <li ng-repeat="choice in items">
+          <a href>{{choice}}</a>
+        </li>
+      </ul>
+    </span>
+
+    <!-- Single button -->
+    <div class="btn-group" uib-dropdown is-open="status.isopen">
+      <button id="single-button" type="button" class="btn btn-primary" uib-dropdown-toggle ng-disabled="disabled">
+        Button dropdown <span class="caret"></span>
+      </button>
+      <ul class="uib-dropdown-menu" role="menu" aria-labelledby="single-button">
+        <li role="menuitem"><a href="#">Action</a></li>
+        <li role="menuitem"><a href="#">Another action</a></li>
+        <li role="menuitem"><a href="#">Something else here</a></li>
+        <li class="divider"></li>
+        <li role="menuitem"><a href="#">Separated link</a></li>
+      </ul>
+    </div>
+
+    <!-- Split button -->
+    <div class="btn-group" uib-dropdown>
+      <button id="split-button" type="button" class="btn btn-danger">Action</button>
+      <button type="button" class="btn btn-danger" uib-dropdown-toggle>
+        <span class="caret"></span>
+        <span class="sr-only">Split button!</span>
+      </button>
+      <ul class="uib-dropdown-menu" role="menu" aria-labelledby="split-button">
+        <li role="menuitem"><a href="#">Action</a></li>
+        <li role="menuitem"><a href="#">Another action</a></li>
+        <li role="menuitem"><a href="#">Something else here</a></li>
+        <li class="divider"></li>
+        <li role="menuitem"><a href="#">Separated link</a></li>
+      </ul>
+    </div>
+
+    <!-- Single button using append-to-body -->
+    <div class="btn-group" uib-dropdown dropdown-append-to-body>
+      <button id="btn-append-to-body" type="button" class="btn btn-primary" uib-dropdown-toggle>
+        Dropdown on Body <span class="caret"></span>
+      </button>
+      <ul class="uib-dropdown-menu" role="menu" aria-labelledby="btn-append-to-body">
+        <li role="menuitem"><a href="#">Action</a></li>
+        <li role="menuitem"><a href="#">Another action</a></li>
+        <li role="menuitem"><a href="#">Something else here</a></li>
+        <li class="divider"></li>
+        <li role="menuitem"><a href="#">Separated link</a></li>
+      </ul>
+    </div>
+    
+    <!-- Single button using template-url -->
+    <div class="btn-group" uib-dropdown>
+      <button id="button-template-url" type="button" class="btn btn-primary" uib-dropdown-toggle ng-disabled="disabled">
+        Dropdown using template <span class="caret"></span>
+      </button>
+      <ul class="uib-dropdown-menu" template-url="dropdown.html" aria-labelledby="button-template-url">
+      </ul>
+    </div>
+
+    <hr />
+    <p>
+        <button type="button" class="btn btn-default btn-sm" ng-click="toggleDropdown($event)">Toggle button dropdown</button>
+        <button type="button" class="btn btn-warning btn-sm" ng-click="disabled = !disabled">Enable/Disable</button>
+    </p>
+
+    <hr>
+    <!-- Single button with keyboard nav -->
+    <div class="btn-group" uib-dropdown uib-keyboard-nav>
+        <button id="simple-btn-keyboard-nav" type="button" class="btn btn-primary" uib-dropdown-toggle>
+            Dropdown with keyboard navigation <span class="caret"></span>
+        </button>
+        <ul class="uib-dropdown-menu" role="menu" aria-labelledby="simple-btn-keyboard-nav">
+            <li role="menuitem"><a href="#">Action</a></li>
+            <li role="menuitem"><a href="#">Another action</a></li>
+            <li role="menuitem"><a href="#">Something else here</a></li>
+            <li class="divider"></li>
+            <li role="menuitem"><a href="#">Separated link</a></li>
+        </ul>
+    </div>
+
+    <script type="text/ng-template" id="dropdown.html">
+        <ul class="uib-dropdown-menu" role="menu" aria-labelledby="button-template-url">
+          <li role="menuitem"><a href="#">Action in Template</a></li>
+          <li role="menuitem"><a href="#">Another action in Template</a></li>
+          <li role="menuitem"><a href="#">Something else here</a></li>
+          <li class="divider"></li>
+          <li role="menuitem"><a href="#">Separated link in Template</a></li>
+        </ul>
+    </script>
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/dropdown/docs/demo.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/dropdown/docs/demo.js
new file mode 100644
index 0000000..321d493
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/dropdown/docs/demo.js
@@ -0,0 +1,21 @@
+angular.module('ui.bootstrap.demo').controller('DropdownCtrl', function ($scope, $log) {
+  $scope.items = [
+    'The first choice!',
+    'And another choice for you.',
+    'but wait! A third!'
+  ];
+
+  $scope.status = {
+    isopen: false
+  };
+
+  $scope.toggled = function(open) {
+    $log.log('Dropdown is now: ', open);
+  };
+
+  $scope.toggleDropdown = function($event) {
+    $event.preventDefault();
+    $event.stopPropagation();
+    $scope.status.isopen = !$scope.status.isopen;
+  };
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/dropdown/docs/readme.md b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/dropdown/docs/readme.md
new file mode 100644
index 0000000..b8571a1
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/dropdown/docs/readme.md
@@ -0,0 +1,19 @@
+
+Dropdown is a simple directive which will toggle a dropdown menu on click or programmatically.
+You can either use `is-open` to toggle or add inside a `<a uib-dropdown-toggle>` element to toggle it when is clicked.
+There is also the `on-toggle(open)` optional expression fired when dropdown changes state.
+
+Add `dropdown-append-to-body` to the `uib-dropdown` element to append to the inner `dropdown-menu` to the body.
+This is useful when the dropdown button is inside a div with `overflow: hidden`, and the menu would otherwise be hidden.
+
+Add `uib-keyboard-nav` to the `uib-dropdown` element to enable navigation of dropdown list elements with the arrow keys.
+
+By default the dropdown will automatically close if any of its elements is clicked, you can change this behavior by setting the `auto-close` option as follows:
+
+  * `always` - (Default) automatically closes the dropdown when any of its elements is clicked.
+  * `outsideClick` - closes the dropdown automatically only when the user clicks any element outside the dropdown.
+  * `disabled` - disables the auto close. You can then control the open/close status of the dropdown manually, by using `is-open`. Please notice that the dropdown will still close if the toggle is clicked, the `esc` key is pressed or another dropdown is open. The dropdown will no longer close on `$locationChangeSuccess` events.
+
+Optionally, you may specify a template for the dropdown menu using the `template-url` attribute. This is especially useful when you have multiple similar dropdowns in a repeater and you want to keep your HTML output lean and your number of scopes to a minimum. The template has full access to the scope in which the dropdown lies.
+
+Example: `<ul class="uib-dropdown-menu" template-url="custom-dropdown.html"></ul>`.
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/dropdown/dropdown.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/dropdown/dropdown.js
new file mode 100644
index 0000000..1e666f2
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/dropdown/dropdown.js
@@ -0,0 +1,658 @@
+angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
+
+.constant('uibDropdownConfig', {
+  openClass: 'open'
+})
+
+.service('uibDropdownService', ['$document', '$rootScope', function($document, $rootScope) {
+  var openScope = null;
+
+  this.open = function(dropdownScope) {
+    if (!openScope) {
+      $document.bind('click', closeDropdown);
+      $document.bind('keydown', keybindFilter);
+    }
+
+    if (openScope && openScope !== dropdownScope) {
+      openScope.isOpen = false;
+    }
+
+    openScope = dropdownScope;
+  };
+
+  this.close = function(dropdownScope) {
+    if (openScope === dropdownScope) {
+      openScope = null;
+      $document.unbind('click', closeDropdown);
+      $document.unbind('keydown', keybindFilter);
+    }
+  };
+
+  var closeDropdown = function(evt) {
+    // This method may still be called during the same mouse event that
+    // unbound this event handler. So check openScope before proceeding.
+    if (!openScope) { return; }
+
+    if (evt && openScope.getAutoClose() === 'disabled')  { return ; }
+
+    var toggleElement = openScope.getToggleElement();
+    if (evt && toggleElement && toggleElement[0].contains(evt.target)) {
+      return;
+    }
+
+    var dropdownElement = openScope.getDropdownElement();
+    if (evt && openScope.getAutoClose() === 'outsideClick' &&
+      dropdownElement && dropdownElement[0].contains(evt.target)) {
+      return;
+    }
+
+    openScope.isOpen = false;
+
+    if (!$rootScope.$$phase) {
+      openScope.$apply();
+    }
+  };
+
+  var keybindFilter = function(evt) {
+    if (evt.which === 27) {
+      openScope.focusToggleElement();
+      closeDropdown();
+    } else if (openScope.isKeynavEnabled() && /(38|40)/.test(evt.which) && openScope.isOpen) {
+      evt.preventDefault();
+      evt.stopPropagation();
+      openScope.focusDropdownEntry(evt.which);
+    }
+  };
+}])
+
+.controller('UibDropdownController', ['$scope', '$element', '$attrs', '$parse', 'uibDropdownConfig', 'uibDropdownService', '$animate', '$uibPosition', '$document', '$compile', '$templateRequest', function($scope, $element, $attrs, $parse, dropdownConfig, uibDropdownService, $animate, $position, $document, $compile, $templateRequest) {
+  var self = this,
+    scope = $scope.$new(), // create a child scope so we are not polluting original one
+    templateScope,
+    openClass = dropdownConfig.openClass,
+    getIsOpen,
+    setIsOpen = angular.noop,
+    toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
+    appendToBody = false,
+    keynavEnabled =false,
+    selectedOption = null;
+
+
+  $element.addClass('dropdown');
+
+  this.init = function() {
+    if ($attrs.isOpen) {
+      getIsOpen = $parse($attrs.isOpen);
+      setIsOpen = getIsOpen.assign;
+
+      $scope.$watch(getIsOpen, function(value) {
+        scope.isOpen = !!value;
+      });
+    }
+
+    appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
+    keynavEnabled = angular.isDefined($attrs.uibKeyboardNav);
+
+    if (appendToBody && self.dropdownMenu) {
+      $document.find('body').append(self.dropdownMenu);
+      $element.on('$destroy', function handleDestroyEvent() {
+        self.dropdownMenu.remove();
+      });
+    }
+  };
+
+  this.toggle = function(open) {
+    return scope.isOpen = arguments.length ? !!open : !scope.isOpen;
+  };
+
+  // Allow other directives to watch status
+  this.isOpen = function() {
+    return scope.isOpen;
+  };
+
+  scope.getToggleElement = function() {
+    return self.toggleElement;
+  };
+
+  scope.getAutoClose = function() {
+    return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled'
+  };
+
+  scope.getElement = function() {
+    return $element;
+  };
+
+  scope.isKeynavEnabled = function() {
+    return keynavEnabled;
+  };
+
+  scope.focusDropdownEntry = function(keyCode) {
+    var elems = self.dropdownMenu ? //If append to body is used.
+      (angular.element(self.dropdownMenu).find('a')) :
+      (angular.element($element).find('ul').eq(0).find('a'));
+
+    switch (keyCode) {
+      case (40): {
+        if (!angular.isNumber(self.selectedOption)) {
+          self.selectedOption = 0;
+        } else {
+          self.selectedOption = (self.selectedOption === elems.length - 1 ?
+            self.selectedOption :
+            self.selectedOption + 1);
+        }
+        break;
+      }
+      case (38): {
+        if (!angular.isNumber(self.selectedOption)) {
+          self.selectedOption = elems.length - 1;
+        } else {
+          self.selectedOption = self.selectedOption === 0 ?
+            0 : self.selectedOption - 1;
+        }
+        break;
+      }
+    }
+    elems[self.selectedOption].focus();
+  };
+
+  scope.getDropdownElement = function() {
+    return self.dropdownMenu;
+  };
+
+  scope.focusToggleElement = function() {
+    if (self.toggleElement) {
+      self.toggleElement[0].focus();
+    }
+  };
+
+  scope.$watch('isOpen', function(isOpen, wasOpen) {
+    if (appendToBody && self.dropdownMenu) {
+      var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true);
+      var css = {
+        top: pos.top + 'px',
+        display: isOpen ? 'block' : 'none'
+      };
+
+      var rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
+      if (!rightalign) {
+        css.left = pos.left + 'px';
+        css.right = 'auto';
+      } else {
+        css.left = 'auto';
+        css.right = (window.innerWidth - (pos.left + $element.prop('offsetWidth'))) + 'px';
+      }
+
+      self.dropdownMenu.css(css);
+    }
+
+    $animate[isOpen ? 'addClass' : 'removeClass']($element, openClass).then(function() {
+      if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
+        toggleInvoker($scope, { open: !!isOpen });
+      }
+    });
+
+    if (isOpen) {
+      if (self.dropdownMenuTemplateUrl) {
+        $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) {
+          templateScope = scope.$new();
+          $compile(tplContent.trim())(templateScope, function(dropdownElement) {
+            var newEl = dropdownElement;
+            self.dropdownMenu.replaceWith(newEl);
+            self.dropdownMenu = newEl;
+          });
+        });
+      }
+
+      scope.focusToggleElement();
+      uibDropdownService.open(scope);
+    } else {
+      if (self.dropdownMenuTemplateUrl) {
+        if (templateScope) {
+          templateScope.$destroy();
+        }
+        var newEl = angular.element('<ul class="dropdown-menu"></ul>');
+        self.dropdownMenu.replaceWith(newEl);
+        self.dropdownMenu = newEl;
+      }
+
+      uibDropdownService.close(scope);
+      self.selectedOption = null;
+    }
+
+    if (angular.isFunction(setIsOpen)) {
+      setIsOpen($scope, isOpen);
+    }
+  });
+
+  $scope.$on('$locationChangeSuccess', function() {
+    if (scope.getAutoClose() !== 'disabled') {
+      scope.isOpen = false;
+    }
+  });
+
+  var offDestroy = $scope.$on('$destroy', function() {
+    scope.$destroy();
+  });
+  scope.$on('$destroy', offDestroy);
+}])
+
+.directive('uibDropdown', function() {
+  return {
+    controller: 'UibDropdownController',
+    link: function(scope, element, attrs, dropdownCtrl) {
+      dropdownCtrl.init();
+    }
+  };
+})
+
+.directive('uibDropdownMenu', function() {
+  return {
+    restrict: 'AC',
+    require: '?^uibDropdown',
+    link: function(scope, element, attrs, dropdownCtrl) {
+      if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) {
+        return;
+      }
+
+      element.addClass('dropdown-menu');
+
+      var tplUrl = attrs.templateUrl;
+      if (tplUrl) {
+        dropdownCtrl.dropdownMenuTemplateUrl = tplUrl;
+      }
+
+      if (!dropdownCtrl.dropdownMenu) {
+        dropdownCtrl.dropdownMenu = element;
+      }
+    }
+  };
+})
+
+.directive('uibKeyboardNav', function() {
+  return {
+    restrict: 'A',
+    require: '?^uibDropdown',
+    link: function(scope, element, attrs, dropdownCtrl) {
+      element.bind('keydown', function(e) {
+        if ([38, 40].indexOf(e.which) !== -1) {
+          e.preventDefault();
+          e.stopPropagation();
+
+          var elems = dropdownCtrl.dropdownMenu.find('a');
+
+          switch (e.which) {
+            case (40): { // Down
+              if (!angular.isNumber(dropdownCtrl.selectedOption)) {
+                dropdownCtrl.selectedOption = 0;
+              } else {
+                dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === elems.length -1 ?
+                  dropdownCtrl.selectedOption : dropdownCtrl.selectedOption + 1;
+              }
+              break;
+            }
+            case (38): { // Up
+              if (!angular.isNumber(dropdownCtrl.selectedOption)) {
+                dropdownCtrl.selectedOption = elems.length - 1;
+              } else {
+                dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === 0 ?
+                  0 : dropdownCtrl.selectedOption - 1;
+              }
+              break;
+            }
+          }
+          elems[dropdownCtrl.selectedOption].focus();
+        }
+      });
+    }
+  };
+})
+
+.directive('uibDropdownToggle', function() {
+  return {
+    require: '?^uibDropdown',
+    link: function(scope, element, attrs, dropdownCtrl) {
+      if (!dropdownCtrl) {
+        return;
+      }
+
+      element.addClass('dropdown-toggle');
+
+      dropdownCtrl.toggleElement = element;
+
+      var toggleDropdown = function(event) {
+        event.preventDefault();
+
+        if (!element.hasClass('disabled') && !attrs.disabled) {
+          scope.$apply(function() {
+            dropdownCtrl.toggle();
+          });
+        }
+      };
+
+      element.bind('click', toggleDropdown);
+
+      // WAI-ARIA
+      element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
+      scope.$watch(dropdownCtrl.isOpen, function(isOpen) {
+        element.attr('aria-expanded', !!isOpen);
+      });
+
+      scope.$on('$destroy', function() {
+        element.unbind('click', toggleDropdown);
+      });
+    }
+  };
+});
+
+/* Deprecated dropdown below */
+
+angular.module('ui.bootstrap.dropdown')
+
+.value('$dropdownSuppressWarning', false)
+
+.service('dropdownService', ['$log', '$dropdownSuppressWarning', 'uibDropdownService', function($log, $dropdownSuppressWarning, uibDropdownService) {
+  if (!$dropdownSuppressWarning) {
+    $log.warn('dropdownService is now deprecated. Use uibDropdownService instead.');
+  }
+
+  angular.extend(this, uibDropdownService);
+}])
+
+.controller('DropdownController', ['$scope', '$element', '$attrs', '$parse', 'uibDropdownConfig', 'uibDropdownService', '$animate', '$uibPosition', '$document', '$compile', '$templateRequest', '$log', '$dropdownSuppressWarning', function($scope, $element, $attrs, $parse, dropdownConfig, uibDropdownService, $animate, $position, $document, $compile, $templateRequest, $log, $dropdownSuppressWarning) {
+  if (!$dropdownSuppressWarning) {
+    $log.warn('DropdownController is now deprecated. Use UibDropdownController instead.');
+  }
+
+  var self = this,
+    scope = $scope.$new(), // create a child scope so we are not polluting original one
+    templateScope,
+    openClass = dropdownConfig.openClass,
+    getIsOpen,
+    setIsOpen = angular.noop,
+    toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
+    appendToBody = false,
+    keynavEnabled =false,
+    selectedOption = null;
+
+
+  $element.addClass('dropdown');
+
+  this.init = function() {
+    if ($attrs.isOpen) {
+      getIsOpen = $parse($attrs.isOpen);
+      setIsOpen = getIsOpen.assign;
+
+      $scope.$watch(getIsOpen, function(value) {
+        scope.isOpen = !!value;
+      });
+    }
+
+    appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
+    keynavEnabled = angular.isDefined($attrs.uibKeyboardNav);
+
+    if (appendToBody && self.dropdownMenu) {
+      $document.find('body').append(self.dropdownMenu);
+      $element.on('$destroy', function handleDestroyEvent() {
+        self.dropdownMenu.remove();
+      });
+    }
+  };
+
+  this.toggle = function(open) {
+    return scope.isOpen = arguments.length ? !!open : !scope.isOpen;
+  };
+
+  // Allow other directives to watch status
+  this.isOpen = function() {
+    return scope.isOpen;
+  };
+
+  scope.getToggleElement = function() {
+    return self.toggleElement;
+  };
+
+  scope.getAutoClose = function() {
+    return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled'
+  };
+
+  scope.getElement = function() {
+    return $element;
+  };
+
+  scope.isKeynavEnabled = function() {
+    return keynavEnabled;
+  };
+
+  scope.focusDropdownEntry = function(keyCode) {
+    var elems = self.dropdownMenu ? //If append to body is used.
+      (angular.element(self.dropdownMenu).find('a')) :
+      (angular.element($element).find('ul').eq(0).find('a'));
+
+    switch (keyCode) {
+      case (40): {
+        if (!angular.isNumber(self.selectedOption)) {
+          self.selectedOption = 0;
+        } else {
+          self.selectedOption = (self.selectedOption === elems.length -1 ?
+            self.selectedOption :
+          self.selectedOption + 1);
+        }
+        break;
+      }
+      case (38): {
+        if (!angular.isNumber(self.selectedOption)) {
+          self.selectedOption = elems.length - 1;
+        } else {
+          self.selectedOption = self.selectedOption === 0 ?
+            0 : self.selectedOption - 1;
+        }
+        break;
+      }
+    }
+    elems[self.selectedOption].focus();
+  };
+
+  scope.getDropdownElement = function() {
+    return self.dropdownMenu;
+  };
+
+  scope.focusToggleElement = function() {
+    if (self.toggleElement) {
+      self.toggleElement[0].focus();
+    }
+  };
+
+  scope.$watch('isOpen', function(isOpen, wasOpen) {
+    if (appendToBody && self.dropdownMenu) {
+      var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true);
+      var css = {
+        top: pos.top + 'px',
+        display: isOpen ? 'block' : 'none'
+      };
+
+      var rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
+      if (!rightalign) {
+        css.left = pos.left + 'px';
+        css.right = 'auto';
+      } else {
+        css.left = 'auto';
+        css.right = (window.innerWidth - (pos.left + $element.prop('offsetWidth'))) + 'px';
+      }
+
+      self.dropdownMenu.css(css);
+    }
+
+    $animate[isOpen ? 'addClass' : 'removeClass']($element, openClass).then(function() {
+      if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
+        toggleInvoker($scope, { open: !!isOpen });
+      }
+    });
+
+    if (isOpen) {
+      if (self.dropdownMenuTemplateUrl) {
+        $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) {
+          templateScope = scope.$new();
+          $compile(tplContent.trim())(templateScope, function(dropdownElement) {
+            var newEl = dropdownElement;
+            self.dropdownMenu.replaceWith(newEl);
+            self.dropdownMenu = newEl;
+          });
+        });
+      }
+
+      scope.focusToggleElement();
+      uibDropdownService.open(scope);
+    } else {
+      if (self.dropdownMenuTemplateUrl) {
+        if (templateScope) {
+          templateScope.$destroy();
+        }
+        var newEl = angular.element('<ul class="dropdown-menu"></ul>');
+        self.dropdownMenu.replaceWith(newEl);
+        self.dropdownMenu = newEl;
+      }
+
+      uibDropdownService.close(scope);
+      self.selectedOption = null;
+    }
+
+    if (angular.isFunction(setIsOpen)) {
+      setIsOpen($scope, isOpen);
+    }
+  });
+
+  $scope.$on('$locationChangeSuccess', function() {
+    if (scope.getAutoClose() !== 'disabled') {
+      scope.isOpen = false;
+    }
+  });
+
+  var offDestroy = $scope.$on('$destroy', function() {
+    scope.$destroy();
+  });
+  scope.$on('$destroy', offDestroy);
+}])
+
+.directive('dropdown', ['$log', '$dropdownSuppressWarning', function($log, $dropdownSuppressWarning) {
+  return {
+    controller: 'DropdownController',
+    link: function(scope, element, attrs, dropdownCtrl) {
+      if (!$dropdownSuppressWarning) {
+        $log.warn('dropdown is now deprecated. Use uib-dropdown instead.');
+      }
+
+      dropdownCtrl.init();
+    }
+  };
+}])
+
+.directive('dropdownMenu', ['$log', '$dropdownSuppressWarning', function($log, $dropdownSuppressWarning) {
+  return {
+    restrict: 'AC',
+    require: '?^dropdown',
+    link: function(scope, element, attrs, dropdownCtrl) {
+      if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) {
+        return;
+      }
+
+      if (!$dropdownSuppressWarning) {
+        $log.warn('dropdown-menu is now deprecated. Use uib-dropdown-menu instead.');
+      }
+
+      element.addClass('dropdown-menu');
+
+      var tplUrl = attrs.templateUrl;
+      if (tplUrl) {
+        dropdownCtrl.dropdownMenuTemplateUrl = tplUrl;
+      }
+
+      if (!dropdownCtrl.dropdownMenu) {
+        dropdownCtrl.dropdownMenu = element;
+      }
+    }
+  };
+}])
+
+.directive('keyboardNav', ['$log', '$dropdownSuppressWarning', function($log, $dropdownSuppressWarning) {
+  return {
+    restrict: 'A',
+    require: '?^dropdown',
+    link: function(scope, element, attrs, dropdownCtrl) {
+      if (!$dropdownSuppressWarning) {
+        $log.warn('keyboard-nav is now deprecated. Use uib-keyboard-nav instead.');
+      }
+
+      element.bind('keydown', function(e) {
+        if ([38, 40].indexOf(e.which) !== -1) {
+          e.preventDefault();
+          e.stopPropagation();
+
+          var elems = dropdownCtrl.dropdownMenu.find('a');
+
+          switch (e.which) {
+            case (40): { // Down
+              if (!angular.isNumber(dropdownCtrl.selectedOption)) {
+                dropdownCtrl.selectedOption = 0;
+              } else {
+                dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === elems.length -1 ?
+                  dropdownCtrl.selectedOption : dropdownCtrl.selectedOption + 1;
+              }
+              break;
+            }
+            case (38): { // Up
+              if (!angular.isNumber(dropdownCtrl.selectedOption)) {
+                dropdownCtrl.selectedOption = elems.length - 1;
+              } else {
+                dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === 0 ?
+                  0 : dropdownCtrl.selectedOption - 1;
+              }
+              break;
+            }
+          }
+          elems[dropdownCtrl.selectedOption].focus();
+        }
+      });
+    }
+  };
+}])
+
+.directive('dropdownToggle', ['$log', '$dropdownSuppressWarning', function($log, $dropdownSuppressWarning) {
+  return {
+    require: '?^dropdown',
+    link: function(scope, element, attrs, dropdownCtrl) {
+      if (!$dropdownSuppressWarning) {
+        $log.warn('dropdown-toggle is now deprecated. Use uib-dropdown-toggle instead.');
+      }
+
+      if (!dropdownCtrl) {
+        return;
+      }
+
+      element.addClass('dropdown-toggle');
+
+      dropdownCtrl.toggleElement = element;
+
+      var toggleDropdown = function(event) {
+        event.preventDefault();
+
+        if (!element.hasClass('disabled') && !attrs.disabled) {
+          scope.$apply(function() {
+            dropdownCtrl.toggle();
+          });
+        }
+      };
+
+      element.bind('click', toggleDropdown);
+
+      // WAI-ARIA
+      element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
+      scope.$watch(dropdownCtrl.isOpen, function(isOpen) {
+        element.attr('aria-expanded', !!isOpen);
+      });
+
+      scope.$on('$destroy', function() {
+        element.unbind('click', toggleDropdown);
+      });
+    }
+  };
+}]);
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/dropdown/test/dropdown.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/dropdown/test/dropdown.spec.js
new file mode 100644
index 0000000..6d6a87d
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/dropdown/test/dropdown.spec.js
@@ -0,0 +1,727 @@
+describe('dropdownToggle', function() {
+  var $animate, $compile, $rootScope, $document, $templateCache, dropdownConfig, element, $browser, $log;
+
+  beforeEach(module('ngAnimateMock'));
+  beforeEach(module('ui.bootstrap.dropdown'));
+
+  beforeEach(inject(function(_$animate_, _$compile_, _$rootScope_, _$document_, _$templateCache_, uibDropdownConfig, _$browser_, _$log_) {
+    $animate = _$animate_;
+    $compile = _$compile_;
+    $rootScope = _$rootScope_;
+    $document = _$document_;
+    $templateCache = _$templateCache_;
+    dropdownConfig = uibDropdownConfig;
+    $browser = _$browser_;
+    $log = _$log_;
+  }));
+
+  afterEach(function() {
+    element.remove();
+  });
+
+  var clickDropdownToggle = function(elm) {
+    elm = elm || element;
+    elm.find('a[uib-dropdown-toggle]').click();
+  };
+
+  var triggerKeyDown = function (element, keyCode) {
+    var e = $.Event('keydown');
+    e.which = keyCode;
+    element.trigger(e);
+  };
+
+  var isFocused = function(elm) {
+    return elm[0] === document.activeElement;
+  };
+
+  describe('basic', function() {
+    function dropdown() {
+      return $compile('<li uib-dropdown><a href uib-dropdown-toggle></a><ul><li><a href>Hello</a></li></ul></li>')($rootScope);
+    }
+
+    beforeEach(function() {
+      element = dropdown();
+    });
+
+    it('should toggle on `a` click', function() {
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
+      clickDropdownToggle();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+      clickDropdownToggle();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
+    });
+
+    it('should toggle when an option is clicked', function() {
+      $document.find('body').append(element);
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
+      clickDropdownToggle();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+
+      var optionEl = element.find('ul > li').eq(0).find('a').eq(0);
+      optionEl.click();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
+    });
+
+    it('should close on document click', function() {
+      clickDropdownToggle();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+      $document.click();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
+    });
+
+    it('should close on escape key & focus toggle element', function() {
+      $document.find('body').append(element);
+      clickDropdownToggle();
+      triggerKeyDown($document, 27);
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
+      expect(isFocused(element.find('a'))).toBe(true);
+    });
+
+    it('should not close on backspace key', function() {
+      clickDropdownToggle();
+      triggerKeyDown($document, 8);
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+    });
+
+    it('should close on $location change', function() {
+      clickDropdownToggle();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+      $rootScope.$broadcast('$locationChangeSuccess');
+      $rootScope.$apply();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
+    });
+
+    it('should only allow one dropdown to be open at once', function() {
+      var elm1 = dropdown();
+      var elm2 = dropdown();
+      expect(elm1.hasClass(dropdownConfig.openClass)).toBe(false);
+      expect(elm2.hasClass(dropdownConfig.openClass)).toBe(false);
+
+      clickDropdownToggle( elm1 );
+      expect(elm1.hasClass(dropdownConfig.openClass)).toBe(true);
+      expect(elm2.hasClass(dropdownConfig.openClass)).toBe(false);
+
+      clickDropdownToggle( elm2 );
+      expect(elm1.hasClass(dropdownConfig.openClass)).toBe(false);
+      expect(elm2.hasClass(dropdownConfig.openClass)).toBe(true);
+    });
+
+    it('should not toggle if the element has `disabled` class', function() {
+      var elm = $compile('<li uib-dropdown><a class="disabled" uib-dropdown-toggle></a><ul><li>Hello</li></ul></li>')($rootScope);
+      clickDropdownToggle( elm );
+      expect(elm.hasClass(dropdownConfig.openClass)).toBe(false);
+    });
+
+    it('should not toggle if the element is disabled', function() {
+      var elm = $compile('<li uib-dropdown><button disabled="disabled" uib-dropdown-toggle></button><ul><li>Hello</li></ul></li>')($rootScope);
+      elm.find('button').click();
+      expect(elm.hasClass(dropdownConfig.openClass)).toBe(false);
+    });
+
+    it('should not toggle if the element has `ng-disabled` as true', function() {
+      $rootScope.isdisabled = true;
+      var elm = $compile('<li uib-dropdown><div ng-disabled="isdisabled" uib-dropdown-toggle></div><ul><li>Hello</li></ul></li>')($rootScope);
+      $rootScope.$digest();
+      elm.find('div').click();
+      expect(elm.hasClass(dropdownConfig.openClass)).toBe(false);
+
+      $rootScope.isdisabled = false;
+      $rootScope.$digest();
+      elm.find('div').click();
+      expect(elm.hasClass(dropdownConfig.openClass)).toBe(true);
+    });
+
+    it('should unbind events on scope destroy', function() {
+      var $scope = $rootScope.$new();
+      var elm = $compile('<li uib-dropdown><button ng-disabled="isdisabled" uib-dropdown-toggle></button><ul><li>Hello</li></ul></li>')($scope);
+      $scope.$digest();
+
+      var buttonEl = elm.find('button');
+      buttonEl.click();
+      expect(elm.hasClass(dropdownConfig.openClass)).toBe(true);
+      buttonEl.click();
+      expect(elm.hasClass(dropdownConfig.openClass)).toBe(false);
+
+      $scope.$destroy();
+      buttonEl.click();
+      expect(elm.hasClass(dropdownConfig.openClass)).toBe(false);
+    });
+
+    // issue 270
+    it('executes other document click events normally', function() {
+      var checkboxEl = $compile('<input type="checkbox" ng-click="clicked = true" />')($rootScope);
+      $rootScope.$digest();
+
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
+      expect($rootScope.clicked).toBeFalsy();
+
+      clickDropdownToggle();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+      expect($rootScope.clicked).toBeFalsy();
+
+      checkboxEl.click();
+      expect($rootScope.clicked).toBeTruthy();
+    });
+
+    // WAI-ARIA
+    it('should aria markup to the `dropdown-toggle`', function() {
+      var toggleEl = element.find('a');
+      expect(toggleEl.attr('aria-haspopup')).toBe('true');
+      expect(toggleEl.attr('aria-expanded')).toBe('false');
+
+      clickDropdownToggle();
+      expect(toggleEl.attr('aria-expanded')).toBe('true');
+      clickDropdownToggle();
+      expect(toggleEl.attr('aria-expanded')).toBe('false');
+    });
+
+    // pr/issue 3274
+    it('should not raise $digest:inprog if dismissed during a digest cycle', function() {
+      clickDropdownToggle();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+
+      $rootScope.$apply(function() {
+        $document.click();
+      });
+
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
+    });
+  });
+
+  describe('using dropdownMenuTemplate', function() {
+    function dropdown() {
+      $templateCache.put('custom.html', '<ul class="uib-dropdown-menu"><li>Item 1</li></ul>');
+
+      return $compile('<li uib-dropdown><a href uib-dropdown-toggle></a><ul class="uib-dropdown-menu" template-url="custom.html"></ul></li>')($rootScope);
+    }
+
+    beforeEach(function() {
+      element = dropdown();
+    });
+
+    it('should apply custom template for dropdown menu', function() {
+      element.find('a').click();
+      expect(element.find('ul.uib-dropdown-menu').eq(0).find('li').eq(0).text()).toEqual('Item 1');
+    });
+
+    it('should clear ul when dropdown menu is closed', function() {
+      element.find('a').click();
+      expect(element.find('ul.uib-dropdown-menu').eq(0).find('li').eq(0).text()).toEqual('Item 1');
+      element.find('a').click();
+      expect(element.find('ul.uib-dropdown-menu').eq(0).find('li').length).toEqual(0);
+    });
+  });
+
+  describe('using dropdown-append-to-body', function() {
+    function dropdown() {
+      return $compile('<li uib-dropdown dropdown-append-to-body><a href uib-dropdown-toggle></a><ul class="uib-dropdown-menu" id="dropdown-menu"><li><a href>Hello On Body</a></li></ul></li>')($rootScope);
+    }
+
+    beforeEach(function() {
+      element = dropdown();
+    });
+
+    it('adds the menu to the body', function() {
+      expect($document.find('#dropdown-menu').parent()[0]).toBe($document.find('body')[0]);
+    });
+
+    it('removes the menu when the dropdown is removed', function() {
+      element.remove();
+      $rootScope.$digest();
+      expect($document.find('#dropdown-menu').length).toEqual(0);
+    });
+  });
+
+  describe('integration with $location URL rewriting', function() {
+    function dropdown() {
+      // Simulate URL rewriting behavior
+      $document.on('click', 'a[href="#something"]', function() {
+        $rootScope.$broadcast('$locationChangeSuccess');
+        $rootScope.$apply();
+      });
+
+      return $compile('<li uib-dropdown><a href uib-dropdown-toggle></a>' +
+        '<ul><li><a href="#something">Hello</a></li></ul></li>')($rootScope);
+    }
+
+    beforeEach(function() {
+      element = dropdown();
+    });
+
+    it('should close without errors on $location change', function() {
+      $document.find('body').append(element);
+      clickDropdownToggle();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+      var optionEl = element.find('ul > li').eq(0).find('a').eq(0);
+      optionEl.click();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
+    });
+  });
+
+  describe('without trigger', function() {
+    beforeEach(function() {
+      $rootScope.isopen = true;
+      element = $compile('<li uib-dropdown is-open="isopen"><ul><li>Hello</li></ul></li>')($rootScope);
+      $rootScope.$digest();
+    });
+
+    it('should be open initially', function() {
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+    });
+
+    it('should toggle when `is-open` changes', function() {
+      $rootScope.isopen = false;
+      $rootScope.$digest();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
+    });
+  });
+
+  describe('`is-open`', function() {
+    beforeEach(function() {
+      $rootScope.isopen = true;
+      element = $compile('<li uib-dropdown is-open="isopen"><a href uib-dropdown-toggle></a><ul><li>Hello</li></ul></li>')($rootScope);
+      $rootScope.$digest();
+    });
+
+    it('should be open initially', function() {
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+    });
+
+    it('should change `is-open` binding when toggles', function() {
+      clickDropdownToggle();
+      expect($rootScope.isopen).toBe(false);
+    });
+
+    it('should toggle when `is-open` changes', function() {
+      $rootScope.isopen = false;
+      $rootScope.$digest();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
+    });
+
+    it('focus toggle element when opening', function() {
+      $document.find('body').append(element);
+      clickDropdownToggle();
+      $rootScope.isopen = false;
+      $rootScope.$digest();
+      expect(isFocused(element.find('a'))).toBe(false);
+      $rootScope.isopen = true;
+      $rootScope.$digest();
+      expect(isFocused(element.find('a'))).toBe(true);
+    });
+  });
+
+  describe('`on-toggle`', function() {
+    beforeEach(function() {
+      $rootScope.toggleHandler = jasmine.createSpy('toggleHandler');
+      $rootScope.isopen = false;
+      element = $compile('<li uib-dropdown on-toggle="toggleHandler(open)"  is-open="isopen"><a uib-dropdown-toggle></a><ul><li>Hello</li></ul></li>')($rootScope);
+      $rootScope.$digest();
+    });
+
+    it('should not have been called initially', function() {
+      expect($rootScope.toggleHandler).not.toHaveBeenCalled();
+    });
+
+    it('should call it correctly when toggles', function() {
+      $rootScope.isopen = true;
+      $rootScope.$digest();
+
+      $animate.flush();
+      $rootScope.$digest();
+      expect($rootScope.toggleHandler).toHaveBeenCalledWith(true);
+
+      clickDropdownToggle();
+      $animate.flush();
+      $rootScope.$digest();
+      expect($rootScope.toggleHandler).toHaveBeenCalledWith(false);
+    });
+  });
+
+  describe('`on-toggle` with initially open', function() {
+    beforeEach(function() {
+      $rootScope.toggleHandler = jasmine.createSpy('toggleHandler');
+      $rootScope.isopen = true;
+      element = $compile('<li uib-dropdown on-toggle="toggleHandler(open)" is-open="isopen"><a uib-dropdown-toggle></a><ul><li>Hello</li></ul></li>')($rootScope);
+      $rootScope.$digest();
+    });
+
+    it('should not have been called initially', function() {
+      expect($rootScope.toggleHandler).not.toHaveBeenCalled();
+    });
+
+    it('should call it correctly when toggles', function() {
+      $rootScope.isopen = false;
+      $rootScope.$digest();
+
+      $animate.flush();
+      $rootScope.$digest();
+      expect($rootScope.toggleHandler).toHaveBeenCalledWith(false);
+
+      $rootScope.isopen = true;
+      $rootScope.$digest();
+
+      $animate.flush();
+      $rootScope.$digest();
+      expect($rootScope.toggleHandler).toHaveBeenCalledWith(true);
+    });
+  });
+
+  describe('`on-toggle` without is-open', function() {
+    beforeEach(function() {
+      $rootScope.toggleHandler = jasmine.createSpy('toggleHandler');
+      element = $compile('<li uib-dropdown on-toggle="toggleHandler(open)"><a uib-dropdown-toggle></a><ul><li>Hello</li></ul></li>')($rootScope);
+      $rootScope.$digest();
+    });
+
+    it('should not have been called initially', function() {
+      expect($rootScope.toggleHandler).not.toHaveBeenCalled();
+    });
+
+    it('should call it when clicked', function() {
+      clickDropdownToggle();
+
+      $animate.flush();
+      $rootScope.$digest();
+      expect($rootScope.toggleHandler).toHaveBeenCalledWith(true);
+
+      clickDropdownToggle();
+
+      $animate.flush();
+      $rootScope.$digest();
+      expect($rootScope.toggleHandler).toHaveBeenCalledWith(false);
+    });
+  });
+
+  describe('`auto-close` option', function() {
+    function dropdown(autoClose) {
+      return $compile('<li uib-dropdown ' +
+        (autoClose === void 0 ? '' : 'auto-close="'+autoClose+'"') +
+        '><a href uib-dropdown-toggle></a><ul><li><a href>Hello</a></li></ul></li>')($rootScope);
+    }
+
+    it('should close on document click if no auto-close is specified', function() {
+      element = dropdown();
+      clickDropdownToggle();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+      $document.click();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
+    });
+
+    it('should close on document click if empty auto-close is specified', function() {
+      element = dropdown('');
+      clickDropdownToggle();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+      $document.click();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
+    });
+
+    it('auto-close="disabled"', function() {
+      element = dropdown('disabled');
+      clickDropdownToggle();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+      $document.click();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+    });
+
+    describe('outsideClick', function() {
+      it('should close only on a click outside of the dropdown menu', function() {
+        element = dropdown('outsideClick');
+        clickDropdownToggle();
+        expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+        element.find('ul li a').click();
+        expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+        $document.click();
+        expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
+      });
+
+      it('should work with dropdown-append-to-body', function() {
+        element = $compile('<li uib-dropdown dropdown-append-to-body auto-close="outsideClick"><a href uib-dropdown-toggle></a><ul class="uib-dropdown-menu" id="dropdown-menu"><li><a href>Hello On Body</a></li></ul></li>')($rootScope);
+        clickDropdownToggle();
+        expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+        $document.find('#dropdown-menu').find('li').eq(0).trigger('click');
+        expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+        $document.click();
+        expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
+      });
+    });
+
+    it('control with is-open', function() {
+      $rootScope.isopen = true;
+      element = $compile('<li uib-dropdown is-open="isopen" auto-close="disabled"><a href uib-dropdown-toggle></a><ul><li>Hello</li></ul></li>')($rootScope);
+      $rootScope.$digest();
+
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+      //should remain open
+      $document.click();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+      //now should close
+      $rootScope.isopen = false;
+      $rootScope.$digest();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
+    });
+
+    it('should close anyway if toggle is clicked', function() {
+      element = dropdown('disabled');
+      clickDropdownToggle();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+      clickDropdownToggle();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
+    });
+
+    it('should close anyway if esc is pressed', function() {
+      element = dropdown('disabled');
+      $document.find('body').append(element);
+      clickDropdownToggle();
+      triggerKeyDown($document, 27);
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
+      expect(isFocused(element.find('a'))).toBe(true);
+    });
+
+    it('should close anyway if another dropdown is opened', function() {
+      var elm1 = dropdown('disabled');
+      var elm2 = dropdown();
+      expect(elm1.hasClass(dropdownConfig.openClass)).toBe(false);
+      expect(elm2.hasClass(dropdownConfig.openClass)).toBe(false);
+      clickDropdownToggle(elm1);
+      expect(elm1.hasClass(dropdownConfig.openClass)).toBe(true);
+      expect(elm2.hasClass(dropdownConfig.openClass)).toBe(false);
+      clickDropdownToggle(elm2);
+      expect(elm1.hasClass(dropdownConfig.openClass)).toBe(false);
+      expect(elm2.hasClass(dropdownConfig.openClass)).toBe(true);
+    });
+
+    it('should not close on $locationChangeSuccess if auto-close="disabled"', function() {
+      var elm1 = dropdown('disabled');
+      expect(elm1.hasClass(dropdownConfig.openClass)).toBe(false);
+      clickDropdownToggle(elm1);
+      expect(elm1.hasClass(dropdownConfig.openClass)).toBe(true);
+      $rootScope.$broadcast('$locationChangeSuccess');
+      $rootScope.$digest();
+      expect(elm1.hasClass(dropdownConfig.openClass)).toBe(true);
+    });
+  });
+
+  describe('`keyboard-nav` option', function() {
+    function dropdown() {
+      return $compile('<li uib-dropdown uib-keyboard-nav><a href uib-dropdown-toggle></a><ul><li><a href>Hello</a></li><li><a href>Hello Again</a></li></ul></li>')($rootScope);
+    }
+    beforeEach(function() {
+      element = dropdown();
+    });
+
+    it('should focus first list element when down arrow pressed', function() {
+      $document.find('body').append(element);
+      clickDropdownToggle();
+      triggerKeyDown($document, 40);
+
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+      var optionEl = element.find('ul').eq(0).find('a').eq(0);
+      expect(isFocused(optionEl)).toBe(true);
+    });
+
+    it('should not focus first list element when down arrow pressed if closed', function() {
+      $document.find('body').append(element);
+      triggerKeyDown($document, 40);
+
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
+      var focusEl = element.find('ul').eq(0).find('a').eq(0);
+      expect(isFocused(focusEl)).toBe(false);
+    });
+
+    it('should focus second list element when down arrow pressed twice', function() {
+      $document.find('body').append(element);
+      clickDropdownToggle();
+      triggerKeyDown($document, 40);
+      triggerKeyDown($document, 40);
+
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+      var focusEl = element.find('ul').eq(0).find('a').eq(1);
+      expect(isFocused(focusEl)).toBe(true);
+    });
+  });
+
+  describe('`keyboard-nav` option', function() {
+    function dropdown() {
+      return $compile('<li uib-dropdown uib-keyboard-nav><a href uib-dropdown-toggle></a><ul><li><a href>Hello</a></li><li><a href>Hello Again</a></li></ul></li>')($rootScope);
+    }
+    beforeEach(function() {
+      element = dropdown();
+    });
+
+    it('should focus first list element when down arrow pressed', function() {
+      $document.find('body').append(element);
+      clickDropdownToggle();
+      triggerKeyDown($document, 40);
+
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+      var focusEl = element.find('ul').eq(0).find('a').eq(0);
+      expect(isFocused(focusEl)).toBe(true);
+    });
+
+    it('should not focus first list element when up arrow pressed after dropdown toggled', function() {
+      $document.find('body').append(element);
+      clickDropdownToggle();
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+
+      triggerKeyDown($document, 38);
+      var focusEl = element.find('ul').eq(0).find('a').eq(0);
+      expect(isFocused(focusEl)).toBe(false);
+    });
+
+    it('should focus last list element when up arrow pressed after dropdown toggled', function() {
+      $document.find('body').append(element);
+      clickDropdownToggle();
+      triggerKeyDown($document, 38);
+
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+      var focusEl = element.find('ul').eq(0).find('a').eq(1);
+      expect(isFocused(focusEl)).toBe(true);
+    });
+
+    it('should not focus any list element when down arrow pressed if closed', function() {
+      $document.find('body').append(element);
+      triggerKeyDown($document, 40);
+
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
+      var focusEl = element.find('ul').eq(0).find('a');
+      expect(isFocused(focusEl[0])).toBe(false);
+      expect(isFocused(focusEl[1])).toBe(false);
+    });
+
+    it('should not change focus when other keys are pressed', function() {
+      $document.find('body').append(element);
+      clickDropdownToggle();
+      triggerKeyDown($document, 37);
+
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+      var focusEl = element.find('ul').eq(0).find('a');
+      expect(isFocused(focusEl[0])).toBe(false);
+      expect(isFocused(focusEl[1])).toBe(false);
+    });
+
+    it('should focus second list element when down arrow pressed twice', function() {
+      $document.find('body').append(element);
+      clickDropdownToggle();
+      triggerKeyDown($document, 40);
+      triggerKeyDown($document, 40);
+
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+      var focusEl = element.find('ul').eq(0).find('a').eq(1);
+      expect(isFocused(focusEl)).toBe(true);
+    });
+
+    it('should focus first list element when down arrow pressed 2x and up pressed 1x', function() {
+      $document.find('body').append(element);
+      clickDropdownToggle();
+      triggerKeyDown($document, 40);
+      triggerKeyDown($document, 40);
+
+      triggerKeyDown($document, 38);
+
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+      var focusEl = element.find('ul').eq(0).find('a').eq(0);
+      expect(isFocused(focusEl)).toBe(true);
+    });
+
+    it('should stay focused on final list element if down pressed at list end', function() {
+      $document.find('body').append(element);
+      clickDropdownToggle();
+      triggerKeyDown($document, 40);
+      triggerKeyDown($document, 40);
+
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+      var focusEl = element.find('ul').eq(0).find('a').eq(1);
+      expect(isFocused(focusEl)).toBe(true);
+
+      triggerKeyDown($document, 40);
+      expect(isFocused(focusEl)).toBe(true);
+    });
+
+    it('should close if esc is pressed while focused', function() {
+      element = dropdown('disabled');
+      $document.find('body').append(element);
+      clickDropdownToggle();
+
+      triggerKeyDown($document, 40);
+
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+      var focusEl = element.find('ul').eq(0).find('a').eq(0);
+      expect(isFocused(focusEl)).toBe(true);
+
+      triggerKeyDown($document, 27);
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(false);
+    });
+  });
+
+  describe('`keyboard-nav` option with `dropdown-append-to-body` option', function() {
+    function dropdown() {
+      return $compile('<li uib-dropdown dropdown-append-to-body uib-keyboard-nav><a href uib-dropdown-toggle></a><ul class="uib-dropdown-menu" id="dropdown-menu"><li><a href>Hello On Body</a></li><li><a href>Hello Again</a></li></ul></li>')($rootScope);
+    }
+
+    beforeEach(function() {
+      element = dropdown();
+    });
+
+    it('should focus first list element when down arrow pressed', function() {
+      clickDropdownToggle();
+
+      triggerKeyDown(element, 40);
+
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+      var focusEl = $document.find('ul').eq(0).find('a');
+      expect(isFocused(focusEl)).toBe(true);
+    });
+
+    it('should focus second list element when down arrow pressed twice', function() {
+      clickDropdownToggle();
+      triggerKeyDown(element, 40);
+      triggerKeyDown(element, 40);
+
+      expect(element.hasClass(dropdownConfig.openClass)).toBe(true);
+      var elem1 = $document.find('ul');
+      var elem2 = elem1.find('a');
+      var focusEl = $document.find('ul').eq(0).find('a').eq(1);
+      expect(isFocused(focusEl)).toBe(true);
+    });
+  });
+});
+
+/* Deprecation tests below */
+
+describe('dropdown deprecation', function() {
+  beforeEach(module('ngAnimateMock'));
+  beforeEach(module('ui.bootstrap.dropdown'));
+  
+  it('should suppress warning', function() {
+    module(function($provide) {
+      $provide.value('$dropdownSuppressWarning', true);
+    });
+    
+    inject(function($compile, $log, $rootScope) {
+      spyOn($log, 'warn');
+      var element = $compile('<li dropdown><a href dropdown-toggle></a><ul><li><a href>Hello</a></li></ul></li>')($rootScope);
+      $rootScope.$digest();
+      expect($log.warn.calls.count()).toBe(0);
+    });
+  });
+  
+  it('should give warning by default', inject(function($compile, $log, $rootScope) {
+    spyOn($log, 'warn');
+    var element = $compile('<li dropdown><a href></a><ul><li><a href dropdown-toggle>Hello</a></li></ul></li>')($rootScope);
+    $rootScope.$digest();
+    expect($log.warn.calls.count()).toBe(3);
+    expect($log.warn.calls.argsFor(0)).toEqual(['DropdownController is now deprecated. Use UibDropdownController instead.']);
+    expect($log.warn.calls.argsFor(1)).toEqual(['dropdown-toggle is now deprecated. Use uib-dropdown-toggle instead.']);
+    expect($log.warn.calls.argsFor(2)).toEqual(['dropdown is now deprecated. Use uib-dropdown instead.']);
+  }));
+  
+  it('should give warning by default for keyboardNav', inject(function($compile, $log, $rootScope) {
+    spyOn($log, 'warn');
+    var element = $compile('<li dropdown keyboard-nav><a href ></a><ul><li><a href>Hello</a></li></ul></li>')($rootScope);
+    $rootScope.$digest();
+    expect($log.warn.calls.count()).toBe(3);
+    expect($log.warn.calls.argsFor(0)).toEqual(['DropdownController is now deprecated. Use UibDropdownController instead.']);
+    expect($log.warn.calls.argsFor(1)).toEqual(['keyboard-nav is now deprecated. Use uib-keyboard-nav instead.']);
+    expect($log.warn.calls.argsFor(2)).toEqual(['dropdown is now deprecated. Use uib-dropdown instead.']);
+  }));
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/modal/docs/demo.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/modal/docs/demo.html
new file mode 100644
index 0000000..ac96e07
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/modal/docs/demo.html
@@ -0,0 +1,25 @@
+<div ng-controller="ModalDemoCtrl">
+    <script type="text/ng-template" id="myModalContent.html">
+        <div class="modal-header">
+            <h3 class="modal-title">I'm a modal!</h3>
+        </div>
+        <div class="modal-body">
+            <ul>
+                <li ng-repeat="item in items">
+                    <a href="#" ng-click="$event.preventDefault(); selected.item = item">{{ item }}</a>
+                </li>
+            </ul>
+            Selected: <b>{{ selected.item }}</b>
+        </div>
+        <div class="modal-footer">
+            <button class="btn btn-primary" type="button" ng-click="ok()">OK</button>
+            <button class="btn btn-warning" type="button" ng-click="cancel()">Cancel</button>
+        </div>
+    </script>
+
+    <button type="button" class="btn btn-default" ng-click="open()">Open me!</button>
+    <button type="button" class="btn btn-default" ng-click="open('lg')">Large modal</button>
+    <button type="button" class="btn btn-default" ng-click="open('sm')">Small modal</button>
+    <button type="button" class="btn btn-default" ng-click="toggleAnimation()">Toggle Animation ({{ animationsEnabled }})</button>
+    <div ng-show="selected">Selection from a modal: {{ selected }}</div>
+</div>
\ No newline at end of file
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/modal/docs/demo.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/modal/docs/demo.js
new file mode 100644
index 0000000..60e3a5e
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/modal/docs/demo.js
@@ -0,0 +1,51 @@
+angular.module('ui.bootstrap.demo').controller('ModalDemoCtrl', function ($scope, $uibModal, $log) {
+
+  $scope.items = ['item1', 'item2', 'item3'];
+
+  $scope.animationsEnabled = true;
+
+  $scope.open = function (size) {
+
+    var modalInstance = $uibModal.open({
+      animation: $scope.animationsEnabled,
+      templateUrl: 'myModalContent.html',
+      controller: 'ModalInstanceCtrl',
+      size: size,
+      resolve: {
+        items: function () {
+          return $scope.items;
+        }
+      }
+    });
+
+    modalInstance.result.then(function (selectedItem) {
+      $scope.selected = selectedItem;
+    }, function () {
+      $log.info('Modal dismissed at: ' + new Date());
+    });
+  };
+
+  $scope.toggleAnimation = function () {
+    $scope.animationsEnabled = !$scope.animationsEnabled;
+  };
+
+});
+
+// Please note that $modalInstance represents a modal window (instance) dependency.
+// It is not the same as the $uibModal service used above.
+
+angular.module('ui.bootstrap.demo').controller('ModalInstanceCtrl', function ($scope, $uibModalInstance, items) {
+
+  $scope.items = items;
+  $scope.selected = {
+    item: $scope.items[0]
+  };
+
+  $scope.ok = function () {
+    $uibModalInstance.close($scope.selected.item);
+  };
+
+  $scope.cancel = function () {
+    $uibModalInstance.dismiss('cancel');
+  };
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/modal/docs/readme.md b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/modal/docs/readme.md
new file mode 100644
index 0000000..e3c5064
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/modal/docs/readme.md
@@ -0,0 +1,45 @@
+`$uibModal` is a service to quickly create AngularJS-powered modal windows.
+Creating custom modals is straightforward: create a partial view, its controller and reference them when using the service.
+
+The `$uibModal` service has only one method: `open(options)` where available options are like follows:
+
+* `templateUrl` - a path to a template representing modal's content
+* `template` - inline template representing the modal's content
+* `scope` - a scope instance to be used for the modal's content (actually the `$uibModal` service is going to create a child scope of a provided scope). Defaults to `$rootScope`
+* `controller` - a controller for a modal instance - it can initialize scope used by modal. Accepts the "controller-as" syntax in the form 'SomeCtrl as myctrl'; can be injected with `$uibModalInstance`
+* `controllerAs` - an alternative to the controller-as syntax, matching the API of directive definitions. Requires the `controller` option to be provided as well
+* `bindToController` - when used with `controllerAs` & set to `true`, it will bind the $scope properties onto the controller directly
+* `resolve` - members that will be resolved and passed to the controller as locals; it is equivalent of the `resolve` property for AngularJS routes
+* `animation` - set to false to disable animations on new modal/backdrop. Does not toggle animations for modals/backdrops that are already displayed.
+* `backdrop` - controls presence of a backdrop. Allowed values: true (default), false (no backdrop), `'static'` - backdrop is present but modal window is not closed when clicking outside of the modal window.
+* `keyboard` - indicates whether the dialog should be closable by hitting the ESC key, defaults to true
+* `backdropClass` - additional CSS class(es) to be added to a modal backdrop template
+* `windowClass` - additional CSS class(es) to be added to a modal window template
+* `windowTopClass` - CSS class(es) to be added to the top modal window
+* `windowTemplateUrl` - a path to a template overriding modal's window template
+* `size` - optional suffix of modal window class. The value used is appended to the `modal-` class, i.e. a value of `sm` gives `modal-sm`
+* `openedClass` - class added to the `body` element when the modal is opened. Defaults to `modal-open`
+
+Global defaults may be set for `$uibModal` via `$uibModalProvider.options`.
+
+The `open` method returns a modal instance, an object with the following properties:
+
+* `close(result)` - a method that can be used to close a modal, passing a result
+* `dismiss(reason)` - a method that can be used to dismiss a modal, passing a reason
+* `result` - a promise that is resolved when a modal is closed and rejected when a modal is dismissed
+* `opened` - a promise that is resolved when a modal gets opened after downloading content's template and resolving all variables
+* `rendered` - a promise that is resolved when a modal is rendered. 
+
+In addition the scope associated with modal's content is augmented with 2 methods:
+
+* `$close(result)`
+* `$dismiss(reason)`
+
+Those methods make it easy to close a modal window without a need to create a dedicated controller.
+
+If the $scope is destroyed via unexpected mechanism, such as it being passed in the modal options and a $route/$state transition occurs, the modal will be dismissed with the value `$uibUnscheduledDestruction`.
+
+Finally, a `modal.closing` event is broadcast to the modal scope before the modal closes.  If the listener calls 
+preventDefault on the event, then the modal will remain open.  The $close and $dismiss methods return true if the 
+event was allowed.  The event itself includes a parameter for the result/reason and a boolean parameter that indicates
+whether the modal is being closed (true) or dismissed.
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/modal/modal.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/modal/modal.js
new file mode 100644
index 0000000..93d07cf
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/modal/modal.js
@@ -0,0 +1,933 @@
+angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
+/**
+ * A helper, internal data structure that stores all references attached to key
+ */
+  .factory('$$multiMap', function() {
+    return {
+      createNew: function() {
+        var map = {};
+
+        return {
+          entries: function() {
+            return Object.keys(map).map(function(key) {
+              return {
+                key: key,
+                value: map[key]
+              };
+            });
+          },
+          get: function(key) {
+            return map[key];
+          },
+          hasKey: function(key) {
+            return !!map[key];
+          },
+          keys: function() {
+            return Object.keys(map);
+          },
+          put: function(key, value) {
+            if (!map[key]) {
+              map[key] = [];
+            }
+
+            map[key].push(value);
+          },
+          remove: function(key, value) {
+            var values = map[key];
+
+            if (!values) {
+              return;
+            }
+
+            var idx = values.indexOf(value);
+
+            if (idx !== -1) {
+              values.splice(idx, 1);
+            }
+
+            if (!values.length) {
+              delete map[key];
+            }
+          }
+        };
+      }
+    };
+  })
+
+/**
+ * A helper directive for the $modal service. It creates a backdrop element.
+ */
+  .directive('uibModalBackdrop', [
+           '$animate', '$injector', '$uibModalStack',
+  function($animate ,  $injector,   $modalStack) {
+    var $animateCss = null;
+
+    if ($injector.has('$animateCss')) {
+      $animateCss = $injector.get('$animateCss');
+    }
+
+    return {
+      replace: true,
+      templateUrl: 'template/modal/backdrop.html',
+      compile: function(tElement, tAttrs) {
+        tElement.addClass(tAttrs.backdropClass);
+        return linkFn;
+      }
+    };
+
+    function linkFn(scope, element, attrs) {
+      // Temporary fix for prefixing
+      element.addClass('modal-backdrop');
+
+      if (attrs.modalInClass) {
+        if ($animateCss) {
+          $animateCss(element, {
+            addClass: attrs.modalInClass
+          }).start();
+        } else {
+          $animate.addClass(element, attrs.modalInClass);
+        }
+
+        scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
+          var done = setIsAsync();
+          if ($animateCss) {
+            $animateCss(element, {
+              removeClass: attrs.modalInClass
+            }).start().then(done);
+          } else {
+            $animate.removeClass(element, attrs.modalInClass).then(done);
+          }
+        });
+      }
+    }
+  }])
+
+  .directive('uibModalWindow', [
+           '$uibModalStack', '$q', '$animate', '$injector',
+  function($modalStack ,  $q ,  $animate,   $injector) {
+    var $animateCss = null;
+
+    if ($injector.has('$animateCss')) {
+      $animateCss = $injector.get('$animateCss');
+    }
+
+    return {
+      scope: {
+        index: '@'
+      },
+      replace: true,
+      transclude: true,
+      templateUrl: function(tElement, tAttrs) {
+        return tAttrs.templateUrl || 'template/modal/window.html';
+      },
+      link: function(scope, element, attrs) {
+        element.addClass(attrs.windowClass || '');
+        element.addClass(attrs.windowTopClass || '');
+        scope.size = attrs.size;
+
+        scope.close = function(evt) {
+          var modal = $modalStack.getTop();
+          if (modal && modal.value.backdrop && modal.value.backdrop !== 'static' && (evt.target === evt.currentTarget)) {
+            evt.preventDefault();
+            evt.stopPropagation();
+            $modalStack.dismiss(modal.key, 'backdrop click');
+          }
+        };
+
+        // moved from template to fix issue #2280
+        element.on('click', scope.close);
+
+        // This property is only added to the scope for the purpose of detecting when this directive is rendered.
+        // We can detect that by using this property in the template associated with this directive and then use
+        // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.
+        scope.$isRendered = true;
+
+        // Deferred object that will be resolved when this modal is render.
+        var modalRenderDeferObj = $q.defer();
+        // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready.
+        // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template.
+        attrs.$observe('modalRender', function(value) {
+          if (value == 'true') {
+            modalRenderDeferObj.resolve();
+          }
+        });
+
+        modalRenderDeferObj.promise.then(function() {
+          var animationPromise = null;
+
+          if (attrs.modalInClass) {
+            if ($animateCss) {
+              animationPromise = $animateCss(element, {
+                addClass: attrs.modalInClass
+              }).start();
+            } else {
+              animationPromise = $animate.addClass(element, attrs.modalInClass);
+            }
+
+            scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
+              var done = setIsAsync();
+              if ($animateCss) {
+                $animateCss(element, {
+                  removeClass: attrs.modalInClass
+                }).start().then(done);
+              } else {
+                $animate.removeClass(element, attrs.modalInClass).then(done);
+              }
+            });
+          }
+
+
+          $q.when(animationPromise).then(function() {
+            var inputWithAutofocus = element[0].querySelector('[autofocus]');
+            /**
+             * Auto-focusing of a freshly-opened modal element causes any child elements
+             * with the autofocus attribute to lose focus. This is an issue on touch
+             * based devices which will show and then hide the onscreen keyboard.
+             * Attempts to refocus the autofocus element via JavaScript will not reopen
+             * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
+             * the modal element if the modal does not contain an autofocus element.
+             */
+            if (inputWithAutofocus) {
+              inputWithAutofocus.focus();
+            } else {
+              element[0].focus();
+            }
+          });
+
+          // Notify {@link $modalStack} that modal is rendered.
+          var modal = $modalStack.getTop();
+          if (modal) {
+            $modalStack.modalRendered(modal.key);
+          }
+        });
+      }
+    };
+  }])
+
+  .directive('uibModalAnimationClass', function() {
+    return {
+      compile: function(tElement, tAttrs) {
+        if (tAttrs.modalAnimation) {
+          tElement.addClass(tAttrs.uibModalAnimationClass);
+        }
+      }
+    };
+  })
+
+  .directive('uibModalTransclude', function() {
+    return {
+      link: function($scope, $element, $attrs, controller, $transclude) {
+        $transclude($scope.$parent, function(clone) {
+          $element.empty();
+          $element.append(clone);
+        });
+      }
+    };
+  })
+
+  .factory('$uibModalStack', [
+             '$animate', '$timeout', '$document', '$compile', '$rootScope',
+             '$q',
+             '$injector',
+             '$$multiMap',
+             '$$stackedMap',
+    function($animate ,  $timeout ,  $document ,  $compile ,  $rootScope ,
+              $q,
+              $injector,
+              $$multiMap,
+              $$stackedMap) {
+      var $animateCss = null;
+
+      if ($injector.has('$animateCss')) {
+        $animateCss = $injector.get('$animateCss');
+      }
+
+      var OPENED_MODAL_CLASS = 'modal-open';
+
+      var backdropDomEl, backdropScope;
+      var openedWindows = $$stackedMap.createNew();
+      var openedClasses = $$multiMap.createNew();
+      var $modalStack = {
+        NOW_CLOSING_EVENT: 'modal.stack.now-closing'
+      };
+
+      //Modal focus behavior
+      var focusableElementList;
+      var focusIndex = 0;
+      var tababbleSelector = 'a[href], area[href], input:not([disabled]), ' +
+        'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' +
+        'iframe, object, embed, *[tabindex], *[contenteditable=true]';
+
+      function backdropIndex() {
+        var topBackdropIndex = -1;
+        var opened = openedWindows.keys();
+        for (var i = 0; i < opened.length; i++) {
+          if (openedWindows.get(opened[i]).value.backdrop) {
+            topBackdropIndex = i;
+          }
+        }
+        return topBackdropIndex;
+      }
+
+      $rootScope.$watch(backdropIndex, function(newBackdropIndex) {
+        if (backdropScope) {
+          backdropScope.index = newBackdropIndex;
+        }
+      });
+
+      function removeModalWindow(modalInstance, elementToReceiveFocus) {
+        var body = $document.find('body').eq(0);
+        var modalWindow = openedWindows.get(modalInstance).value;
+
+        //clean up the stack
+        openedWindows.remove(modalInstance);
+
+        removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() {
+          var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS;
+          openedClasses.remove(modalBodyClass, modalInstance);
+          body.toggleClass(modalBodyClass, openedClasses.hasKey(modalBodyClass));
+          toggleTopWindowClass(true);
+        });
+        checkRemoveBackdrop();
+
+        //move focus to specified element if available, or else to body
+        if (elementToReceiveFocus && elementToReceiveFocus.focus) {
+          elementToReceiveFocus.focus();
+        } else {
+          body.focus();
+        }
+      }
+
+      // Add or remove "windowTopClass" from the top window in the stack
+      function toggleTopWindowClass(toggleSwitch) {
+        var modalWindow;
+
+        if (openedWindows.length() > 0) {
+          modalWindow = openedWindows.top().value;
+          modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch);
+        }
+      }
+
+      function checkRemoveBackdrop() {
+        //remove backdrop if no longer needed
+        if (backdropDomEl && backdropIndex() == -1) {
+          var backdropScopeRef = backdropScope;
+          removeAfterAnimate(backdropDomEl, backdropScope, function() {
+            backdropScopeRef = null;
+          });
+          backdropDomEl = undefined;
+          backdropScope = undefined;
+        }
+      }
+
+      function removeAfterAnimate(domEl, scope, done) {
+        var asyncDeferred;
+        var asyncPromise = null;
+        var setIsAsync = function() {
+          if (!asyncDeferred) {
+            asyncDeferred = $q.defer();
+            asyncPromise = asyncDeferred.promise;
+          }
+
+          return function asyncDone() {
+            asyncDeferred.resolve();
+          };
+        };
+        scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync);
+
+        // Note that it's intentional that asyncPromise might be null.
+        // That's when setIsAsync has not been called during the
+        // NOW_CLOSING_EVENT broadcast.
+        return $q.when(asyncPromise).then(afterAnimating);
+
+        function afterAnimating() {
+          if (afterAnimating.done) {
+            return;
+          }
+          afterAnimating.done = true;
+
+          if ($animateCss) {
+            $animateCss(domEl, {
+              event: 'leave'
+            }).start().then(function() {
+              domEl.remove();
+            });
+          } else {
+            $animate.leave(domEl);
+          }
+          scope.$destroy();
+          if (done) {
+            done();
+          }
+        }
+      }
+
+      $document.bind('keydown', function(evt) {
+        if (evt.isDefaultPrevented()) {
+          return evt;
+        }
+
+        var modal = openedWindows.top();
+        if (modal && modal.value.keyboard) {
+          switch (evt.which) {
+            case 27: {
+              evt.preventDefault();
+              $rootScope.$apply(function() {
+                $modalStack.dismiss(modal.key, 'escape key press');
+              });
+              break;
+            }
+            case 9: {
+              $modalStack.loadFocusElementList(modal);
+              var focusChanged = false;
+              if (evt.shiftKey) {
+                if ($modalStack.isFocusInFirstItem(evt)) {
+                  focusChanged = $modalStack.focusLastFocusableElement();
+                }
+              } else {
+                if ($modalStack.isFocusInLastItem(evt)) {
+                  focusChanged = $modalStack.focusFirstFocusableElement();
+                }
+              }
+
+              if (focusChanged) {
+                evt.preventDefault();
+                evt.stopPropagation();
+              }
+              break;
+            }
+          }
+        }
+      });
+
+      $modalStack.open = function(modalInstance, modal) {
+        var modalOpener = $document[0].activeElement,
+          modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS;
+
+        toggleTopWindowClass(false);
+
+        openedWindows.add(modalInstance, {
+          deferred: modal.deferred,
+          renderDeferred: modal.renderDeferred,
+          modalScope: modal.scope,
+          backdrop: modal.backdrop,
+          keyboard: modal.keyboard,
+          openedClass: modal.openedClass,
+          windowTopClass: modal.windowTopClass
+        });
+
+        openedClasses.put(modalBodyClass, modalInstance);
+
+        var body = $document.find('body').eq(0),
+            currBackdropIndex = backdropIndex();
+
+        if (currBackdropIndex >= 0 && !backdropDomEl) {
+          backdropScope = $rootScope.$new(true);
+          backdropScope.index = currBackdropIndex;
+          var angularBackgroundDomEl = angular.element('<div uib-modal-backdrop="modal-backdrop"></div>');
+          angularBackgroundDomEl.attr('backdrop-class', modal.backdropClass);
+          if (modal.animation) {
+            angularBackgroundDomEl.attr('modal-animation', 'true');
+          }
+          backdropDomEl = $compile(angularBackgroundDomEl)(backdropScope);
+          body.append(backdropDomEl);
+        }
+
+        var angularDomEl = angular.element('<div uib-modal-window="modal-window"></div>');
+        angularDomEl.attr({
+          'template-url': modal.windowTemplateUrl,
+          'window-class': modal.windowClass,
+          'window-top-class': modal.windowTopClass,
+          'size': modal.size,
+          'index': openedWindows.length() - 1,
+          'animate': 'animate'
+        }).html(modal.content);
+        if (modal.animation) {
+          angularDomEl.attr('modal-animation', 'true');
+        }
+
+        var modalDomEl = $compile(angularDomEl)(modal.scope);
+        openedWindows.top().value.modalDomEl = modalDomEl;
+        openedWindows.top().value.modalOpener = modalOpener;
+        body.append(modalDomEl);
+        body.addClass(modalBodyClass);
+
+        $modalStack.clearFocusListCache();
+      };
+
+      function broadcastClosing(modalWindow, resultOrReason, closing) {
+        return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented;
+      }
+
+      $modalStack.close = function(modalInstance, result) {
+        var modalWindow = openedWindows.get(modalInstance);
+        if (modalWindow && broadcastClosing(modalWindow, result, true)) {
+          modalWindow.value.modalScope.$$uibDestructionScheduled = true;
+          modalWindow.value.deferred.resolve(result);
+          removeModalWindow(modalInstance, modalWindow.value.modalOpener);
+          return true;
+        }
+        return !modalWindow;
+      };
+
+      $modalStack.dismiss = function(modalInstance, reason) {
+        var modalWindow = openedWindows.get(modalInstance);
+        if (modalWindow && broadcastClosing(modalWindow, reason, false)) {
+          modalWindow.value.modalScope.$$uibDestructionScheduled = true;
+          modalWindow.value.deferred.reject(reason);
+          removeModalWindow(modalInstance, modalWindow.value.modalOpener);
+          return true;
+        }
+        return !modalWindow;
+      };
+
+      $modalStack.dismissAll = function(reason) {
+        var topModal = this.getTop();
+        while (topModal && this.dismiss(topModal.key, reason)) {
+          topModal = this.getTop();
+        }
+      };
+
+      $modalStack.getTop = function() {
+        return openedWindows.top();
+      };
+
+      $modalStack.modalRendered = function(modalInstance) {
+        var modalWindow = openedWindows.get(modalInstance);
+        if (modalWindow) {
+          modalWindow.value.renderDeferred.resolve();
+        }
+      };
+
+      $modalStack.focusFirstFocusableElement = function() {
+        if (focusableElementList.length > 0) {
+          focusableElementList[0].focus();
+          return true;
+        }
+        return false;
+      };
+      $modalStack.focusLastFocusableElement = function() {
+        if (focusableElementList.length > 0) {
+          focusableElementList[focusableElementList.length - 1].focus();
+          return true;
+        }
+        return false;
+      };
+
+      $modalStack.isFocusInFirstItem = function(evt) {
+        if (focusableElementList.length > 0) {
+          return (evt.target || evt.srcElement) == focusableElementList[0];
+        }
+        return false;
+      };
+
+      $modalStack.isFocusInLastItem = function(evt) {
+        if (focusableElementList.length > 0) {
+          return (evt.target || evt.srcElement) == focusableElementList[focusableElementList.length - 1];
+        }
+        return false;
+      };
+
+      $modalStack.clearFocusListCache = function() {
+        focusableElementList = [];
+        focusIndex = 0;
+      };
+
+      $modalStack.loadFocusElementList = function(modalWindow) {
+        if (focusableElementList === undefined || !focusableElementList.length) {
+          if (modalWindow) {
+            var modalDomE1 = modalWindow.value.modalDomEl;
+            if (modalDomE1 && modalDomE1.length) {
+              focusableElementList = modalDomE1[0].querySelectorAll(tababbleSelector);
+            }
+          }
+        }
+      };
+
+      return $modalStack;
+    }])
+
+  .provider('$uibModal', function() {
+    var $modalProvider = {
+      options: {
+        animation: true,
+        backdrop: true, //can also be false or 'static'
+        keyboard: true
+      },
+      $get: ['$injector', '$rootScope', '$q', '$templateRequest', '$controller', '$uibModalStack', '$modalSuppressWarning', '$log',
+        function ($injector, $rootScope, $q, $templateRequest, $controller, $modalStack, $modalSuppressWarning, $log) {
+          var $modal = {};
+
+          function getTemplatePromise(options) {
+            return options.template ? $q.when(options.template) :
+              $templateRequest(angular.isFunction(options.templateUrl) ? (options.templateUrl)() : options.templateUrl);
+          }
+
+          function getResolvePromises(resolves) {
+            var promisesArr = [];
+            angular.forEach(resolves, function(value) {
+              if (angular.isFunction(value) || angular.isArray(value)) {
+                promisesArr.push($q.when($injector.invoke(value)));
+              } else if (angular.isString(value)) {
+                promisesArr.push($q.when($injector.get(value)));
+              } else {
+                promisesArr.push($q.when(value));
+              }
+            });
+            return promisesArr;
+          }
+
+          var promiseChain = null;
+          $modal.getPromiseChain = function() {
+            return promiseChain;
+          };
+
+          $modal.open = function(modalOptions) {
+            var modalResultDeferred = $q.defer();
+            var modalOpenedDeferred = $q.defer();
+            var modalRenderDeferred = $q.defer();
+
+            //prepare an instance of a modal to be injected into controllers and returned to a caller
+            var modalInstance = {
+              result: modalResultDeferred.promise,
+              opened: modalOpenedDeferred.promise,
+              rendered: modalRenderDeferred.promise,
+              close: function (result) {
+                return $modalStack.close(modalInstance, result);
+              },
+              dismiss: function (reason) {
+                return $modalStack.dismiss(modalInstance, reason);
+              }
+            };
+
+            //merge and clean up options
+            modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
+            modalOptions.resolve = modalOptions.resolve || {};
+
+            //verify options
+            if (!modalOptions.template && !modalOptions.templateUrl) {
+              throw new Error('One of template or templateUrl options is required.');
+            }
+
+            var templateAndResolvePromise =
+              $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve)));
+
+            function resolveWithTemplate() {
+              return templateAndResolvePromise;
+            }
+
+            // Wait for the resolution of the existing promise chain.
+            // Then switch to our own combined promise dependency (regardless of how the previous modal fared).
+            // Then add to $modalStack and resolve opened.
+            // Finally clean up the chain variable if no subsequent modal has overwritten it.
+            var samePromise;
+            samePromise = promiseChain = $q.all([promiseChain])
+              .then(resolveWithTemplate, resolveWithTemplate)
+              .then(function resolveSuccess(tplAndVars) {
+
+                var modalScope = (modalOptions.scope || $rootScope).$new();
+                modalScope.$close = modalInstance.close;
+                modalScope.$dismiss = modalInstance.dismiss;
+
+                modalScope.$on('$destroy', function() {
+                  if (!modalScope.$$uibDestructionScheduled) {
+                    modalScope.$dismiss('$uibUnscheduledDestruction');
+                  }
+                });
+
+                var ctrlInstance, ctrlLocals = {};
+                var resolveIter = 1;
+
+                //controllers
+                if (modalOptions.controller) {
+                  ctrlLocals.$scope = modalScope;
+                  ctrlLocals.$uibModalInstance = modalInstance;
+                  Object.defineProperty(ctrlLocals, '$modalInstance', {
+                    get: function() {
+                      if (!$modalSuppressWarning) {
+                        $log.warn('$modalInstance is now deprecated. Use $uibModalInstance instead.');
+                      }
+
+                      return modalInstance;
+                    }
+                  });
+                  angular.forEach(modalOptions.resolve, function(value, key) {
+                    ctrlLocals[key] = tplAndVars[resolveIter++];
+                  });
+
+                  ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
+                  if (modalOptions.controllerAs) {
+                    if (modalOptions.bindToController) {
+                      angular.extend(ctrlInstance, modalScope);
+                    }
+
+                    modalScope[modalOptions.controllerAs] = ctrlInstance;
+                  }
+                }
+
+                $modalStack.open(modalInstance, {
+                  scope: modalScope,
+                  deferred: modalResultDeferred,
+                  renderDeferred: modalRenderDeferred,
+                  content: tplAndVars[0],
+                  animation: modalOptions.animation,
+                  backdrop: modalOptions.backdrop,
+                  keyboard: modalOptions.keyboard,
+                  backdropClass: modalOptions.backdropClass,
+                  windowTopClass: modalOptions.windowTopClass,
+                  windowClass: modalOptions.windowClass,
+                  windowTemplateUrl: modalOptions.windowTemplateUrl,
+                  size: modalOptions.size,
+                  openedClass: modalOptions.openedClass
+                });
+                modalOpenedDeferred.resolve(true);
+
+            }, function resolveError(reason) {
+              modalOpenedDeferred.reject(reason);
+              modalResultDeferred.reject(reason);
+            })
+            .finally(function() {
+              if (promiseChain === samePromise) {
+                promiseChain = null;
+              }
+            });
+
+            return modalInstance;
+          };
+
+          return $modal;
+        }
+      ]
+    };
+
+    return $modalProvider;
+  });
+
+/* deprecated modal below */
+
+angular.module('ui.bootstrap.modal')
+
+  .value('$modalSuppressWarning', false)
+
+  /**
+   * A helper directive for the $modal service. It creates a backdrop element.
+   */
+  .directive('modalBackdrop', [
+    '$animate', '$injector', '$modalStack', '$log', '$modalSuppressWarning',
+    function($animate ,  $injector,   $modalStack, $log, $modalSuppressWarning) {
+      var $animateCss = null;
+
+      if ($injector.has('$animateCss')) {
+        $animateCss = $injector.get('$animateCss');
+      }
+
+      return {
+        replace: true,
+        templateUrl: 'template/modal/backdrop.html',
+        compile: function(tElement, tAttrs) {
+          tElement.addClass(tAttrs.backdropClass);
+          return linkFn;
+        }
+      };
+
+      function linkFn(scope, element, attrs) {
+        if (!$modalSuppressWarning) {
+          $log.warn('modal-backdrop is now deprecated. Use uib-modal-backdrop instead.');
+        }
+        element.addClass('modal-backdrop');
+
+        if (attrs.modalInClass) {
+          if ($animateCss) {
+            $animateCss(element, {
+              addClass: attrs.modalInClass
+            }).start();
+          } else {
+            $animate.addClass(element, attrs.modalInClass);
+          }
+
+          scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
+            var done = setIsAsync();
+            if ($animateCss) {
+              $animateCss(element, {
+                removeClass: attrs.modalInClass
+              }).start().then(done);
+            } else {
+              $animate.removeClass(element, attrs.modalInClass).then(done);
+            }
+          });
+        }
+      }
+    }])
+
+  .directive('modalWindow', [
+    '$modalStack', '$q', '$animate', '$injector', '$log', '$modalSuppressWarning',
+    function($modalStack ,  $q ,  $animate,   $injector, $log, $modalSuppressWarning) {
+      var $animateCss = null;
+
+      if ($injector.has('$animateCss')) {
+        $animateCss = $injector.get('$animateCss');
+      }
+
+      return {
+        scope: {
+          index: '@'
+        },
+        replace: true,
+        transclude: true,
+        templateUrl: function(tElement, tAttrs) {
+          return tAttrs.templateUrl || 'template/modal/window.html';
+        },
+        link: function(scope, element, attrs) {
+          if (!$modalSuppressWarning) {
+            $log.warn('modal-window is now deprecated. Use uib-modal-window instead.');
+          }
+          element.addClass(attrs.windowClass || '');
+          element.addClass(attrs.windowTopClass || '');
+          scope.size = attrs.size;
+
+          scope.close = function(evt) {
+            var modal = $modalStack.getTop();
+            if (modal && modal.value.backdrop && modal.value.backdrop !== 'static' && (evt.target === evt.currentTarget)) {
+              evt.preventDefault();
+              evt.stopPropagation();
+              $modalStack.dismiss(modal.key, 'backdrop click');
+            }
+          };
+
+          // moved from template to fix issue #2280
+          element.on('click', scope.close);
+
+          // This property is only added to the scope for the purpose of detecting when this directive is rendered.
+          // We can detect that by using this property in the template associated with this directive and then use
+          // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.
+          scope.$isRendered = true;
+
+          // Deferred object that will be resolved when this modal is render.
+          var modalRenderDeferObj = $q.defer();
+          // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready.
+          // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template.
+          attrs.$observe('modalRender', function(value) {
+            if (value == 'true') {
+              modalRenderDeferObj.resolve();
+            }
+          });
+
+          modalRenderDeferObj.promise.then(function() {
+            var animationPromise = null;
+
+            if (attrs.modalInClass) {
+              if ($animateCss) {
+                animationPromise = $animateCss(element, {
+                  addClass: attrs.modalInClass
+                }).start();
+              } else {
+                animationPromise = $animate.addClass(element, attrs.modalInClass);
+              }
+
+              scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
+                var done = setIsAsync();
+                if ($animateCss) {
+                  $animateCss(element, {
+                    removeClass: attrs.modalInClass
+                  }).start().then(done);
+                } else {
+                  $animate.removeClass(element, attrs.modalInClass).then(done);
+                }
+              });
+            }
+
+
+            $q.when(animationPromise).then(function() {
+              var inputWithAutofocus = element[0].querySelector('[autofocus]');
+              /**
+               * Auto-focusing of a freshly-opened modal element causes any child elements
+               * with the autofocus attribute to lose focus. This is an issue on touch
+               * based devices which will show and then hide the onscreen keyboard.
+               * Attempts to refocus the autofocus element via JavaScript will not reopen
+               * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
+               * the modal element if the modal does not contain an autofocus element.
+               */
+              if (inputWithAutofocus) {
+                inputWithAutofocus.focus();
+              } else {
+                element[0].focus();
+              }
+            });
+
+            // Notify {@link $modalStack} that modal is rendered.
+            var modal = $modalStack.getTop();
+            if (modal) {
+              $modalStack.modalRendered(modal.key);
+            }
+          });
+        }
+      };
+    }])
+
+  .directive('modalAnimationClass', [
+    '$log', '$modalSuppressWarning',
+    function ($log, $modalSuppressWarning) {
+      return {
+        compile: function(tElement, tAttrs) {
+          if (!$modalSuppressWarning) {
+            $log.warn('modal-animation-class is now deprecated. Use uib-modal-animation-class instead.');
+          }
+          if (tAttrs.modalAnimation) {
+            tElement.addClass(tAttrs.modalAnimationClass);
+          }
+        }
+      };
+    }])
+
+  .directive('modalTransclude', [
+    '$log', '$modalSuppressWarning',
+    function ($log, $modalSuppressWarning) {
+    return {
+      link: function($scope, $element, $attrs, controller, $transclude) {
+        if (!$modalSuppressWarning) {
+          $log.warn('modal-transclude is now deprecated. Use uib-modal-transclude instead.');
+        }
+        $transclude($scope.$parent, function(clone) {
+          $element.empty();
+          $element.append(clone);
+        });
+      }
+    };
+  }])
+
+  .service('$modalStack', [
+    '$animate', '$timeout', '$document', '$compile', '$rootScope',
+    '$q',
+    '$injector',
+    '$$multiMap',
+    '$$stackedMap',
+    '$uibModalStack',
+    '$log',
+    '$modalSuppressWarning',
+    function($animate ,  $timeout ,  $document ,  $compile ,  $rootScope ,
+             $q,
+             $injector,
+             $$multiMap,
+             $$stackedMap,
+             $uibModalStack,
+             $log,
+             $modalSuppressWarning) {
+      if (!$modalSuppressWarning) {
+        $log.warn('$modalStack is now deprecated. Use $uibModalStack instead.');
+      }
+
+      angular.extend(this, $uibModalStack);
+    }])
+
+  .provider('$modal', ['$uibModalProvider', function($uibModalProvider) {
+    angular.extend(this, $uibModalProvider);
+
+    this.$get = ['$injector', '$log', '$modalSuppressWarning',
+      function ($injector, $log, $modalSuppressWarning) {
+        if (!$modalSuppressWarning) {
+          $log.warn('$modal is now deprecated. Use $uibModal instead.');
+        }
+
+        return $injector.invoke($uibModalProvider.$get);
+      }];
+  }]);
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/modal/test/modal.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/modal/test/modal.spec.js
new file mode 100644
index 0000000..a427584
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/modal/test/modal.spec.js
@@ -0,0 +1,1255 @@
+describe('$uibModal', function () {
+  var $animate, $controllerProvider, $rootScope, $document, $compile, $templateCache, $timeout, $q;
+  var $uibModal, $uibModalStack, $uibModalProvider;
+
+  beforeEach(module('ngAnimateMock'));
+  beforeEach(module('ui.bootstrap.modal'));
+  beforeEach(module('template/modal/backdrop.html'));
+  beforeEach(module('template/modal/window.html'));
+  beforeEach(module(function(_$controllerProvider_, _$uibModalProvider_){
+    $controllerProvider = _$controllerProvider_;
+    $uibModalProvider = _$uibModalProvider_;
+  }));
+
+  beforeEach(inject(function(_$animate_, _$rootScope_, _$document_, _$compile_, _$templateCache_, _$timeout_, _$q_, _$uibModal_, _$uibModalStack_) {
+    $animate = _$animate_;
+    $rootScope = _$rootScope_;
+    $document = _$document_;
+    $compile = _$compile_;
+    $templateCache = _$templateCache_;
+    $timeout = _$timeout_;
+    $q = _$q_;
+    $uibModal = _$uibModal_;
+    $uibModalStack = _$uibModalStack_;
+  }));
+
+  beforeEach(function() {
+    jasmine.addMatchers({
+      toBeResolvedWith: function(util, customEqualityTesters) {
+        return {
+          compare: function(promise, expected) {
+            promise.then(function(result) {
+              expect(result).toEqual(expected);
+
+              if (result === expected) {
+                result.message = 'Expected "' + angular.mock.dump(result) + '" not to be resolved with "' + expected + '".';
+              } else {
+                result.message = 'Expected "' + angular.mock.dump(result) + '" to be resolved with "' + expected + '".';
+              }
+            });
+
+            $rootScope.$digest();
+
+            return {pass: true};
+          }
+        };
+      },
+      toBeRejectedWith: function(util, customEqualityTesters) {
+        return {
+          compare: function(promise, expected) {
+            var result = {};
+
+            promise.then(function() {
+
+            }, function(result) {
+              expect(result).toEqual(expected);
+
+              if (result === expected) {
+                result.message = 'Expected "' + angular.mock.dump(result) + '" not to be rejected with "' + expected + '".';
+              } else {
+                result.message = 'Expected "' + angular.mock.dump(result) + '" to be rejected with "' + expected + '".';
+              }
+            });
+
+            $rootScope.$digest();
+
+            return {pass: true};
+          }
+        };
+      },
+      toHaveModalOpenWithContent: function(util, customEqualityTesters) {
+        return {
+          compare: function(actual, content, selector) {
+            var contentToCompare, modalDomEls = actual.find('body > div.modal > div.modal-dialog > div.modal-content');
+
+            contentToCompare = selector ? modalDomEls.find(selector) : modalDomEls;
+
+            var result = {
+              pass: modalDomEls.css('display') === 'block' && contentToCompare.html() === content
+            };
+
+            if (result.pass) {
+              result.message = '"Expected "' + angular.mock.dump(modalDomEls) + '" not to be open with "' + content + '".';
+            } else {
+              result.message = '"Expected "' + angular.mock.dump(modalDomEls) + '" to be open with "' + content + '".';
+            }
+
+            return result;
+          }
+        };
+      },
+      toHaveModalsOpen: function(util, customEqualityTesters) {
+        return {
+          compare: function(actual, expected) {
+            var modalDomEls = actual.find('body > div.modal');
+
+            var result = {
+              pass: util.equals(modalDomEls.length, expected, customEqualityTesters)
+            };
+
+            if (result.pass) {
+              result.message = 'Expected "' + angular.mock.dump(modalDomEls) + '" not to have "' + expected + '" modals opened.';
+            } else {
+              result.message = 'Expected "' + angular.mock.dump(modalDomEls) + '" to have "' + expected + '" modals opened.';
+            }
+
+            return result;
+          }
+        };
+      },
+      toHaveBackdrop: function(util, customEqualityTesters) {
+        return {
+          compare: function(actual, expected) {
+            var backdropDomEls = actual.find('body > div.modal-backdrop');
+
+            var result = {
+              pass: util.equals(backdropDomEls.length, 1, customEqualityTesters)
+            };
+
+            if (result.pass) {
+              result.message = 'Expected "' + angular.mock.dump(backdropDomEls) + '" not to be a backdrop element".';
+            } else {
+              result.message = 'Expected "' + angular.mock.dump(backdropDomEls) + '" to be a backdrop element".';
+            }
+
+            return result;
+          }
+        };
+      }
+    });
+  });
+
+  afterEach(function () {
+    var body = $document.find('body');
+    body.find('div.modal').remove();
+    body.find('div.modal-backdrop').remove();
+    body.removeClass('modal-open');
+  });
+
+  function triggerKeyDown(element, keyCode, shiftKey) {
+    var e = $.Event('keydown');
+    e.srcElement = element[0];
+    e.which = keyCode;
+    e.shiftKey = shiftKey;
+    element.trigger(e);
+  }
+
+  function open(modalOptions) {
+    var modal = $uibModal.open(modalOptions);
+    $rootScope.$digest();
+    $timeout.flush(0);
+    return modal;
+  }
+
+  function close(modal, result, noFlush) {
+    var closed = modal.close(result);
+    $rootScope.$digest();
+    if (!noFlush) {
+      $animate.flush();
+      $rootScope.$digest();
+      $animate.flush();
+      $rootScope.$digest();
+    }
+    return closed;
+  }
+
+  function dismiss(modal, reason, noFlush) {
+    var closed = modal.dismiss(reason);
+    $rootScope.$digest();
+    if (!noFlush) {
+      $animate.flush();
+      $rootScope.$digest();
+      $animate.flush();
+      $rootScope.$digest();
+    }
+    return closed;
+  }
+
+  describe('basic scenarios with default options', function() {
+    it('should open and dismiss a modal with a minimal set of options', function() {
+      var modal = open({template: '<div>Content</div>'});
+
+      expect($document).toHaveModalsOpen(1);
+      expect($document).toHaveModalOpenWithContent('Content', 'div');
+      expect($document).toHaveBackdrop();
+
+      dismiss(modal, 'closing in test');
+
+      expect($document).toHaveModalsOpen(0);
+
+      expect($document).not.toHaveBackdrop();
+    });
+
+    it('should not throw an exception on a second dismiss', function() {
+      var modal = open({template: '<div>Content</div>'});
+
+      expect($document).toHaveModalsOpen(1);
+      expect($document).toHaveModalOpenWithContent('Content', 'div');
+      expect($document).toHaveBackdrop();
+
+      dismiss(modal, 'closing in test');
+
+      expect($document).toHaveModalsOpen(0);
+
+      dismiss(modal, 'closing in test', true);
+    });
+
+    it('should not throw an exception on a second close', function() {
+      var modal = open({template: '<div>Content</div>'});
+
+      expect($document).toHaveModalsOpen(1);
+      expect($document).toHaveModalOpenWithContent('Content', 'div');
+      expect($document).toHaveBackdrop();
+
+      close(modal, 'closing in test');
+
+      expect($document).toHaveModalsOpen(0);
+
+      close(modal, 'closing in test', true);
+    });
+
+    it('should open a modal from templateUrl', function() {
+      $templateCache.put('content.html', '<div>URL Content</div>');
+      var modal = open({templateUrl: 'content.html'});
+
+      expect($document).toHaveModalsOpen(1);
+      expect($document).toHaveModalOpenWithContent('URL Content', 'div');
+      expect($document).toHaveBackdrop();
+
+      dismiss(modal, 'closing in test');
+
+      expect($document).toHaveModalsOpen(0);
+
+      expect($document).not.toHaveBackdrop();
+    });
+
+    it('should support closing on ESC', function() {
+      var modal = open({template: '<div>Content</div>'});
+      expect($document).toHaveModalsOpen(1);
+
+      triggerKeyDown($document, 27);
+      $animate.flush();
+      $rootScope.$digest();
+      $animate.flush();
+      $rootScope.$digest();
+
+      expect($document).toHaveModalsOpen(0);
+    });
+
+    it('should not close on ESC if event.preventDefault() was issued', function() {
+      var modal = open({template: '<div><button>x</button></div>' });
+      expect($document).toHaveModalsOpen(1);
+
+      var button = angular.element('button').on('keydown', preventKeyDown);
+
+      triggerKeyDown(button, 27);
+      $rootScope.$digest();
+
+      expect($document).toHaveModalsOpen(1);
+
+      button.off('keydown', preventKeyDown);
+
+      triggerKeyDown(button, 27);
+      $animate.flush();
+      $rootScope.$digest();
+      $animate.flush();
+      $rootScope.$digest();
+
+      expect($document).toHaveModalsOpen(0);
+
+      function preventKeyDown(evt) {
+        evt.preventDefault();
+      }
+    });
+
+    it('should support closing on backdrop click', function() {
+      var modal = open({template: '<div>Content</div>'});
+      expect($document).toHaveModalsOpen(1);
+
+      $document.find('body > div.modal').click();
+      $animate.flush();
+      $rootScope.$digest();
+      $animate.flush();
+      $rootScope.$digest();
+
+      expect($document).toHaveModalsOpen(0);
+    });
+
+    it('should return to the element which had focus before the dialog was invoked', function() {
+      var link = '<a href>Link</a>';
+      var element = angular.element(link);
+      angular.element(document.body).append(element);
+      element.focus();
+      expect(document.activeElement.tagName).toBe('A');
+
+      var modal = open({template: '<div>Content<button>inside modal</button></div>'});
+      $animate.flush();
+      $rootScope.$digest();
+      expect(document.activeElement.tagName).toBe('DIV');
+      expect($document).toHaveModalsOpen(1);
+
+      triggerKeyDown($document, 27);
+      $animate.flush();
+      $rootScope.$digest();
+      $animate.flush();
+      $rootScope.$digest();
+
+      expect(document.activeElement.tagName).toBe('A');
+      expect($document).toHaveModalsOpen(0);
+
+      element.remove();
+    });
+
+    it('should return to document.body if element which had focus before the dialog was invoked is gone, or is missing focus function', function() {
+      var link = '<a href>Link</a>';
+      var element = angular.element(link);
+      angular.element(document.body).append(element);
+      element.focus();
+      expect(document.activeElement.tagName).toBe('A');
+
+      var modal = open({template: '<div>Content</div>'});
+      $animate.flush();
+      $rootScope.$digest();
+      expect(document.activeElement.tagName).toBe('DIV');
+      expect($document).toHaveModalsOpen(1);
+
+      // Fake undefined focus function, happening in IE in certain
+      // iframe conditions. See issue 3639
+      element[0].focus = undefined;
+      triggerKeyDown($document, 27);
+      $animate.flush();
+      $rootScope.$digest();
+      $animate.flush();
+      $rootScope.$digest();
+
+      expect(document.activeElement.tagName).toBe('BODY');
+      expect($document).toHaveModalsOpen(0);
+      element.remove();
+    });
+
+    it('should resolve returned promise on close', function() {
+      var modal = open({template: '<div>Content</div>'});
+      close(modal, 'closed ok');
+
+      expect(modal.result).toBeResolvedWith('closed ok');
+    });
+
+    it('should reject returned promise on dismiss', function() {
+
+      var modal = open({template: '<div>Content</div>'});
+      dismiss(modal, 'esc');
+
+      expect(modal.result).toBeRejectedWith('esc');
+    });
+
+    it('should reject returned promise on unexpected closure', function() {
+      var scope = $rootScope.$new();
+      var modal = open({template: '<div>Content</div>', scope: scope});
+      scope.$destroy();
+
+      expect(modal.result).toBeRejectedWith('$uibUnscheduledDestruction');
+
+      $animate.flush();
+      $rootScope.$digest();
+      $animate.flush();
+      $rootScope.$digest();
+      expect($document).toHaveModalsOpen(0);
+    });
+
+    it('should expose a promise linked to the templateUrl / resolve promises', function() {
+      var modal = open({template: '<div>Content</div>', resolve: {
+          ok: function() {return $q.when('ok');}
+        }}
+      );
+      expect(modal.opened).toBeResolvedWith(true);
+    });
+
+    it('should expose a promise linked to the templateUrl / resolve promises and reject it if needed', function() {
+      var modal = open({template: '<div>Content</div>', resolve: {
+          ok: function() {return $q.reject('ko');}
+        }}
+      );
+      expect(modal.opened).toBeRejectedWith('ko');
+    });
+
+    it('should focus on the element that has autofocus attribute when the modal is open/reopen and the animations have finished', function() {
+      function openAndCloseModalWithAutofocusElement() {
+        var modal = open({template: '<div><input type="text" id="auto-focus-element" autofocus></div>'});
+        $animate.flush();
+        $rootScope.$digest();
+        expect(angular.element('#auto-focus-element')).toHaveFocus();
+
+        close(modal, 'closed ok');
+
+        expect(modal.result).toBeResolvedWith('closed ok');
+      }
+
+      openAndCloseModalWithAutofocusElement();
+      openAndCloseModalWithAutofocusElement();
+    });
+
+    it('should wait until the in animation is finished before attempting to focus the modal or autofocus element', function() {
+      function openAndCloseModalWithAutofocusElement() {
+        var modal = open({template: '<div><input type="text" id="auto-focus-element" autofocus></div>'});
+        expect(angular.element('#auto-focus-element')).not.toHaveFocus();
+
+        $animate.flush();
+        $rootScope.$digest();
+
+        expect(angular.element('#auto-focus-element')).toHaveFocus();
+
+        close(modal, 'closed ok');
+
+        expect(modal.result).toBeResolvedWith('closed ok');
+      }
+
+      function openAndCloseModalWithOutAutofocusElement() {
+        var link = '<a href>Link</a>';
+        var element = angular.element(link);
+        angular.element(document.body).append(element);
+        element.focus();
+        expect(document.activeElement.tagName).toBe('A');
+
+        var modal = open({template: '<div><input type="text"></div>'});
+        expect(document.activeElement.tagName).toBe('A');
+
+        $animate.flush();
+        $rootScope.$digest();
+
+        expect(document.activeElement.tagName).toBe('DIV');
+
+        close(modal, 'closed ok');
+
+        expect(modal.result).toBeResolvedWith('closed ok');
+      }
+
+      openAndCloseModalWithAutofocusElement();
+      openAndCloseModalWithOutAutofocusElement();
+    });
+
+    it('should change focus to first element when tab key was pressed', function() {
+      var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
+      angular.element(document.body).append(initialPage);
+      initialPage.focus();
+
+      open({
+        template:'<a href="#" id="tab-focus-link"><input type="text" id="tab-focus-input1"/><input type="text" id="tab-focus-input2"/>' +
+        '<button id="tab-focus-button">Open me!</button>'
+      });
+      expect($document).toHaveModalsOpen(1);
+
+      var lastElement = angular.element(document.getElementById('tab-focus-button'));
+      lastElement.focus();
+      triggerKeyDown(lastElement, 9);
+      expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link');
+
+      initialPage.remove();
+    });
+
+    it('should change focus to last element when shift+tab key is pressed', function() {
+      var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
+      angular.element(document.body).append(initialPage);
+      initialPage.focus();
+
+      open({
+        template:'<a href="#" id="tab-focus-link"><input type="text" id="tab-focus-input1"/><input type="text" id="tab-focus-input2"/>' +
+        '<button id="tab-focus-button">Open me!</button>'
+      });
+      expect($document).toHaveModalsOpen(1);
+
+      var lastElement = angular.element(document.getElementById('tab-focus-link'));
+      lastElement.focus();
+      triggerKeyDown(lastElement, 9, true);
+      expect(document.activeElement.getAttribute('id')).toBe('tab-focus-button');
+
+      initialPage.remove();
+    });
+  });
+
+  describe('default options can be changed in a provider', function() {
+    it('should allow overriding default options in a provider', function() {
+      $uibModalProvider.options.backdrop = false;
+      var modal = open({template: '<div>Content</div>'});
+
+      expect($document).toHaveModalOpenWithContent('Content', 'div');
+      expect($document).not.toHaveBackdrop();
+    });
+
+    it('should accept new objects with default options in a provider', function() {
+      $uibModalProvider.options = {
+        backdrop: false
+      };
+      var modal = open({template: '<div>Content</div>'});
+
+      expect($document).toHaveModalOpenWithContent('Content', 'div');
+      expect($document).not.toHaveBackdrop();
+    });
+  });
+
+  describe('option by option', function () {
+    describe('template and templateUrl', function () {
+      it('should throw an error if none of template and templateUrl are provided', function() {
+        expect(function(){
+          var modal = open({});
+        }).toThrow(new Error('One of template or templateUrl options is required.'));
+      });
+
+      it('should not fail if a templateUrl contains leading / trailing white spaces', function() {
+
+        $templateCache.put('whitespace.html', '  <div>Whitespaces</div>  ');
+        open({templateUrl: 'whitespace.html'});
+        expect($document).toHaveModalOpenWithContent('Whitespaces', 'div');
+      });
+
+      it('should accept template as a function', function() {
+        open({template: function() {
+          return '<div>From a function</div>';
+        }});
+
+        expect($document).toHaveModalOpenWithContent('From a function', 'div');
+      });
+
+      it('should not fail if a templateUrl as a function', function() {
+
+        $templateCache.put('whitespace.html', '  <div>Whitespaces</div>  ');
+        open({templateUrl: function() {
+          return 'whitespace.html';
+        }});
+        expect($document).toHaveModalOpenWithContent('Whitespaces', 'div');
+      });
+    });
+
+    describe('controller', function() {
+      it('should accept controllers and inject modal instances', function() {
+        var TestCtrl = function($scope, $uibModalInstance) {
+          $scope.fromCtrl = 'Content from ctrl';
+          $scope.isModalInstance = angular.isObject($uibModalInstance) && angular.isFunction($uibModalInstance.close);
+        };
+
+        open({template: '<div>{{fromCtrl}} {{isModalInstance}}</div>', controller: TestCtrl});
+        expect($document).toHaveModalOpenWithContent('Content from ctrl true', 'div');
+      });
+
+      it('should accept controllerAs alias', function() {
+        $controllerProvider.register('TestCtrl', function($uibModalInstance) {
+          this.fromCtrl = 'Content from ctrl';
+          this.isModalInstance = angular.isObject($uibModalInstance) && angular.isFunction($uibModalInstance.close);
+        });
+
+        open({template: '<div>{{test.fromCtrl}} {{test.isModalInstance}}</div>', controller: 'TestCtrl as test'});
+        expect($document).toHaveModalOpenWithContent('Content from ctrl true', 'div');
+      });
+
+      it('should respect the controllerAs property as an alternative for the controller-as syntax', function() {
+        $controllerProvider.register('TestCtrl', function($uibModalInstance) {
+          this.fromCtrl = 'Content from ctrl';
+          this.isModalInstance = angular.isObject($uibModalInstance) && angular.isFunction($uibModalInstance.close);
+        });
+
+        open({template: '<div>{{test.fromCtrl}} {{test.isModalInstance}}</div>', controller: 'TestCtrl', controllerAs: 'test'});
+        expect($document).toHaveModalOpenWithContent('Content from ctrl true', 'div');
+      });
+
+      it('should allow defining in-place controller-as controllers', function() {
+        open({template: '<div>{{test.fromCtrl}} {{test.isModalInstance}}</div>', controller: function($uibModalInstance) {
+          this.fromCtrl = 'Content from ctrl';
+          this.isModalInstance = angular.isObject($uibModalInstance) && angular.isFunction($uibModalInstance.close);
+        }, controllerAs: 'test'});
+        expect($document).toHaveModalOpenWithContent('Content from ctrl true', 'div');
+      });
+
+      it('should allow usage of bindToController', function() {
+        open({template: '<div>{{test.fromCtrl}} {{test.isModalInstance}}</div>', controller: function($uibModalInstance) {
+          this.fromCtrl = 'Content from ctrl';
+          this.isModalInstance = angular.isObject($uibModalInstance) && angular.isFunction($uibModalInstance.close);
+        }, controllerAs: 'test', bindToController: true});
+        expect($document).toHaveModalOpenWithContent('Content from ctrl true', 'div');
+      });
+    });
+
+    describe('resolve', function() {
+      var ExposeCtrl = function($scope, value) {
+        $scope.value = value;
+      };
+
+      function modalDefinition(template, resolve) {
+        return {
+          template: template,
+          controller: ExposeCtrl,
+          resolve: resolve
+        };
+      }
+
+      it('should resolve simple values', function() {
+        open(modalDefinition('<div>{{value}}</div>', {
+          value: function() {
+            return 'Content from resolve';
+          }
+        }));
+
+        expect($document).toHaveModalOpenWithContent('Content from resolve', 'div');
+      });
+
+      it('should resolve string references to injectables', function() {
+        open({
+          controller: function($scope, $foo) {
+            $scope.value = 'Content from resolve';
+            expect($foo).toBe($uibModal);
+          },
+          resolve: {
+            $foo: '$uibModal'
+          },
+          template: '<div>{{value}}</div>'
+        });
+
+        expect($document).toHaveModalOpenWithContent('Content from resolve', 'div');
+      });
+
+      it('should resolve promises as promises', function() {
+        open({
+          controller: function($scope, $foo) {
+            $scope.value = 'Content from resolve';
+            expect($foo).toBe('bar');
+          },
+          resolve: {
+            $foo: $q.when('bar')
+          },
+          template: '<div>{{value}}</div>'
+        });
+      });
+
+      it('should delay showing modal if one of the resolves is a promise', function() {
+        open(modalDefinition('<div>{{value}}</div>', {
+          value: function() {
+            return $timeout(function() { return 'Promise'; }, 100);
+          }
+        }));
+        expect($document).toHaveModalsOpen(0);
+
+        $timeout.flush();
+        expect($document).toHaveModalOpenWithContent('Promise', 'div');
+      });
+
+      it('should not open dialog (and reject returned promise) if one of resolve fails', function() {
+        var deferred = $q.defer();
+
+        var modal = open(modalDefinition('<div>{{value}}</div>', {
+          value: function() {
+            return deferred.promise;
+          }
+        }));
+        expect($document).toHaveModalsOpen(0);
+
+        deferred.reject('error in test');
+        $rootScope.$digest();
+
+        expect($document).toHaveModalsOpen(0);
+        expect(modal.result).toBeRejectedWith('error in test');
+      });
+
+      it('should support injection with minification-safe syntax in resolve functions', function() {
+        open(modalDefinition('<div>{{value.id}}</div>', {
+          value: ['$locale', function (e) {
+            return e;
+          }]
+        }));
+
+        expect($document).toHaveModalOpenWithContent('en-us', 'div');
+      });
+
+      //TODO: resolves with dependency injection - do we want to support them?
+    });
+
+    describe('scope', function() {
+      it('should use custom scope if provided', function() {
+        var $scope = $rootScope.$new();
+        $scope.fromScope = 'Content from custom scope';
+        open({
+          template: '<div>{{fromScope}}</div>',
+          scope: $scope
+        });
+        expect($document).toHaveModalOpenWithContent('Content from custom scope', 'div');
+      });
+
+      it('should create and use child of $rootScope if custom scope not provided', function() {
+        var scopeTailBefore = $rootScope.$$childTail;
+
+        $rootScope.fromScope = 'Content from root scope';
+        open({
+          template: '<div>{{fromScope}}</div>'
+        });
+        expect($document).toHaveModalOpenWithContent('Content from root scope', 'div');
+      });
+    });
+
+    describe('keyboard', function () {
+      it('should not close modals if keyboard option is set to false', function() {
+        open({
+          template: '<div>No keyboard</div>',
+          keyboard: false
+        });
+
+        expect($document).toHaveModalsOpen(1);
+
+        triggerKeyDown($document, 27);
+        $rootScope.$digest();
+
+        expect($document).toHaveModalsOpen(1);
+      });
+    });
+
+    describe('backdrop', function() {
+
+      it('should not have any backdrop element if backdrop set to false', function() {
+        var modal = open({
+          template: '<div>No backdrop</div>',
+          backdrop: false
+        });
+        expect($document).toHaveModalOpenWithContent('No backdrop', 'div');
+        expect($document).not.toHaveBackdrop();
+
+        dismiss(modal);
+        expect($document).toHaveModalsOpen(0);
+      });
+
+      it('should not close modal on backdrop click if backdrop is specified as "static"', function() {
+        open({
+          template: '<div>Static backdrop</div>',
+          backdrop: 'static'
+        });
+
+        $document.find('body > div.modal-backdrop').click();
+        $rootScope.$digest();
+
+        expect($document).toHaveModalOpenWithContent('Static backdrop', 'div');
+        expect($document).toHaveBackdrop();
+      });
+
+      it('should contain backdrop in classes on each modal opening', function() {
+        var modal = open({ template: '<div>With backdrop</div>' });
+        $animate.flush();
+        var backdropEl = $document.find('body > div.modal-backdrop');
+        expect(backdropEl).toHaveClass('in');
+
+        dismiss(modal);
+
+        modal = open({ template: '<div>With backdrop</div>' });
+        $animate.flush();
+        backdropEl = $document.find('body > div.modal-backdrop');
+        expect(backdropEl).toHaveClass('in');
+
+      });
+
+      describe('custom backdrop classes', function () {
+        it('should support additional backdrop class as string', function() {
+          open({
+            template: '<div>With custom backdrop class</div>',
+            backdropClass: 'additional'
+          });
+
+          expect($document.find('div.modal-backdrop')).toHaveClass('additional');
+        });
+      });
+    });
+
+    describe('custom window classes', function() {
+      it('should support additional window class as string', function() {
+        open({
+          template: '<div>With custom window class</div>',
+          windowClass: 'additional'
+        });
+
+        expect($document.find('div.modal')).toHaveClass('additional');
+      });
+    });
+
+    describe('top window class', function () {
+      it('should support top class option', function () {
+        open({
+          template: '<div>With custom window top class</div>',
+          windowTopClass: 'top-class'
+        });
+
+        expect($document.find('div.modal')).toHaveClass('top-class');
+      });
+    });
+
+    describe('size', function() {
+      it('should support creating small modal dialogs', function() {
+        open({
+          template: '<div>Small modal dialog</div>',
+          size: 'sm'
+        });
+
+        expect($document.find('div.modal-dialog')).toHaveClass('modal-sm');
+      });
+
+      it('should support creating large modal dialogs', function() {
+        open({
+          template: '<div>Large modal dialog</div>',
+          size: 'lg'
+        });
+
+        expect($document.find('div.modal-dialog')).toHaveClass('modal-lg');
+      });
+
+      it('should support custom size modal dialogs', function() {
+        open({
+          template: '<div>Large modal dialog</div>',
+          size: 'custom'
+        });
+
+        expect($document.find('div.modal-dialog')).toHaveClass('modal-custom');
+      });
+    });
+
+    describe('animation', function() {
+      it('should have animation fade classes by default', function() {
+        open({
+          template: '<div>Small modal dialog</div>'
+        });
+
+        expect($document.find('.modal')).toHaveClass('fade');
+        expect($document.find('.modal-backdrop')).toHaveClass('fade');
+      });
+
+      it('should not have fade classes if animation false', function() {
+        open({
+          template: '<div>Small modal dialog</div>',
+          animation: false
+        });
+
+        expect($document.find('.modal')).not.toHaveClass('fade');
+        expect($document.find('.modal-backdrop')).not.toHaveClass('fade');
+      });
+    });
+
+    describe('openedClass', function() {
+      var body;
+
+      beforeEach(function() {
+        body = $document.find('body');
+      });
+
+      it('should add the modal-open class to the body element by default', function() {
+        open({
+          template: '<div>dummy modal</div>'
+        });
+
+        expect(body).toHaveClass('modal-open');
+      });
+
+      it('should add the custom class to the body element', function() {
+        open({
+          template: '<div>dummy modal</div>',
+          openedClass: 'foo'
+        });
+
+        expect(body).toHaveClass('foo');
+        expect(body).not.toHaveClass('modal-open');
+      });
+
+      it('should remove the custom class on closing of modal', function() {
+        var modal = open({
+          template: '<div>dummy modal</div>',
+          openedClass: 'foo'
+        });
+
+        expect(body).toHaveClass('foo');
+
+        close(modal);
+
+        expect(body).not.toHaveClass('foo');
+      });
+
+      it('should add multiple custom classes to the body element and remove appropriately', function() {
+        var modal1 = open({
+          template: '<div>dummy modal</div>',
+          openedClass: 'foo'
+        });
+
+        expect(body).toHaveClass('foo');
+        expect(body).not.toHaveClass('modal-open');
+
+        var modal2 = open({
+          template: '<div>dummy modal</div>',
+          openedClass: 'bar'
+        });
+
+        expect(body).toHaveClass('foo');
+        expect(body).toHaveClass('bar');
+        expect(body).not.toHaveClass('modal-open');
+
+        var modal3 = open({
+          template: '<div>dummy modal</div>',
+          openedClass: 'foo'
+        });
+
+        expect(body).toHaveClass('foo');
+        expect(body).toHaveClass('bar');
+        expect(body).not.toHaveClass('modal-open');
+
+        close(modal1);
+
+        expect(body).toHaveClass('foo');
+        expect(body).toHaveClass('bar');
+        expect(body).not.toHaveClass('modal-open');
+
+        close(modal2);
+
+        expect(body).toHaveClass('foo');
+        expect(body).not.toHaveClass('bar');
+        expect(body).not.toHaveClass('modal-open');
+
+        close(modal3);
+
+        expect(body).not.toHaveClass('foo');
+        expect(body).not.toHaveClass('bar');
+        expect(body).not.toHaveClass('modal-open');
+      });
+    });
+  });
+
+  describe('multiple modals', function() {
+    it('it should allow opening of multiple modals', function() {
+      var modal1 = open({template: '<div>Modal1</div>'});
+      var modal2 = open({template: '<div>Modal2</div>'});
+      expect($document).toHaveModalsOpen(2);
+
+      dismiss(modal2);
+      expect($document).toHaveModalsOpen(1);
+      expect($document).toHaveModalOpenWithContent('Modal1', 'div');
+
+      dismiss(modal1);
+      expect($document).toHaveModalsOpen(0);
+    });
+
+    it('should not close any modals on ESC if the topmost one does not allow it', function() {
+      var modal1 = open({template: '<div>Modal1</div>'});
+      var modal2 = open({template: '<div>Modal2</div>', keyboard: false});
+
+      triggerKeyDown($document, 27);
+      $rootScope.$digest();
+
+      expect($document).toHaveModalsOpen(2);
+    });
+
+    it('should not close any modals on click if a topmost modal does not have backdrop', function() {
+      var modal1 = open({template: '<div>Modal1</div>'});
+      var modal2 = open({template: '<div>Modal2</div>', backdrop: false});
+
+      $document.find('body > div.modal-backdrop').click();
+      $rootScope.$digest();
+
+      expect($document).toHaveModalsOpen(2);
+    });
+
+    it('multiple modals should not interfere with default options', function() {
+      var modal1 = open({template: '<div>Modal1</div>', backdrop: false});
+      var modal2 = open({template: '<div>Modal2</div>'});
+      $rootScope.$digest();
+
+      expect($document).toHaveBackdrop();
+    });
+
+    it('should add "modal-open" class when a modal gets opened', function() {
+      var body = $document.find('body');
+      expect(body).not.toHaveClass('modal-open');
+
+      var modal1 = open({template: '<div>Content1</div>'});
+      expect(body).toHaveClass('modal-open');
+
+      var modal2 = open({template: '<div>Content1</div>'});
+      expect(body).toHaveClass('modal-open');
+
+      dismiss(modal1);
+      expect(body).toHaveClass('modal-open');
+
+      dismiss(modal2);
+      expect(body).not.toHaveClass('modal-open');
+    });
+
+    it('should return to the element which had focus before the dialog is invoked', function() {
+      var link = '<a href>Link</a>';
+      var element = angular.element(link);
+      angular.element(document.body).append(element);
+      element.focus();
+      expect(document.activeElement.tagName).toBe('A');
+
+      var modal1 = open({template: '<div>Modal1<button id="focus">inside modal1</button></div>'});
+      $animate.flush();
+      $rootScope.$digest();
+      document.getElementById('focus').focus();
+      expect(document.activeElement.tagName).toBe('BUTTON');
+      expect($document).toHaveModalsOpen(1);
+
+      var modal2 = open({template: '<div>Modal2</div>'});
+      $animate.flush();
+      $rootScope.$digest();
+      expect(document.activeElement.tagName).toBe('DIV');
+      expect($document).toHaveModalsOpen(2);
+
+      dismiss(modal2);
+      expect(document.activeElement.tagName).toBe('BUTTON');
+      expect($document).toHaveModalsOpen(1);
+
+      dismiss(modal1);
+      expect(document.activeElement.tagName).toBe('A');
+      expect($document).toHaveModalsOpen(0);
+
+      element.remove();
+    });
+
+    it('should open modals and resolve the opened promises in order', function() {
+      // Opens a modal for each element in array order.
+      // Order is an array of non-repeating integers from 0..length-1 representing when to resolve that modal's promise.
+      // For example [1,2,0] would resolve the 3rd modal's promise first and the 2nd modal's promise last.
+      // Tests that the modals are added to $uibModalStack and that each resolves its "opened" promise sequentially.
+      // If an element is {reject:n} then n is still the order, but the corresponding promise is rejected.
+      // A rejection earlier in the open sequence should not affect modals opened later.
+      function test(order) {
+        var ds = []; // {index, deferred, reject}
+        var expected = ''; // 0..length-1
+        var actual = '';
+        angular.forEach(order, function(x, i) {
+          var reject = x.reject !== undefined;
+          if (reject) {
+            x = x.reject;
+          } else {
+            expected += i;
+          }
+          ds[x] = {index:i, deferred:$q.defer(), reject:reject};
+
+          var scope = $rootScope.$new();
+          scope.index = i;
+          open({
+            template: '<div>' + i + '</div>',
+            scope: scope,
+            resolve: {
+              x: function() { return ds[x].deferred.promise; }
+            }
+          }).opened.then(function() {
+            expect($uibModalStack.getTop().value.modalScope.index).toEqual(i);
+            actual += i;
+          });
+        });
+
+        angular.forEach(ds, function(d, i) {
+          if (d.reject) {
+            d.deferred.reject('rejected:' + d.index );
+          } else {
+            d.deferred.resolve('resolved:' + d.index );
+          }
+          $rootScope.$digest();
+        });
+
+        expect(actual).toEqual(expected);
+        expect($uibModal.getPromiseChain()).toEqual(null);
+      }
+
+      // Calls emit n! times on arrays of length n containing all non-repeating permutations of the integers 0..n-1.
+      function permute(n, emit) {
+        if (n < 1 || typeof emit !== 'function') {
+          return;
+        }
+        var a = [];
+        function _permute(depth) {
+          index: for (var i = 0; i < n; i++) {
+            for (var j = 0; j < depth; j++) {
+              if (a[j] === i) {
+                continue index; // already used
+              }
+            }
+
+            a[depth] = i;
+            if (depth + 1 === n) {
+              emit(angular.copy(a));
+            } else {
+              _permute(depth + 1);
+            }
+          }
+        }
+        _permute(0);
+      }
+
+      permute(2, function(a) { test(a); });
+      permute(2, function(a) { test(a.map(function(x, i) { return {reject:x}; })); });
+      permute(2, function(a) { test(a.map(function(x, i) { return i === 0 ? {reject:x} : x; })); });
+      permute(3, function(a) { test(a); });
+      permute(3, function(a) { test(a.map(function(x, i) { return {reject:x}; })); });
+      permute(3, function(a) { test(a.map(function(x, i) { return i === 0 ? {reject:x} : x; })); });
+      permute(3, function(a) { test(a.map(function(x, i) { return i === 1 ? {reject:x} : x; })); });
+    });
+
+    it('should have top class only on top window', function () {
+      var modal1 = open({template: '<div>Content1</div>', windowClass: 'modal1', windowTopClass: 'modal-top'});
+      expect($document.find('div.modal1')).toHaveClass('modal-top');
+      expect($document).toHaveModalsOpen(1);
+
+      var modal2 = open({template: '<div>Content1</div>', windowClass: 'modal2', windowTopClass: 'modal-top'});
+      expect($document.find('div.modal1')).not.toHaveClass('modal-top');
+      expect($document.find('div.modal2')).toHaveClass('modal-top');
+      expect($document).toHaveModalsOpen(2);
+
+      var modal3 = open({template: '<div>Content1</div>', windowClass: 'modal3', windowTopClass: 'modal-top'});
+      expect($document.find('div.modal1')).not.toHaveClass('modal-top');
+      expect($document.find('div.modal2')).not.toHaveClass('modal-top');
+      expect($document.find('div.modal3')).toHaveClass('modal-top');
+      expect($document).toHaveModalsOpen(3);
+
+      dismiss(modal2);
+      expect($document.find('div.modal1')).not.toHaveClass('modal-top');
+      expect($document.find('div.modal3')).toHaveClass('modal-top');
+      expect($document).toHaveModalsOpen(2);
+
+      close(modal3);
+      expect($document.find('div.modal1')).toHaveClass('modal-top');
+      expect($document).toHaveModalsOpen(1);
+    });
+  });
+
+  describe('modal.closing event', function() {
+    it('should close the modal contingent on the modal.closing event and return whether the modal closed', function() {
+      var preventDefault;
+      var modal;
+      var template = '<div>content</div>';
+
+      function TestCtrl($scope) {
+        $scope.$on('modal.closing', function(event, resultOrReason, closing) {
+          if (preventDefault) {
+            event.preventDefault();
+          }
+        });
+      }
+
+      modal = open({template: template, controller: TestCtrl});
+
+      preventDefault = true;
+      expect(close(modal, 'result', true)).toBeFalsy();
+      expect($document).toHaveModalsOpen(1);
+
+      preventDefault = false;
+      expect(close(modal, 'result')).toBeTruthy();
+      expect($document).toHaveModalsOpen(0);
+
+      modal = open({template: template, controller: TestCtrl});
+
+      preventDefault = true;
+      expect(dismiss(modal, 'result', true)).toBeFalsy();
+      expect($document).toHaveModalsOpen(1);
+
+      preventDefault = false;
+      expect(dismiss(modal, 'result')).toBeTruthy();
+      expect($document).toHaveModalsOpen(0);
+    });
+
+    it('should trigger modal.closing and pass result/reason and closing parameters to the event', function() {
+      var called;
+
+      called = false;
+      close(open({
+        template: '<div>content</div>',
+        controller: function($scope) {
+          $scope.$on('modal.closing', function(event, resultOrReason, closing) {
+            called = true;
+            expect(resultOrReason).toBe('result');
+            expect(closing).toBeTruthy();
+          });
+        }
+      }), 'result');
+      expect(called).toBeTruthy();
+
+      called = false;
+      dismiss(open({
+        template: '<div>content</div>',
+        controller: function($scope) {
+          $scope.$on('modal.closing', function(event, resultOrReason, closing) {
+            called = true;
+            expect(resultOrReason).toBe('reason');
+            expect(closing).toBeFalsy();
+          });
+        }
+      }), 'reason');
+      expect(called).toBeTruthy();
+    });
+  });
+});
+
+/* deprecation tests below */
+
+describe('$modal deprecation', function() {
+  beforeEach(module('ngAnimateMock'));
+  beforeEach(module('ui.bootstrap.modal'));
+  beforeEach(module('template/modal/backdrop.html'));
+  beforeEach(module('template/modal/window.html'));
+
+  it('should suppress warning', function() {
+    module(function($provide) {
+      $provide.value('$modalSuppressWarning', true);
+    });
+
+    inject(function($modal, $timeout, $log, $rootScope) {
+      spyOn($log, 'warn');
+
+      $modal.open({template: '<div>Foo</div>', controller: function($modalInstance) {}});
+      $rootScope.$digest();
+      $timeout.flush(0);
+      expect($log.warn.calls.count()).toBe(0);
+    });
+  });
+
+  it('should give warning by default', inject(function($log) {
+    spyOn($log, 'warn');
+
+    inject(function($compile, $templateCache, $rootScope, $modal, $timeout) {
+      var backdropTemplate =
+        '<div class="modal-backdrop"' +
+        'modal-animation-class="fade"' +
+        'modal-in-class="in"' +
+        'ng-style="{\'z-index\': 1040 + (index && 1 || 0) + index*10}"' +
+        '></div>';
+      $templateCache.put('template/modal/backdrop.html', backdropTemplate);
+
+      var windowTemplate =
+        '<div modal-render="{{$isRendered}}" tabindex="-1" role="dialog" class="modal"' +
+        'modal-animation-class="fade"' +
+        'modal-in-class="in"' +
+        'ng-style="{\'z-index\': 1050 + index*10, display: \'block\'}">' +
+        '<div class="modal-dialog" ng-class="size ? \'modal-\' + size : \'\'"><div class="modal-content" modal-transclude></div></div>' +
+        '</div>';
+      $templateCache.put('template/modal/window.html', windowTemplate);
+
+      $modal.open({template: '<div>Foo</div>', controller: function($modalInstance) {}});
+      $rootScope.$digest();
+      $timeout.flush(0);
+
+      expect($log.warn.calls.count()).toBe(6);
+      expect($log.warn.calls.argsFor(0)).toEqual(['$modal is now deprecated. Use $uibModal instead.']);
+      expect($log.warn.calls.argsFor(1)).toEqual(['$modalInstance is now deprecated. Use $uibModalInstance instead.']);
+      expect($log.warn.calls.argsFor(2)).toEqual(['$modalStack is now deprecated. Use $uibModalStack instead.']);
+      expect($log.warn.calls.argsFor(3)).toEqual(['modal-animation-class is now deprecated. Use uib-modal-animation-class instead.']);
+      expect($log.warn.calls.argsFor(4)).toEqual(['modal-animation-class is now deprecated. Use uib-modal-animation-class instead.']);
+      expect($log.warn.calls.argsFor(5)).toEqual(['modal-transclude is now deprecated. Use uib-modal-transclude instead.']);
+
+      $log.warn.calls.reset();
+      $compile('<div modal-backdrop></div>')($rootScope);
+      $rootScope.$digest();
+      expect($log.warn.calls.argsFor(1)).toEqual(['modal-backdrop is now deprecated. Use uib-modal-backdrop instead.']);
+
+      $log.warn.calls.reset();
+      $compile('<div modal-window></div>')($rootScope);
+      $rootScope.$digest();
+      expect($log.warn.calls.argsFor(2)).toEqual(['modal-window is now deprecated. Use uib-modal-window instead.']);
+    });
+  }));
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/modal/test/modalWindow.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/modal/test/modalWindow.spec.js
new file mode 100644
index 0000000..60c00ae
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/modal/test/modalWindow.spec.js
@@ -0,0 +1,46 @@
+describe('modal window', function() {
+  var $rootScope, $compile;
+
+  beforeEach(module('ui.bootstrap.modal'));
+  beforeEach(module('template/modal/window.html'));
+  beforeEach(inject(function (_$rootScope_, _$compile_) {
+    $rootScope = _$rootScope_;
+    $compile = _$compile_;
+  }));
+
+  it('should not use transclusion scope for modals content - issue 2110', function() {
+    $rootScope.animate = false;
+    $compile('<div uib-modal-window animate="animate"><span ng-init="foo=true"></span></div>')($rootScope);
+    $rootScope.$digest();
+
+    expect($rootScope.foo).toBeTruthy();
+  });
+
+  it('should support custom CSS classes as string', function() {
+    $rootScope.animate = false;
+    var windowEl = $compile('<div uib-modal-window animate="animate" window-class="test foo">content</div>')($rootScope);
+    $rootScope.$digest();
+
+    expect(windowEl).toHaveClass('test');
+    expect(windowEl).toHaveClass('foo');
+  });
+
+  it('should support window top class', function () {
+    $rootScope.animate = false;
+    var windowEl = $compile('<div uib-modal-window animate="animate" window-top-class="test foo">content</div>')($rootScope);
+    $rootScope.$digest();
+
+    expect(windowEl).toHaveClass('test');
+    expect(windowEl).toHaveClass('foo');
+  });
+
+  it('should support custom template url', inject(function($templateCache) {
+    $templateCache.put('window.html', '<div class="mywindow" ng-transclude></div>');
+
+    var windowEl = $compile('<div uib-modal-window template-url="window.html" window-class="test">content</div>')($rootScope);
+    $rootScope.$digest();
+
+    expect(windowEl).toHaveClass('mywindow');
+    expect(windowEl).toHaveClass('test');
+  }));
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/modal/test/multiMap.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/modal/test/multiMap.spec.js
new file mode 100644
index 0000000..c89624e
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/modal/test/multiMap.spec.js
@@ -0,0 +1,58 @@
+describe('multi map', function() {
+  var multiMap;
+
+  beforeEach(module('ui.bootstrap.modal'));
+  beforeEach(inject(function($$multiMap) {
+    multiMap = $$multiMap.createNew();
+  }));
+
+  it('should add and remove objects by key', function() {
+    multiMap.put('foo', 'bar');
+
+    expect(multiMap.get('foo')).toEqual(['bar']);
+
+    multiMap.put('foo', 'baz');
+
+    expect(multiMap.get('foo')).toEqual(['bar', 'baz']);
+
+    multiMap.remove('foo', 'bar');
+
+    expect(multiMap.get('foo')).toEqual(['baz']);
+
+    multiMap.remove('foo', 'baz');
+
+    expect(multiMap.hasKey('foo')).toBe(false);
+  });
+
+  it('should support getting the keys', function() {
+    multiMap.put('foo', 'bar');
+    multiMap.put('baz', 'boo');
+
+    expect(multiMap.keys()).toEqual(['foo', 'baz']);
+  });
+
+  it('should return all entries', function() {
+    multiMap.put('foo', 'bar');
+    multiMap.put('foo', 'bar2');
+    multiMap.put('baz', 'boo');
+
+    expect(multiMap.entries()).toEqual([
+      {
+        key: 'foo',
+        value: ['bar', 'bar2']
+      },
+      {
+        key: 'baz',
+        value: ['boo']
+      }
+    ]);
+  });
+
+  it('should preserve semantic of an empty key', function() {
+    expect(multiMap.get('key')).toBeUndefined();
+  });
+
+  it('should respect removal of non-existing elements', function() {
+    expect(multiMap.remove('foo', 'bar')).toBeUndefined();
+  });
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/pagination/docs/demo.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/pagination/docs/demo.html
new file mode 100644
index 0000000..16ec3ac
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/pagination/docs/demo.html
@@ -0,0 +1,19 @@
+<div ng-controller="PaginationDemoCtrl">
+    <h4>Default</h4>
+    <uib-pagination total-items="totalItems" ng-model="currentPage" ng-change="pageChanged()"></uib-pagination>
+    <uib-pagination boundary-links="true" total-items="totalItems" ng-model="currentPage" class="pagination-sm" previous-text="&lsaquo;" next-text="&rsaquo;" first-text="&laquo;" last-text="&raquo;"></uib-pagination>
+    <uib-pagination direction-links="false" boundary-links="true" total-items="totalItems" ng-model="currentPage"></uib-pagination>
+    <uib-pagination direction-links="false" total-items="totalItems" ng-model="currentPage" num-pages="smallnumPages"></uib-pagination>
+    <pre>The selected page no: {{currentPage}}</pre>
+    <button type="button" class="btn btn-info" ng-click="setPage(3)">Set current page to: 3</button>
+
+    <hr />
+    <h4>Pager</h4>
+    <uib-pager total-items="totalItems" ng-model="currentPage"></uib-pager>
+
+    <hr />
+    <h4>Limit the maximum visible buttons</h4>
+    <uib-pagination total-items="bigTotalItems" ng-model="bigCurrentPage" max-size="maxSize" class="pagination-sm" boundary-links="true"></uib-pagination>
+    <uib-pagination total-items="bigTotalItems" ng-model="bigCurrentPage" max-size="maxSize" class="pagination-sm" boundary-links="true" rotate="false" num-pages="numPages"></uib-pagination>
+    <pre>Page: {{bigCurrentPage}} / {{numPages}}</pre>
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/pagination/docs/demo.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/pagination/docs/demo.js
new file mode 100644
index 0000000..9791aab
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/pagination/docs/demo.js
@@ -0,0 +1,16 @@
+angular.module('ui.bootstrap.demo').controller('PaginationDemoCtrl', function ($scope, $log) {
+  $scope.totalItems = 64;
+  $scope.currentPage = 4;
+
+  $scope.setPage = function (pageNo) {
+    $scope.currentPage = pageNo;
+  };
+
+  $scope.pageChanged = function() {
+    $log.log('Page changed to: ' + $scope.currentPage);
+  };
+
+  $scope.maxSize = 5;
+  $scope.bigTotalItems = 175;
+  $scope.bigCurrentPage = 1;
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/pagination/docs/readme.md b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/pagination/docs/readme.md
new file mode 100644
index 0000000..07c89d5
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/pagination/docs/readme.md
@@ -0,0 +1,91 @@
+
+A lightweight pagination directive that is focused on ... providing pagination & will take care of visualising a pagination bar and enable / disable buttons correctly!
+
+### Pagination Settings ###
+
+Settings can be provided as attributes in the `<uib-pagination>` or globally configured through the `uibPaginationConfig`.
+
+ * `ng-change`
+ 	:
+ 	`ng-change` can be used together with `ng-model` to call a function whenever the page changes.
+
+ * `ng-model` <i class="glyphicon glyphicon-eye-open"></i>
+ 	:
+ 	Current page number. First page is 1.
+
+ * `ng-disabled` <i class="glyphicon glyphicon-eye-open"></i>
+  :
+  Used to disable the pagination component
+
+ * `total-items` <i class="glyphicon glyphicon-eye-open"></i>
+ 	:
+ 	Total number of items in all pages.
+
+ * `items-per-page` <i class="glyphicon glyphicon-eye-open"></i>
+ 	_(Defaults: 10)_ :
+ 	Maximum number of items per page. A value less than one indicates all items on one page.
+
+ * `max-size` <i class="glyphicon glyphicon-eye-open"></i>
+ 	_(Defaults: null)_ :
+ 	Limit number for pagination size.
+
+ * `num-pages` <small class="badge">readonly</small>
+ 	_(Defaults: angular.noop)_ :
+ 	An optional expression assigned the total number of pages to display.
+
+ * `rotate`
+ 	_(Defaults: true)_ :
+ 	Whether to keep current page in the middle of the visible ones.
+
+ * `direction-links`
+ 	_(Default: true)_ :
+ 	Whether to display Previous / Next buttons.
+
+ * `previous-text`
+ 	_(Default: 'Previous')_ :
+ 	Text for Previous button.
+
+ * `next-text`
+ 	_(Default: 'Next')_ :
+ 	Text for Next button.
+
+ * `boundary-links`
+ 	_(Default: false)_ :
+ 	Whether to display First / Last buttons.
+
+ * `first-text`
+ 	_(Default: 'First')_ :
+ 	Text for First button.
+
+ * `last-text`
+ 	_(Default: 'Last')_ :
+ 	Text for Last button.
+
+ * `template-url`
+  _(Default: 'template/pagination/pagination.html')_ :
+  Override the template for the component with a custom provided template
+
+### Pager Settings ###
+
+Settings can be provided as attributes in the `<uib-pager>` or globally configured through the `uibPagerConfig`.  
+For `ng-model`, `total-items`, `items-per-page` and `num-pages` see pagination settings. Other settings are:
+
+ * `align`
+ 	_(Default: true)_ :
+ 	Whether to align each link to the sides.
+
+ * `previous-text`
+ 	_(Default: '« Previous')_ :
+ 	Text for Previous button.
+
+ * `next-text`
+ 	_(Default: 'Next »')_ :
+ 	Text for Next button.
+
+ * `template-url`
+  _(Default: 'template/pagination/pager.html') :
+  Override the template for the component with a custom provided template
+
+ * `ng-disabled` <i class="glyphicon glyphicon-eye-open"></i>
+  :
+  Used to disable the pager component
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/pagination/pagination.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/pagination/pagination.js
new file mode 100644
index 0000000..1e75dcf
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/pagination/pagination.js
@@ -0,0 +1,457 @@
+angular.module('ui.bootstrap.pagination', [])
+.controller('UibPaginationController', ['$scope', '$attrs', '$parse', function($scope, $attrs, $parse) {
+  var self = this,
+      ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
+      setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
+
+  this.init = function(ngModelCtrl_, config) {
+    ngModelCtrl = ngModelCtrl_;
+    this.config = config;
+
+    ngModelCtrl.$render = function() {
+      self.render();
+    };
+
+    if ($attrs.itemsPerPage) {
+      $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) {
+        self.itemsPerPage = parseInt(value, 10);
+        $scope.totalPages = self.calculateTotalPages();
+      });
+    } else {
+      this.itemsPerPage = config.itemsPerPage;
+    }
+
+    $scope.$watch('totalItems', function() {
+      $scope.totalPages = self.calculateTotalPages();
+    });
+
+    $scope.$watch('totalPages', function(value) {
+      setNumPages($scope.$parent, value); // Readonly variable
+
+      if ( $scope.page > value ) {
+        $scope.selectPage(value);
+      } else {
+        ngModelCtrl.$render();
+      }
+    });
+  };
+
+  this.calculateTotalPages = function() {
+    var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage);
+    return Math.max(totalPages || 0, 1);
+  };
+
+  this.render = function() {
+    $scope.page = parseInt(ngModelCtrl.$viewValue, 10) || 1;
+  };
+
+  $scope.selectPage = function(page, evt) {
+    if (evt) {
+      evt.preventDefault();
+    }
+
+    var clickAllowed = !$scope.ngDisabled || !evt;
+    if (clickAllowed && $scope.page !== page && page > 0 && page <= $scope.totalPages) {
+      if (evt && evt.target) {
+        evt.target.blur();
+      }
+      ngModelCtrl.$setViewValue(page);
+      ngModelCtrl.$render();
+    }
+  };
+
+  $scope.getText = function(key) {
+    return $scope[key + 'Text'] || self.config[key + 'Text'];
+  };
+
+  $scope.noPrevious = function() {
+    return $scope.page === 1;
+  };
+
+  $scope.noNext = function() {
+    return $scope.page === $scope.totalPages;
+  };
+}])
+
+.constant('uibPaginationConfig', {
+  itemsPerPage: 10,
+  boundaryLinks: false,
+  directionLinks: true,
+  firstText: 'First',
+  previousText: 'Previous',
+  nextText: 'Next',
+  lastText: 'Last',
+  rotate: true
+})
+
+.directive('uibPagination', ['$parse', 'uibPaginationConfig', function($parse, paginationConfig) {
+  return {
+    restrict: 'EA',
+    scope: {
+      totalItems: '=',
+      firstText: '@',
+      previousText: '@',
+      nextText: '@',
+      lastText: '@',
+      ngDisabled:'='
+    },
+    require: ['uibPagination', '?ngModel'],
+    controller: 'UibPaginationController',
+    controllerAs: 'pagination',
+    templateUrl: function(element, attrs) {
+      return attrs.templateUrl || 'template/pagination/pagination.html';
+    },
+    replace: true,
+    link: function(scope, element, attrs, ctrls) {
+      var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
+
+      if (!ngModelCtrl) {
+         return; // do nothing if no ng-model
+      }
+
+      // Setup configuration parameters
+      var maxSize = angular.isDefined(attrs.maxSize) ? scope.$parent.$eval(attrs.maxSize) : paginationConfig.maxSize,
+          rotate = angular.isDefined(attrs.rotate) ? scope.$parent.$eval(attrs.rotate) : paginationConfig.rotate;
+      scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : paginationConfig.boundaryLinks;
+      scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : paginationConfig.directionLinks;
+
+      paginationCtrl.init(ngModelCtrl, paginationConfig);
+
+      if (attrs.maxSize) {
+        scope.$parent.$watch($parse(attrs.maxSize), function(value) {
+          maxSize = parseInt(value, 10);
+          paginationCtrl.render();
+        });
+      }
+
+      // Create page object used in template
+      function makePage(number, text, isActive) {
+        return {
+          number: number,
+          text: text,
+          active: isActive
+        };
+      }
+
+      function getPages(currentPage, totalPages) {
+        var pages = [];
+
+        // Default page limits
+        var startPage = 1, endPage = totalPages;
+        var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages;
+
+        // recompute if maxSize
+        if (isMaxSized) {
+          if (rotate) {
+            // Current page is displayed in the middle of the visible ones
+            startPage = Math.max(currentPage - Math.floor(maxSize/2), 1);
+            endPage   = startPage + maxSize - 1;
+
+            // Adjust if limit is exceeded
+            if (endPage > totalPages) {
+              endPage   = totalPages;
+              startPage = endPage - maxSize + 1;
+            }
+          } else {
+            // Visible pages are paginated with maxSize
+            startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1;
+
+            // Adjust last page if limit is exceeded
+            endPage = Math.min(startPage + maxSize - 1, totalPages);
+          }
+        }
+
+        // Add page number links
+        for (var number = startPage; number <= endPage; number++) {
+          var page = makePage(number, number, number === currentPage);
+          pages.push(page);
+        }
+
+        // Add links to move between page sets
+        if (isMaxSized && ! rotate) {
+          if (startPage > 1) {
+            var previousPageSet = makePage(startPage - 1, '...', false);
+            pages.unshift(previousPageSet);
+          }
+
+          if (endPage < totalPages) {
+            var nextPageSet = makePage(endPage + 1, '...', false);
+            pages.push(nextPageSet);
+          }
+        }
+
+        return pages;
+      }
+
+      var originalRender = paginationCtrl.render;
+      paginationCtrl.render = function() {
+        originalRender();
+        if (scope.page > 0 && scope.page <= scope.totalPages) {
+          scope.pages = getPages(scope.page, scope.totalPages);
+        }
+      };
+    }
+  };
+}])
+
+.constant('uibPagerConfig', {
+  itemsPerPage: 10,
+  previousText: '« Previous',
+  nextText: 'Next »',
+  align: true
+})
+
+.directive('uibPager', ['uibPagerConfig', function(pagerConfig) {
+  return {
+    restrict: 'EA',
+    scope: {
+      totalItems: '=',
+      previousText: '@',
+      nextText: '@',
+      ngDisabled: '='
+    },
+    require: ['uibPager', '?ngModel'],
+    controller: 'UibPaginationController',
+    controllerAs: 'pagination',
+    templateUrl: function(element, attrs) {
+      return attrs.templateUrl || 'template/pagination/pager.html';
+    },
+    replace: true,
+    link: function(scope, element, attrs, ctrls) {
+      var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
+
+      if (!ngModelCtrl) {
+         return; // do nothing if no ng-model
+      }
+
+      scope.align = angular.isDefined(attrs.align) ? scope.$parent.$eval(attrs.align) : pagerConfig.align;
+      paginationCtrl.init(ngModelCtrl, pagerConfig);
+    }
+  };
+}]);
+
+/* Deprecated Pagination Below */
+
+angular.module('ui.bootstrap.pagination')
+.value('$paginationSuppressWarning', false)
+.controller('PaginationController', ['$scope', '$attrs', '$parse', '$log', '$paginationSuppressWarning', function($scope, $attrs, $parse, $log, $paginationSuppressWarning) {
+  if (!$paginationSuppressWarning) {
+    $log.warn('PaginationController is now deprecated. Use UibPaginationController instead.');
+  }
+
+  var self = this,
+    ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
+    setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
+
+  this.init = function(ngModelCtrl_, config) {
+    ngModelCtrl = ngModelCtrl_;
+    this.config = config;
+
+    ngModelCtrl.$render = function() {
+      self.render();
+    };
+
+    if ($attrs.itemsPerPage) {
+      $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) {
+        self.itemsPerPage = parseInt(value, 10);
+        $scope.totalPages = self.calculateTotalPages();
+      });
+    } else {
+      this.itemsPerPage = config.itemsPerPage;
+    }
+
+    $scope.$watch('totalItems', function() {
+      $scope.totalPages = self.calculateTotalPages();
+    });
+
+    $scope.$watch('totalPages', function(value) {
+      setNumPages($scope.$parent, value); // Readonly variable
+
+      if ( $scope.page > value ) {
+        $scope.selectPage(value);
+      } else {
+        ngModelCtrl.$render();
+      }
+    });
+  };
+
+  this.calculateTotalPages = function() {
+    var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage);
+    return Math.max(totalPages || 0, 1);
+  };
+
+  this.render = function() {
+    $scope.page = parseInt(ngModelCtrl.$viewValue, 10) || 1;
+  };
+
+  $scope.selectPage = function(page, evt) {
+    if (evt) {
+      evt.preventDefault();
+    }
+
+    var clickAllowed = !$scope.ngDisabled || !evt;
+    if (clickAllowed && $scope.page !== page && page > 0 && page <= $scope.totalPages) {
+      if (evt && evt.target) {
+        evt.target.blur();
+      }
+      ngModelCtrl.$setViewValue(page);
+      ngModelCtrl.$render();
+    }
+  };
+
+  $scope.getText = function(key) {
+    return $scope[key + 'Text'] || self.config[key + 'Text'];
+  };
+
+  $scope.noPrevious = function() {
+    return $scope.page === 1;
+  };
+
+  $scope.noNext = function() {
+    return $scope.page === $scope.totalPages;
+  };
+}])
+.directive('pagination', ['$parse', 'uibPaginationConfig', '$log', '$paginationSuppressWarning', function($parse, paginationConfig, $log, $paginationSuppressWarning) {
+  return {
+    restrict: 'EA',
+    scope: {
+      totalItems: '=',
+      firstText: '@',
+      previousText: '@',
+      nextText: '@',
+      lastText: '@',
+      ngDisabled:'='
+    },
+    require: ['pagination', '?ngModel'],
+    controller: 'PaginationController',
+    controllerAs: 'pagination',
+    templateUrl: function(element, attrs) {
+      return attrs.templateUrl || 'template/pagination/pagination.html';
+    },
+    replace: true,
+    link: function(scope, element, attrs, ctrls) {
+      if (!$paginationSuppressWarning) {
+        $log.warn('pagination is now deprecated. Use uib-pagination instead.');
+      }
+      var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
+
+      if (!ngModelCtrl) {
+         return; // do nothing if no ng-model
+      }
+
+      // Setup configuration parameters
+      var maxSize = angular.isDefined(attrs.maxSize) ? scope.$parent.$eval(attrs.maxSize) : paginationConfig.maxSize,
+          rotate = angular.isDefined(attrs.rotate) ? scope.$parent.$eval(attrs.rotate) : paginationConfig.rotate;
+      scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : paginationConfig.boundaryLinks;
+      scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : paginationConfig.directionLinks;
+
+      paginationCtrl.init(ngModelCtrl, paginationConfig);
+
+      if (attrs.maxSize) {
+        scope.$parent.$watch($parse(attrs.maxSize), function(value) {
+          maxSize = parseInt(value, 10);
+          paginationCtrl.render();
+        });
+      }
+
+      // Create page object used in template
+      function makePage(number, text, isActive) {
+        return {
+          number: number,
+          text: text,
+          active: isActive
+        };
+      }
+
+      function getPages(currentPage, totalPages) {
+        var pages = [];
+
+        // Default page limits
+        var startPage = 1, endPage = totalPages;
+        var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages;
+
+        // recompute if maxSize
+        if (isMaxSized) {
+          if (rotate) {
+            // Current page is displayed in the middle of the visible ones
+            startPage = Math.max(currentPage - Math.floor(maxSize/2), 1);
+            endPage   = startPage + maxSize - 1;
+
+            // Adjust if limit is exceeded
+            if (endPage > totalPages) {
+              endPage   = totalPages;
+              startPage = endPage - maxSize + 1;
+            }
+          } else {
+            // Visible pages are paginated with maxSize
+            startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1;
+
+            // Adjust last page if limit is exceeded
+            endPage = Math.min(startPage + maxSize - 1, totalPages);
+          }
+        }
+
+        // Add page number links
+        for (var number = startPage; number <= endPage; number++) {
+          var page = makePage(number, number, number === currentPage);
+          pages.push(page);
+        }
+
+        // Add links to move between page sets
+        if (isMaxSized && ! rotate) {
+          if (startPage > 1) {
+            var previousPageSet = makePage(startPage - 1, '...', false);
+            pages.unshift(previousPageSet);
+          }
+
+          if (endPage < totalPages) {
+            var nextPageSet = makePage(endPage + 1, '...', false);
+            pages.push(nextPageSet);
+          }
+        }
+
+        return pages;
+      }
+
+      var originalRender = paginationCtrl.render;
+      paginationCtrl.render = function() {
+        originalRender();
+        if (scope.page > 0 && scope.page <= scope.totalPages) {
+          scope.pages = getPages(scope.page, scope.totalPages);
+        }
+      };
+    }
+  };
+}])
+
+.directive('pager', ['uibPagerConfig', '$log', '$paginationSuppressWarning', function(pagerConfig, $log, $paginationSuppressWarning) {
+  return {
+    restrict: 'EA',
+    scope: {
+      totalItems: '=',
+      previousText: '@',
+      nextText: '@',
+      ngDisabled: '='
+    },
+    require: ['pager', '?ngModel'],
+    controller: 'PaginationController',
+    controllerAs: 'pagination',
+    templateUrl: function(element, attrs) {
+      return attrs.templateUrl || 'template/pagination/pager.html';
+    },
+    replace: true,
+    link: function(scope, element, attrs, ctrls) {
+      if (!$paginationSuppressWarning) {
+        $log.warn('pager is now deprecated. Use uib-pager instead.');
+      }
+      var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
+
+      if (!ngModelCtrl) {
+         return; // do nothing if no ng-model
+      }
+
+      scope.align = angular.isDefined(attrs.align) ? scope.$parent.$eval(attrs.align) : pagerConfig.align;
+      paginationCtrl.init(ngModelCtrl, pagerConfig);
+    }
+  };
+}]);
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/pagination/test/pager.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/pagination/test/pager.spec.js
new file mode 100644
index 0000000..3756d70
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/pagination/test/pager.spec.js
@@ -0,0 +1,311 @@
+describe('pager directive', function() {
+  var $compile, $rootScope, $document, $templateCache, body, element;
+  beforeEach(module('ui.bootstrap.pagination'));
+  beforeEach(module('template/pagination/pager.html'));
+  beforeEach(inject(function(_$compile_, _$rootScope_, _$document_, _$templateCache_) {
+    $compile = _$compile_;
+    $rootScope = _$rootScope_;
+    $rootScope.total = 47; // 5 pages
+    $rootScope.currentPage = 3;
+    $document = _$document_;
+    $templateCache = _$templateCache_;
+    body = $document.find('body');
+    element = $compile('<uib-pager total-items="total" ng-model="currentPage"></uib-pager>')($rootScope);
+    $rootScope.$digest();
+  }));
+
+  function getPaginationBarSize() {
+    return element.find('li').length;
+  }
+
+  function getPaginationEl(index) {
+    return element.find('li').eq(index);
+  }
+
+  function clickPaginationEl(index) {
+    getPaginationEl(index).find('a').click();
+  }
+
+  function getPaginationLinkEl(elem, index) {
+    return elem.find('li').eq(index).find('a');
+  }
+
+  function updateCurrentPage(value) {
+    $rootScope.currentPage = value;
+    $rootScope.$digest();
+  }
+
+  it('has a "pager" css class', function() {
+    expect(element.hasClass('pager')).toBe(true);
+  });
+
+  it('contains 2 li elements', function() {
+    expect(getPaginationBarSize()).toBe(2);
+    expect(getPaginationEl(0).text()).toBe('« Previous');
+    expect(getPaginationEl(-1).text()).toBe('Next »');
+  });
+
+  it('aligns previous & next page', function() {
+    expect(getPaginationEl(0)).toHaveClass('previous');
+    expect(getPaginationEl(0)).not.toHaveClass('next');
+
+    expect(getPaginationEl(-1)).not.toHaveClass('previous');
+    expect(getPaginationEl(-1)).toHaveClass('next');
+  });
+
+  it('exposes the controller on the template', function() {
+    $templateCache.put('template/pagination/pager.html', '<div>{{pagination.text}}</div>');
+
+    element = $compile('<uib-pager></uib-pager>')($rootScope);
+    $rootScope.$digest();
+
+    var ctrl = element.controller('uibPager');
+    expect(ctrl).toBeDefined();
+
+    ctrl.text = 'foo';
+    $rootScope.$digest();
+
+    expect(element.html()).toBe('foo');
+  });
+
+  it('disables the "previous" link if current page is 1', function() {
+    updateCurrentPage(1);
+    expect(getPaginationEl(0)).toHaveClass('disabled');
+  });
+
+  it('disables the "next" link if current page is num-pages', function() {
+    updateCurrentPage(5);
+    expect(getPaginationEl(-1)).toHaveClass('disabled');
+  });
+
+  it('changes currentPage if the "previous" link is clicked', function() {
+    clickPaginationEl(0);
+    expect($rootScope.currentPage).toBe(2);
+  });
+
+  it('changes currentPage if the "next" link is clicked', function() {
+    clickPaginationEl(-1);
+    expect($rootScope.currentPage).toBe(4);
+  });
+
+  it('does not change the current page on "previous" click if already at first page', function() {
+    updateCurrentPage(1);
+    clickPaginationEl(0);
+    expect($rootScope.currentPage).toBe(1);
+  });
+
+  it('does not change the current page on "next" click if already at last page', function() {
+    updateCurrentPage(5);
+    clickPaginationEl(-1);
+    expect($rootScope.currentPage).toBe(5);
+  });
+
+  it('executes the `ng-change` expression when an element is clicked', function() {
+    $rootScope.selectPageHandler = jasmine.createSpy('selectPageHandler');
+    element = $compile('<uib-pager total-items="total" ng-model="currentPage" ng-change="selectPageHandler()"></uib-pager>')($rootScope);
+    $rootScope.$digest();
+
+    clickPaginationEl(-1);
+    expect($rootScope.selectPageHandler).toHaveBeenCalled();
+  });
+
+  it('does not changes the number of pages when `total-items` changes', function() {
+    $rootScope.total = 73; // 8 pages
+    $rootScope.$digest();
+
+    expect(getPaginationBarSize()).toBe(2);
+    expect(getPaginationEl(0).text()).toBe('« Previous');
+    expect(getPaginationEl(-1).text()).toBe('Next »');
+  });
+
+  it('should blur the "next" link after it has been clicked', function() {
+    body.append(element);
+    var linkEl = getPaginationLinkEl(element, -1);
+
+    linkEl.focus();
+    expect(linkEl).toHaveFocus();
+
+    linkEl.click();
+    expect(linkEl).not.toHaveFocus();
+
+    element.remove();
+  });
+
+  it('should blur the "prev" link after it has been clicked', function() {
+    body.append(element);
+    var linkEl = getPaginationLinkEl(element, -1);
+
+    linkEl.focus();
+    expect(linkEl).toHaveFocus();
+
+    linkEl.click();
+    expect(linkEl).not.toHaveFocus();
+
+    element.remove();
+  });
+
+  it('allows custom templates', function() {
+    $templateCache.put('foo/bar.html', '<div>baz</div>');
+
+    element = $compile('<pager template-url="foo/bar.html"></pager>')($rootScope);
+    $rootScope.$digest();
+
+    expect(element.html()).toBe('baz');
+  });
+
+  describe('`items-per-page`', function() {
+    beforeEach(function() {
+      $rootScope.perpage = 5;
+      element = $compile('<uib-pager total-items="total" items-per-page="perpage" ng-model="currentPage"></uib-pager>')($rootScope);
+      $rootScope.$digest();
+    });
+
+    it('does not change the number of pages', function() {
+      expect(getPaginationBarSize()).toBe(2);
+      expect(getPaginationEl(0).text()).toBe('« Previous');
+      expect(getPaginationEl(-1).text()).toBe('Next »');
+    });
+
+    it('selects the last page when it is too big', function() {
+      $rootScope.perpage = 30;
+      $rootScope.$digest();
+
+      expect($rootScope.currentPage).toBe(2);
+      expect(getPaginationBarSize()).toBe(2);
+      expect(getPaginationEl(0)).not.toHaveClass('disabled');
+      expect(getPaginationEl(-1)).toHaveClass('disabled');
+    });
+  });
+
+  describe('when `page` is not a number', function() {
+    it('handles string', function() {
+      updateCurrentPage('1');
+      expect(getPaginationEl(0)).toHaveClass('disabled');
+
+      updateCurrentPage('05');
+      expect(getPaginationEl(-1)).toHaveClass('disabled');
+    });
+  });
+
+  describe('`num-pages`', function() {
+    beforeEach(function() {
+      $rootScope.numpg = null;
+      element = $compile('<uib-pager total-items="total" ng-model="currentPage" num-pages="numpg"></uib-pager>')($rootScope);
+      $rootScope.$digest();
+    });
+
+    it('equals to total number of pages', function() {
+      expect($rootScope.numpg).toBe(5);
+    });
+  });
+
+  describe('setting `pagerConfig`', function() {
+    var originalConfig = {};
+    beforeEach(inject(function(uibPagerConfig) {
+      angular.extend(originalConfig, uibPagerConfig);
+      uibPagerConfig.previousText = 'PR';
+      uibPagerConfig.nextText = 'NE';
+      uibPagerConfig.align = false;
+      element = $compile('<uib-pager total-items="total" ng-model="currentPage"></uib-pager>')($rootScope);
+      $rootScope.$digest();
+    }));
+    afterEach(inject(function(uibPagerConfig) {
+      // return it to the original state
+      angular.extend(uibPagerConfig, originalConfig);
+    }));
+
+    it('should change paging text', function() {
+      expect(getPaginationEl(0).text()).toBe('PR');
+      expect(getPaginationEl(-1).text()).toBe('NE');
+    });
+
+    it('should not align previous & next page link', function() {
+      expect(getPaginationEl(0)).not.toHaveClass('previous');
+      expect(getPaginationEl(-1)).not.toHaveClass('next');
+    });
+  });
+
+  describe('override configuration from attributes', function() {
+    beforeEach(function() {
+      element = $compile('<uib-pager align="false" previous-text="<" next-text=">" total-items="total" ng-model="currentPage"></uib-pager>')($rootScope);
+      $rootScope.$digest();
+    });
+
+    it('contains 2 li elements', function() {
+      expect(getPaginationBarSize()).toBe(2);
+    });
+
+    it('should change paging text from attributes', function() {
+      expect(getPaginationEl(0).text()).toBe('<');
+      expect(getPaginationEl(-1).text()).toBe('>');
+    });
+
+    it('should not align previous & next page link', function() {
+      expect(getPaginationEl(0)).not.toHaveClass('previous');
+      expect(getPaginationEl(-1)).not.toHaveClass('next');
+    });
+
+    it('changes "previous" & "next" text from interpolated attributes', function() {
+      $rootScope.previousText = '<<';
+      $rootScope.nextText = '>>';
+      element = $compile('<uib-pager align="false" previous-text="{{previousText}}" next-text="{{nextText}}" total-items="total" ng-model="currentPage"></uib-pager>')($rootScope);
+      $rootScope.$digest();
+
+      expect(getPaginationEl(0).text()).toBe('<<');
+      expect(getPaginationEl(-1).text()).toBe('>>');
+    });
+  });
+
+  it('disables the component when ng-disabled is true', function() {
+    $rootScope.disable = true;
+
+    element = $compile('<uib-pager total-items="total" ng-disabled="disable" ng-model="currentPage"></uib-pager>')($rootScope);
+    $rootScope.$digest();
+    updateCurrentPage(2);
+
+    expect(getPaginationEl(0)).toHaveClass('disabled');
+    expect(getPaginationEl(-1)).toHaveClass('disabled');
+
+    $rootScope.disable = false;
+    $rootScope.$digest();
+
+    expect(getPaginationEl(0)).not.toHaveClass('disabled');
+    expect(getPaginationEl(-1)).not.toHaveClass('disabled');
+
+    $rootScope.disable = true;
+    $rootScope.$digest();
+
+    expect(getPaginationEl(0)).toHaveClass('disabled');
+    expect(getPaginationEl(-1)).toHaveClass('disabled');
+  });
+});
+
+describe('pager deprecation', function() {
+  beforeEach(module('ui.bootstrap.pagination'));
+  beforeEach(module('template/pagination/pager.html'));
+
+  it('should suppress warning', function() {
+    module(function($provide) {
+      $provide.value('$paginationSuppressWarning', true);
+    });
+
+    inject(function($compile, $log, $rootScope) {
+      spyOn($log, 'warn');
+      
+      var element = $compile('<pager total-items="total" ng-model="currentPage"></pager>')($rootScope);
+      $rootScope.$digest();
+      expect($log.warn.calls.count()).toBe(0);
+    });
+  });
+
+  it('should give warning by default', inject(function($compile, $log, $rootScope) {
+    spyOn($log, 'warn');
+
+    var element = $compile('<pager total-items="total" ng-model="currentPage"></pager>')($rootScope);
+    $rootScope.$digest();
+
+    expect($log.warn.calls.count()).toBe(2);
+    expect($log.warn.calls.argsFor(0)).toEqual(['PaginationController is now deprecated. Use UibPaginationController instead.']);
+    expect($log.warn.calls.argsFor(1)).toEqual(['pager is now deprecated. Use uib-pager instead.']);
+  }));
+});
\ No newline at end of file
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/pagination/test/pagination.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/pagination/test/pagination.spec.js
new file mode 100644
index 0000000..a3ef250
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/pagination/test/pagination.spec.js
@@ -0,0 +1,766 @@
+describe('pagination directive', function() {
+  var $compile, $rootScope, $document, $templateCache, body, element;
+  beforeEach(module('ui.bootstrap.pagination'));
+  beforeEach(module('template/pagination/pagination.html'));
+  beforeEach(inject(function(_$compile_, _$rootScope_, _$document_, _$templateCache_) {
+    $compile = _$compile_;
+    $rootScope = _$rootScope_;
+    $rootScope.total = 47; // 5 pages
+    $rootScope.currentPage = 3;
+    $rootScope.disabled = false;
+    $document = _$document_;
+    $templateCache = _$templateCache_;
+    body = $document.find('body');
+    element = $compile('<uib-pagination total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
+    $rootScope.$digest();
+  }));
+
+  function getPaginationBarSize() {
+    return element.find('li').length;
+  }
+
+  function getPaginationEl(index) {
+    return element.find('li').eq(index);
+  }
+
+  function clickPaginationEl(index) {
+    getPaginationEl(index).find('a').click();
+  }
+
+  function getPaginationLinkEl(elem, index) {
+    return elem.find('li').eq(index).find('a');
+  }
+
+  function updateCurrentPage(value) {
+    $rootScope.currentPage = value;
+    $rootScope.$digest();
+  }
+
+  function setDisabled(value) {
+    $rootScope.disabled = value;
+    $rootScope.$digest();
+  }
+
+  it('has a "pagination" css class', function() {
+    expect(element.hasClass('pagination')).toBe(true);
+  });
+
+  it('exposes the controller to the template', function() {
+    $templateCache.put('template/pagination/pagination.html', '<div>{{pagination.randomText}}</div>');
+    var scope = $rootScope.$new();
+
+    element = $compile('<uib-pagination></uib-pagination>')(scope);
+    $rootScope.$digest();
+
+    var ctrl = element.controller('uibPagination');
+
+    expect(ctrl).toBeDefined();
+
+    ctrl.randomText = 'foo';
+    $rootScope.$digest();
+
+    expect(element.html()).toBe('foo');
+  });
+
+  it('allows custom templates', function() {
+    $templateCache.put('foo/bar.html', '<div>baz</div>');
+    var scope = $rootScope.$new();
+
+    element = $compile('<uib-pagination template-url="foo/bar.html"></uib-pagination>')(scope);
+    $rootScope.$digest();
+
+    expect(element.html()).toBe('baz');
+  });
+
+  it('contains num-pages + 2 li elements', function() {
+    expect(getPaginationBarSize()).toBe(7);
+    expect(getPaginationEl(0).text()).toBe('Previous');
+    expect(getPaginationEl(-1).text()).toBe('Next');
+  });
+
+  it('has the number of the page as text in each page item', function() {
+    for (var i = 1; i <= 5; i++) {
+      expect(getPaginationEl(i).text()).toEqual(''+i);
+    }
+  });
+
+  it('sets the current page to be active', function() {
+    expect(getPaginationEl($rootScope.currentPage).hasClass('active')).toBe(true);
+  });
+
+  it('disables the "previous" link if current page is 1', function() {
+    updateCurrentPage(1);
+    expect(getPaginationEl(0).hasClass('disabled')).toBe(true);
+  });
+
+  it('disables the "next" link if current page is last', function() {
+    updateCurrentPage(5);
+    expect(getPaginationEl(-1).hasClass('disabled')).toBe(true);
+  });
+
+  it('changes currentPage if a page link is clicked', function() {
+    clickPaginationEl(2);
+    expect($rootScope.currentPage).toBe(2);
+  });
+
+  it('changes currentPage if the "previous" link is clicked', function() {
+    clickPaginationEl(0);
+    expect($rootScope.currentPage).toBe(2);
+  });
+
+  it('changes currentPage if the "next" link is clicked', function() {
+    clickPaginationEl(-1);
+    expect($rootScope.currentPage).toBe(4);
+  });
+
+  it('does not change the current page on "previous" click if already at first page', function() {
+    updateCurrentPage(1);
+    clickPaginationEl(0);
+    expect($rootScope.currentPage).toBe(1);
+  });
+
+  it('does not change the current page on "next" click if already at last page', function() {
+    updateCurrentPage(5);
+    clickPaginationEl(-1);
+    expect($rootScope.currentPage).toBe(5);
+  });
+
+  it('changes the number of pages when `total-items` changes', function() {
+    $rootScope.total = 78; // 8 pages
+    $rootScope.$digest();
+
+    expect(getPaginationBarSize()).toBe(10);
+    expect(getPaginationEl(0).text()).toBe('Previous');
+    expect(getPaginationEl(-1).text()).toBe('Next');
+  });
+
+  it('does not "break" when `total-items` is undefined', function() {
+    $rootScope.total = undefined;
+    $rootScope.$digest();
+
+    expect(getPaginationBarSize()).toBe(3); // Previous, 1, Next
+    expect(getPaginationEl(0)).toHaveClass('disabled');
+    expect(getPaginationEl(1)).toHaveClass('active');
+    expect(getPaginationEl(2)).toHaveClass('disabled');
+  });
+
+  it('does not "break" when `total-items` is negative', function() {
+    $rootScope.total = -1;
+    $rootScope.$digest();
+
+    expect(getPaginationBarSize()).toBe(3); // Previous, 1, Next
+    expect(getPaginationEl(0)).toHaveClass('disabled');
+    expect(getPaginationEl(1)).toHaveClass('active');
+    expect(getPaginationEl(2)).toHaveClass('disabled');
+  });
+
+  it('does not change the current page when `total-items` changes but is valid', function() {
+    $rootScope.currentPage = 1;
+    $rootScope.total = 18; // 2 pages
+    $rootScope.$digest();
+
+    expect($rootScope.currentPage).toBe(1);
+  });
+
+  it('should blur a page link after it has been clicked', function() {
+    body.append(element);
+    var linkEl = getPaginationLinkEl(element, 2);
+
+    linkEl.focus();
+    expect(linkEl).toHaveFocus();
+
+    linkEl.click();
+    expect(linkEl).not.toHaveFocus();
+
+    element.remove();
+  });
+
+  it('should blur the "next" link after it has been clicked', function() {
+    body.append(element);
+    var linkEl = getPaginationLinkEl(element, -1);
+
+    linkEl.focus();
+    expect(linkEl).toHaveFocus();
+
+    linkEl.click();
+    expect(linkEl).not.toHaveFocus();
+
+    element.remove();
+  });
+
+  it('should blur the "prev" link after it has been clicked', function() {
+    body.append(element);
+    var linkEl = getPaginationLinkEl(element, 0);
+
+    linkEl.focus();
+    expect(linkEl).toHaveFocus();
+
+    linkEl.click();
+    expect(linkEl).not.toHaveFocus();
+
+    element.remove();
+  });
+
+  describe('`items-per-page`', function() {
+    beforeEach(function() {
+      $rootScope.perpage = 5;
+      element = $compile('<uib-pagination total-items="total" items-per-page="perpage" ng-model="currentPage"></uib-pagination>')($rootScope);
+      $rootScope.$digest();
+    });
+
+    it('changes the number of pages', function() {
+      expect(getPaginationBarSize()).toBe(12);
+      expect(getPaginationEl(0).text()).toBe('Previous');
+      expect(getPaginationEl(-1).text()).toBe('Next');
+    });
+
+    it('changes the number of pages when changes', function() {
+      $rootScope.perpage = 20;
+      $rootScope.$digest();
+
+      expect(getPaginationBarSize()).toBe(5);
+      expect(getPaginationEl(0).text()).toBe('Previous');
+      expect(getPaginationEl(-1).text()).toBe('Next');
+    });
+
+    it('selects the last page when current page is too big', function() {
+      $rootScope.perpage = 30;
+      $rootScope.$digest();
+
+      expect($rootScope.currentPage).toBe(2);
+      expect(getPaginationBarSize()).toBe(4);
+      expect(getPaginationEl(0).text()).toBe('Previous');
+      expect(getPaginationEl(-1).text()).toBe('Next');
+    });
+
+    it('displays a single page when it is negative', function() {
+      $rootScope.perpage = -1;
+      $rootScope.$digest();
+
+      expect(getPaginationBarSize()).toBe(3);
+      expect(getPaginationEl(0).text()).toBe('Previous');
+      expect(getPaginationEl(1).text()).toBe('1');
+      expect(getPaginationEl(-1).text()).toBe('Next');
+    });
+  });
+
+  describe('executes  `ng-change` expression', function() {
+    beforeEach(function() {
+      $rootScope.selectPageHandler = jasmine.createSpy('selectPageHandler');
+      element = $compile('<uib-pagination total-items="total" ng-model="currentPage" ng-change="selectPageHandler()"></uib-pagination>')($rootScope);
+      $rootScope.$digest();
+    });
+
+    it('when an element is clicked', function() {
+      clickPaginationEl(2);
+      expect($rootScope.selectPageHandler).toHaveBeenCalled();
+    });
+  });
+
+  describe('when `page` is not a number', function() {
+    it('handles numerical string', function() {
+      updateCurrentPage('2');
+      expect(getPaginationEl(2)).toHaveClass('active');
+
+      updateCurrentPage('04');
+      expect(getPaginationEl(4)).toHaveClass('active');
+    });
+
+    it('defaults to 1 if non-numeric', function() {
+      updateCurrentPage('pizza');
+      expect(getPaginationEl(1)).toHaveClass('active');
+    });
+  });
+
+  describe('with `max-size` option', function() {
+    beforeEach(function() {
+      $rootScope.total = 98; // 10 pages
+      $rootScope.currentPage = 3;
+      $rootScope.maxSize = 5;
+      element = $compile('<uib-pagination total-items="total" ng-model="currentPage" max-size="maxSize"></uib-pagination>')($rootScope);
+      $rootScope.$digest();
+    });
+
+    it('contains maxsize + 2 li elements', function() {
+      expect(getPaginationBarSize()).toBe($rootScope.maxSize + 2);
+      expect(getPaginationEl(0).text()).toBe('Previous');
+      expect(getPaginationEl(-1).text()).toBe('Next');
+    });
+
+    it('shows the page number even if it can\'t be shown in the middle', function() {
+      updateCurrentPage(1);
+      expect(getPaginationEl(1)).toHaveClass('active');
+
+      updateCurrentPage(10);
+      expect(getPaginationEl(-2)).toHaveClass('active');
+    });
+
+    it('shows the page number in middle after the next link is clicked', function() {
+      updateCurrentPage(6);
+      clickPaginationEl(-1);
+
+      expect($rootScope.currentPage).toBe(7);
+      expect(getPaginationEl(3)).toHaveClass('active');
+      expect(getPaginationEl(3).text()).toBe(''+$rootScope.currentPage);
+    });
+
+    it('shows the page number in middle after the prev link is clicked', function() {
+      updateCurrentPage(7);
+      clickPaginationEl(0);
+
+      expect($rootScope.currentPage).toBe(6);
+      expect(getPaginationEl(3)).toHaveClass('active');
+      expect(getPaginationEl(3).text()).toBe(''+$rootScope.currentPage);
+    });
+
+    it('changes pagination bar size when max-size value changed', function() {
+      $rootScope.maxSize = 7;
+      $rootScope.$digest();
+      expect(getPaginationBarSize()).toBe(9);
+    });
+
+    it('sets the pagination bar size to num-pages, if max-size is greater than num-pages ', function() {
+      $rootScope.maxSize = 15;
+      $rootScope.$digest();
+      expect(getPaginationBarSize()).toBe(12);
+    });
+
+    it('should not change value of max-size expression, if max-size is greater than num-pages ', function() {
+      $rootScope.maxSize = 15;
+      $rootScope.$digest();
+      expect($rootScope.maxSize).toBe(15);
+    });
+
+    it('should not display page numbers, if max-size is zero', function() {
+      $rootScope.maxSize = 0;
+      $rootScope.$digest();
+      expect(getPaginationBarSize()).toBe(2);
+      expect(getPaginationEl(0).text()).toBe('Previous');
+      expect(getPaginationEl(-1).text()).toBe('Next');
+    });
+
+    it('should blur page link when visible range changes', function () {
+      body.append(element);
+      var linkEl = getPaginationLinkEl(element, 4);
+
+      linkEl.focus();
+      expect(linkEl).toHaveFocus();
+
+      linkEl.click();
+      expect(linkEl).not.toHaveFocus();
+
+      element.remove();
+    });
+  });
+
+  describe('with `max-size` option & no `rotate`', function() {
+    beforeEach(function() {
+      $rootScope.total = 115; // 12 pages
+      $rootScope.currentPage = 7;
+      $rootScope.maxSize = 5;
+      $rootScope.rotate = false;
+      element = $compile('<uib-pagination total-items="total" ng-model="currentPage" max-size="maxSize" rotate="rotate"></uib-pagination>')($rootScope);
+      $rootScope.$digest();
+    });
+
+    it('contains maxsize + 4 elements', function() {
+      expect(getPaginationBarSize()).toBe($rootScope.maxSize + 4);
+      expect(getPaginationEl(0).text()).toBe('Previous');
+      expect(getPaginationEl(1).text()).toBe('...');
+      expect(getPaginationEl(2).text()).toBe('6');
+      expect(getPaginationEl(-3).text()).toBe('10');
+      expect(getPaginationEl(-2).text()).toBe('...');
+      expect(getPaginationEl(-1).text()).toBe('Next');
+    });
+
+    it('shows only the next ellipsis element on first page set', function() {
+      updateCurrentPage(3);
+      expect(getPaginationEl(1).text()).toBe('1');
+      expect(getPaginationEl(-3).text()).toBe('5');
+      expect(getPaginationEl(-2).text()).toBe('...');
+    });
+
+    it('shows only the previous ellipsis element on last page set', function() {
+      updateCurrentPage(12);
+      expect(getPaginationBarSize()).toBe(5);
+      expect(getPaginationEl(1).text()).toBe('...');
+      expect(getPaginationEl(2).text()).toBe('11');
+      expect(getPaginationEl(-2).text()).toBe('12');
+    });
+
+    it('moves to the previous set when first ellipsis is clicked', function() {
+      expect(getPaginationEl(1).text()).toBe('...');
+
+      clickPaginationEl(1);
+
+      expect($rootScope.currentPage).toBe(5);
+      expect(getPaginationEl(-3)).toHaveClass('active');
+    });
+
+    it('moves to the next set when last ellipsis is clicked', function() {
+      expect(getPaginationEl(-2).text()).toBe('...');
+
+      clickPaginationEl(-2);
+
+      expect($rootScope.currentPage).toBe(11);
+      expect(getPaginationEl(2)).toHaveClass('active');
+    });
+
+    it('should not display page numbers, if max-size is zero', function() {
+      $rootScope.maxSize = 0;
+      $rootScope.$digest();
+
+      expect(getPaginationBarSize()).toBe(2);
+      expect(getPaginationEl(0).text()).toBe('Previous');
+      expect(getPaginationEl(1).text()).toBe('Next');
+    });
+  });
+
+  describe('pagination directive with `boundary-links`', function() {
+    beforeEach(function() {
+      element = $compile('<uib-pagination boundary-links="true" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
+      $rootScope.$digest();
+    });
+
+    it('contains num-pages + 4 li elements', function() {
+      expect(getPaginationBarSize()).toBe(9);
+      expect(getPaginationEl(0).text()).toBe('First');
+      expect(getPaginationEl(1).text()).toBe('Previous');
+      expect(getPaginationEl(-2).text()).toBe('Next');
+      expect(getPaginationEl(-1).text()).toBe('Last');
+    });
+
+    it('has first and last li elements visible', function() {
+      expect(getPaginationEl(0).css('display')).not.toBe('none');
+      expect(getPaginationEl(-1).css('display')).not.toBe('none');
+    });
+
+
+    it('disables the "first" & "previous" link if current page is 1', function() {
+      updateCurrentPage(1);
+
+      expect(getPaginationEl(0)).toHaveClass('disabled');
+      expect(getPaginationEl(1)).toHaveClass('disabled');
+    });
+
+    it('disables the "last" & "next" link if current page is num-pages', function() {
+      updateCurrentPage(5);
+
+      expect(getPaginationEl(-2)).toHaveClass('disabled');
+      expect(getPaginationEl(-1)).toHaveClass('disabled');
+    });
+
+    it('changes currentPage if the "first" link is clicked', function() {
+      clickPaginationEl(0);
+      expect($rootScope.currentPage).toBe(1);
+    });
+
+    it('changes currentPage if the "last" link is clicked', function() {
+      clickPaginationEl(-1);
+      expect($rootScope.currentPage).toBe(5);
+    });
+
+    it('does not change the current page on "first" click if already at first page', function() {
+      updateCurrentPage(1);
+      clickPaginationEl(0);
+      expect($rootScope.currentPage).toBe(1);
+    });
+
+    it('does not change the current page on "last" click if already at last page', function() {
+      updateCurrentPage(5);
+      clickPaginationEl(-1);
+      expect($rootScope.currentPage).toBe(5);
+    });
+
+    it('changes "first" & "last" text from attributes', function() {
+      element = $compile('<uib-pagination boundary-links="true" first-text="<<<" last-text=">>>" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
+      $rootScope.$digest();
+
+      expect(getPaginationEl(0).text()).toBe('<<<');
+      expect(getPaginationEl(-1).text()).toBe('>>>');
+    });
+
+    it('changes "previous" & "next" text from attributes', function() {
+      element = $compile('<uib-pagination boundary-links="true" previous-text="<<" next-text=">>" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
+      $rootScope.$digest();
+
+      expect(getPaginationEl(1).text()).toBe('<<');
+      expect(getPaginationEl(-2).text()).toBe('>>');
+    });
+
+    it('changes "first" & "last" text from interpolated attributes', function() {
+      $rootScope.myfirstText = '<<<';
+      $rootScope.mylastText = '>>>';
+      element = $compile('<uib-pagination boundary-links="true" first-text="{{myfirstText}}" last-text="{{mylastText}}" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
+      $rootScope.$digest();
+
+      expect(getPaginationEl(0).text()).toBe('<<<');
+      expect(getPaginationEl(-1).text()).toBe('>>>');
+    });
+
+    it('changes "previous" & "next" text from interpolated attributes', function() {
+      $rootScope.previousText = '<<';
+      $rootScope.nextText = '>>';
+      element = $compile('<uib-pagination boundary-links="true" previous-text="{{previousText}}" next-text="{{nextText}}" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
+      $rootScope.$digest();
+
+      expect(getPaginationEl(1).text()).toBe('<<');
+      expect(getPaginationEl(-2).text()).toBe('>>');
+    });
+
+    it('should blur the "first" link after it has been clicked', function() {
+      body.append(element);
+      var linkEl = getPaginationLinkEl(element, 0);
+
+      linkEl.focus();
+      expect(linkEl).toHaveFocus();
+
+      linkEl.click();
+      expect(linkEl).not.toHaveFocus();
+
+      element.remove();
+    });
+
+    it('should blur the "last" link after it has been clicked', function() {
+      body.append(element);
+      var linkEl = getPaginationLinkEl(element, -1);
+
+      linkEl.focus();
+      expect(linkEl).toHaveFocus();
+
+      linkEl.click();
+      expect(linkEl).not.toHaveFocus();
+
+      element.remove();
+    });
+  });
+
+  describe('pagination directive with just number links', function() {
+    beforeEach(function() {
+      element = $compile('<uib-pagination direction-links="false" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
+      $rootScope.$digest();
+    });
+
+    it('contains num-pages li elements', function() {
+      expect(getPaginationBarSize()).toBe(5);
+      expect(getPaginationEl(0).text()).toBe('1');
+      expect(getPaginationEl(-1).text()).toBe('5');
+    });
+
+    it('has the number of the page as text in each page item', function() {
+      for(var i = 0; i < 5; i++) {
+        expect(getPaginationEl(i).text()).toEqual(''+(i+1));
+      }
+    });
+
+    it('sets the current page to be active', function() {
+      expect(getPaginationEl(2)).toHaveClass('active');
+    });
+
+    it('does not disable the "1" link if current page is 1', function() {
+      updateCurrentPage(1);
+
+      expect(getPaginationEl(0)).not.toHaveClass('disabled');
+      expect(getPaginationEl(0)).toHaveClass('active');
+    });
+
+    it('does not disable the "last" link if current page is last page', function() {
+      updateCurrentPage(5);
+
+      expect(getPaginationEl(-1)).not.toHaveClass('disabled');
+      expect(getPaginationEl(-1)).toHaveClass('active');
+    });
+
+    it('changes currentPage if a page link is clicked', function() {
+      clickPaginationEl(1);
+      expect($rootScope.currentPage).toBe(2);
+    });
+
+    it('changes the number of items when total items changes', function() {
+      $rootScope.total = 73; // 8 pages
+      $rootScope.$digest();
+
+      expect(getPaginationBarSize()).toBe(8);
+      expect(getPaginationEl(0).text()).toBe('1');
+      expect(getPaginationEl(-1).text()).toBe('8');
+    });
+  });
+
+  describe('with just boundary & number links', function() {
+    beforeEach(function() {
+      $rootScope.directions = false;
+      element = $compile('<uib-pagination boundary-links="true" direction-links="directions" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
+      $rootScope.$digest();
+    });
+
+    it('contains number of pages + 2 li elements', function() {
+      expect(getPaginationBarSize()).toBe(7);
+      expect(getPaginationEl(0).text()).toBe('First');
+      expect(getPaginationEl(1).text()).toBe('1');
+      expect(getPaginationEl(-2).text()).toBe('5');
+      expect(getPaginationEl(-1).text()).toBe('Last');
+    });
+
+    it('disables the "first" & activates "1" link if current page is 1', function() {
+      updateCurrentPage(1);
+
+      expect(getPaginationEl(0)).toHaveClass('disabled');
+      expect(getPaginationEl(1)).not.toHaveClass('disabled');
+      expect(getPaginationEl(1)).toHaveClass('active');
+    });
+
+    it('disables the "last" & "next" link if current page is num-pages', function() {
+      updateCurrentPage(5);
+
+      expect(getPaginationEl(-2)).toHaveClass('active');
+      expect(getPaginationEl(-2)).not.toHaveClass('disabled');
+      expect(getPaginationEl(-1)).toHaveClass('disabled');
+    });
+  });
+
+  describe('`num-pages`', function () {
+    beforeEach(function() {
+      $rootScope.numpg = null;
+      element = $compile('<uib-pagination total-items="total" ng-model="currentPage" num-pages="numpg"></uib-pagination>')($rootScope);
+      $rootScope.$digest();
+    });
+
+    it('equals to total number of pages', function() {
+      expect($rootScope.numpg).toBe(5);
+    });
+
+    it('changes when total number of pages change', function() {
+      $rootScope.total = 73; // 8 pages
+      $rootScope.$digest();
+      expect($rootScope.numpg).toBe(8);
+    });
+
+    it('shows minimun one page if total items are not defined and does not break binding', function() {
+      $rootScope.total = undefined;
+      $rootScope.$digest();
+      expect($rootScope.numpg).toBe(1);
+
+      $rootScope.total = 73; // 8 pages
+      $rootScope.$digest();
+      expect($rootScope.numpg).toBe(8);
+    });
+  });
+
+  describe('setting `paginationConfig`', function() {
+    var originalConfig, paginationConfig;
+    beforeEach(inject(function(_uibPaginationConfig_) {
+      originalConfig = angular.copy(_uibPaginationConfig_);
+      paginationConfig = _uibPaginationConfig_;
+    }));
+    afterEach(inject(function(_uibPaginationConfig_) {
+      // return it to the original stat
+      angular.copy(originalConfig, _uibPaginationConfig_);
+    }));
+
+    it('should change paging text', function() {
+      paginationConfig.boundaryLinks = true;
+      paginationConfig.directionLinks = true;
+      paginationConfig.firstText = 'FI';
+      paginationConfig.previousText = 'PR';
+      paginationConfig.nextText = 'NE';
+      paginationConfig.lastText = 'LA';
+      element = $compile('<uib-pagination total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
+      $rootScope.$digest();
+
+      expect(getPaginationEl(0).text()).toBe('FI');
+      expect(getPaginationEl(1).text()).toBe('PR');
+      expect(getPaginationEl(-2).text()).toBe('NE');
+      expect(getPaginationEl(-1).text()).toBe('LA');
+    });
+
+    it('contains number of pages + 2 li elements', function() {
+      paginationConfig.itemsPerPage = 5;
+      element = $compile('<uib-pagination total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
+      $rootScope.$digest();
+
+      expect(getPaginationBarSize()).toBe(12);
+    });
+
+    it('should take maxSize defaults into account', function() {
+      paginationConfig.maxSize = 2;
+      element = $compile('<uib-pagination total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
+      $rootScope.$digest();
+
+      expect(getPaginationBarSize()).toBe(4);
+    });
+  });
+
+  describe('override configuration from attributes', function() {
+    beforeEach(function() {
+      element = $compile('<uib-pagination boundary-links="true" first-text="<<" previous-text="<" next-text=">" last-text=">>" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
+      $rootScope.$digest();
+    });
+
+    it('contains number of pages + 4 li elements', function() {
+      expect(getPaginationBarSize()).toBe(9);
+    });
+
+    it('should change paging text from attribute', function() {
+      expect(getPaginationEl(0).text()).toBe('<<');
+      expect(getPaginationEl(1).text()).toBe('<');
+      expect(getPaginationEl(-2).text()).toBe('>');
+      expect(getPaginationEl(-1).text()).toBe('>>');
+    });
+  });
+
+  describe('disabled with ngDisable', function() {
+    beforeEach(function() {
+      element = $compile('<uib-pagination total-items="total" ng-model="currentPage" ng-disabled="disabled"></uib-pagination>')($rootScope);
+      $rootScope.currentPage = 3;
+      $rootScope.$digest();
+    });
+
+    it('should not respond to clicking', function() {
+      setDisabled(true);
+      clickPaginationEl(2);
+      expect($rootScope.currentPage).toBe(3);
+      setDisabled(false);
+      clickPaginationEl(2);
+      expect($rootScope.currentPage).toBe(2);
+    });
+
+    it('should change the class of all buttons except selected one', function() {
+      setDisabled(false);
+      expect(getPaginationEl(3).hasClass('active')).toBe(true);
+      expect(getPaginationEl(4).hasClass('active')).toBe(false);
+      setDisabled(true);
+      expect(getPaginationEl(3).hasClass('disabled')).toBe(false);
+      expect(getPaginationEl(4).hasClass('disabled')).toBe(true);
+    });
+  });
+});
+
+describe('pagination deprecation', function() {
+  beforeEach(module('ui.bootstrap.pagination'));
+  beforeEach(module('template/pagination/pagination.html'));
+
+  it('should suppress warning', function() {
+    module(function($provide) {
+      $provide.value('$paginationSuppressWarning', true);
+    });
+
+    inject(function($compile, $log, $rootScope) {
+      spyOn($log, 'warn');
+
+      var element = $compile('<pagination></pagination>')($rootScope);
+      $rootScope.$digest();
+      expect($log.warn.calls.count()).toBe(0);
+    });
+  });
+
+  it('should give warning by default', inject(function($compile, $log, $rootScope) {
+    spyOn($log, 'warn');
+
+    var element = $compile('<pagination></pagination>')($rootScope);
+    $rootScope.$digest();
+
+    expect($log.warn.calls.count()).toBe(2);
+    expect($log.warn.calls.argsFor(0)).toEqual(['PaginationController is now deprecated. Use UibPaginationController instead.']);
+    expect($log.warn.calls.argsFor(1)).toEqual(['pagination is now deprecated. Use uib-pagination instead.']);
+  }));
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/popover/docs/demo.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/popover/docs/demo.html
new file mode 100644
index 0000000..bb90429
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/popover/docs/demo.html
@@ -0,0 +1,44 @@
+<div ng-controller="PopoverDemoCtrl">
+    <h4>Dynamic</h4>
+    <div class="form-group">
+      <label>Popup Text:</label>
+      <input type="text" ng-model="dynamicPopover.content" class="form-control">
+    </div>
+    <div class="form-group">
+      <label>Popup Title:</label>
+      <input type="text" ng-model="dynamicPopover.title" class="form-control">
+    </div>
+    <div class="form-group">
+      <label>Popup Template:</label>
+      <input type="text" ng-model="dynamicPopover.templateUrl" class="form-control">
+    </div>
+    <button uib-popover="{{dynamicPopover.content}}" popover-title="{{dynamicPopover.title}}" type="button" class="btn btn-default">Dynamic Popover</button>
+
+    <button uib-popover-template="dynamicPopover.templateUrl" popover-title="{{dynamicPopover.title}}" type="button" class="btn btn-default">Popover With Template</button>
+
+    <script type="text/ng-template" id="myPopoverTemplate.html">
+        <div>{{dynamicPopover.content}}</div>
+        <div class="form-group">
+          <label>Popup Title:</label>
+          <input type="text" ng-model="dynamicPopover.title" class="form-control">
+        </div>
+    </script>
+    <hr />
+    <h4>Positional</h4>
+    <button popover-placement="top" uib-popover="On the Top!" type="button" class="btn btn-default">Top</button>
+    <button popover-placement="left" uib-popover="On the Left!" type="button" class="btn btn-default">Left</button>
+    <button popover-placement="right" uib-popover="On the Right!" type="button" class="btn btn-default">Right</button>
+    <button popover-placement="bottom" uib-popover="On the Bottom!" type="button" class="btn btn-default">Bottom</button>
+    
+    <hr />
+    <h4>Triggers</h4>
+    <p>
+      <button uib-popover="I appeared on mouse enter!" popover-trigger="mouseenter" type="button" class="btn btn-default">Mouseenter</button>
+    </p>
+    <input type="text" value="Click me!" uib-popover="I appeared on focus! Click away and I'll vanish..."  popover-trigger="focus" class="form-control">
+
+    <hr />
+    <h4>Other</h4>
+    <button popover-animation="true" uib-popover="I fade in and out!" type="button" class="btn btn-default">fading</button>
+    <button uib-popover="I have a title!" popover-title="The title." type="button" class="btn btn-default">title</button>
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/popover/docs/demo.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/popover/docs/demo.js
new file mode 100644
index 0000000..0d94f28
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/popover/docs/demo.js
@@ -0,0 +1,7 @@
+angular.module('ui.bootstrap.demo').controller('PopoverDemoCtrl', function ($scope) {
+  $scope.dynamicPopover = {
+    content: 'Hello, World!',
+    templateUrl: 'myPopoverTemplate.html',
+    title: 'Title'
+  };
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/popover/docs/readme.md b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/popover/docs/readme.md
new file mode 100644
index 0000000..5f91772
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/popover/docs/readme.md
@@ -0,0 +1,49 @@
+A lightweight, extensible directive for fancy popover creation. The popover
+directive supports multiple placements, optional transition animation, and more.
+
+Like the Bootstrap jQuery plugin, the popover **requires** the tooltip
+module.
+
+There are two versions of the popover: `uib-popover` and `uib-popover-template`:
+
+- `uib-popover` takes text only and will escape any HTML provided for the popover
+  body.
+- `uib-popover-html` takes an expression that evaluates to an html string. *The user is responsible for ensuring the
+  content is safe to put into the DOM!*
+- `uib-popover-template` a URL representing the location of a template to
+  use for the popover body. Note that the contents of this template need to be
+  wrapped in a tag, e.g., `<div></div>`.
+
+The popover directives provides several optional attributes to control how it
+will display:
+
+- `popover-title`: A string to display as a fancy title.
+- `popover-placement`: Where to place it? Defaults to "top", but also accepts
+  "bottom", "left", "right".
+- `popover-animation`: Should it fade in and out? Defaults to "true".
+- `popover-popup-delay`: For how long should the user have to have the mouse
+  over the element before the popover shows (in milliseconds)? Defaults to 0.
+- `popover-popup-close-delay`: For how long should the popover remain open
+  after the close trigger event? Defaults to 0.
+- `popover-trigger`: What should trigger the show of the popover? See the
+  `tooltip` directive for supported values.
+- `popover-append-to-body`: Should the tooltip be appended to `$body` instead of
+  the parent element?
+- `popover-is-open` <i class="glyphicon glyphicon-eye-open"></i>
+  _(Default: false)_:
+  Whether to show the popover.
+
+The popover directives require the `$position` service.
+
+The popover directive also supports various default configurations through the
+$tooltipProvider. See the [tooltip](#tooltip) section for more information.
+
+**Known issues**
+
+For Safari 7+ support, if you want to use **focus** `popover-trigger`, you need to use an anchor tag with a tab index. For example:
+
+```
+<a tabindex="0" uib-popover="Test" popover-trigger="focus" class="btn btn-default">
+  Click Me
+</a>
+```
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/popover/popover.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/popover/popover.js
new file mode 100644
index 0000000..bc46086
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/popover/popover.js
@@ -0,0 +1,125 @@
+/**
+ * The following features are still outstanding: popup delay, animation as a
+ * function, placement as a function, inside, support for more triggers than
+ * just mouse enter/leave, and selector delegatation.
+ */
+angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip'])
+
+.directive('uibPopoverTemplatePopup', function() {
+  return {
+    replace: true,
+    scope: { title: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
+      originScope: '&' },
+    templateUrl: 'template/popover/popover-template.html',
+    link: function(scope, element) {
+      element.addClass('popover');
+    }
+  };
+})
+
+.directive('uibPopoverTemplate', ['$uibTooltip', function($uibTooltip) {
+  return $uibTooltip('uibPopoverTemplate', 'popover', 'click', {
+    useContentExp: true
+  });
+}])
+
+.directive('uibPopoverHtmlPopup', function() {
+  return {
+    replace: true,
+    scope: { contentExp: '&', title: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
+    templateUrl: 'template/popover/popover-html.html',
+    link: function(scope, element) {
+      element.addClass('popover');
+    }
+  };
+})
+
+.directive('uibPopoverHtml', ['$uibTooltip', function($uibTooltip) {
+  return $uibTooltip('uibPopoverHtml', 'popover', 'click', {
+    useContentExp: true
+  });
+}])
+
+.directive('uibPopoverPopup', function() {
+  return {
+    replace: true,
+    scope: { title: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
+    templateUrl: 'template/popover/popover.html',
+    link: function(scope, element) {
+      element.addClass('popover');
+    }
+  };
+})
+
+.directive('uibPopover', ['$uibTooltip', function($uibTooltip) {
+  return $uibTooltip('uibPopover', 'popover', 'click');
+}]);
+
+/* Deprecated popover below */
+
+angular.module('ui.bootstrap.popover')
+
+.value('$popoverSuppressWarning', false)
+
+.directive('popoverTemplatePopup', ['$log', '$popoverSuppressWarning', function($log, $popoverSuppressWarning) {
+  return {
+    replace: true,
+    scope: { title: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
+      originScope: '&' },
+    templateUrl: 'template/popover/popover-template.html',
+    link: function(scope, element) {
+      if (!$popoverSuppressWarning) {
+        $log.warn('popover-template-popup is now deprecated. Use uib-popover-template-popup instead.');
+      }
+
+      element.addClass('popover');
+    }
+  };
+}])
+
+.directive('popoverTemplate', ['$tooltip', function($tooltip) {
+  return $tooltip('popoverTemplate', 'popover', 'click', {
+    useContentExp: true
+  });
+}])
+
+.directive('popoverHtmlPopup', ['$log', '$popoverSuppressWarning', function($log, $popoverSuppressWarning) {
+  return {
+    replace: true,
+    scope: { contentExp: '&', title: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
+    templateUrl: 'template/popover/popover-html.html',
+    link: function(scope, element) {
+      if (!$popoverSuppressWarning) {
+        $log.warn('popover-html-popup is now deprecated. Use uib-popover-html-popup instead.');
+      }
+
+      element.addClass('popover');
+    }
+  };
+}])
+
+.directive('popoverHtml', ['$tooltip', function($tooltip) {
+  return $tooltip('popoverHtml', 'popover', 'click', {
+    useContentExp: true
+  });
+}])
+
+.directive('popoverPopup', ['$log', '$popoverSuppressWarning', function($log, $popoverSuppressWarning) {
+  return {
+    replace: true,
+    scope: { title: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
+    templateUrl: 'template/popover/popover.html',
+    link: function(scope, element) {
+      if (!$popoverSuppressWarning) {
+        $log.warn('popover-popup is now deprecated. Use uib-popover-popup instead.');
+      }
+
+      element.addClass('popover');
+    }
+  };
+}])
+
+.directive('popover', ['$tooltip', function($tooltip) {
+
+  return $tooltip('popover', 'popover', 'click');
+}]);
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/popover/test/popover-html.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/popover/test/popover-html.spec.js
new file mode 100644
index 0000000..9c59daa
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/popover/test/popover-html.spec.js
@@ -0,0 +1,250 @@
+describe('popover', function() {
+  var elm,
+      elmBody,
+      scope,
+      elmScope,
+      tooltipScope;
+
+  // load the popover code
+  beforeEach(module('ui.bootstrap.popover'));
+
+  // load the template
+  beforeEach(module('template/popover/popover-html.html'));
+
+  beforeEach(inject(function($rootScope, $compile, $sce) {
+    elmBody = angular.element(
+      '<div><span uib-popover-html="template">Selector Text</span></div>'
+    );
+
+    scope = $rootScope;
+    scope.template = $sce.trustAsHtml('<span>My template</span>');
+    $compile(elmBody)(scope);
+    scope.$digest();
+    elm = elmBody.find('span');
+    elmScope = elm.scope();
+    tooltipScope = elmScope.$$childTail;
+  }));
+
+  it('should not be open initially', inject(function() {
+    expect(tooltipScope.isOpen).toBe(false);
+
+    // We can only test *that* the popover-popup element wasn't created as the
+    // implementation is templated and replaced.
+    expect(elmBody.children().length).toBe(1);
+  }));
+
+  it('should open on click', inject(function() {
+    elm.trigger('click');
+    tooltipScope.$digest();
+    expect(tooltipScope.isOpen).toBe(true);
+
+    // We can only test *that* the popover-popup element was created as the
+    // implementation is templated and replaced.
+    expect(elmBody.children().length).toBe(2);
+  }));
+
+  it('should close on second click', inject(function() {
+    elm.trigger('click');
+    tooltipScope.$digest();
+    expect(tooltipScope.isOpen).toBe(true);
+    elm.trigger('click');
+    tooltipScope.$digest();
+    expect(tooltipScope.isOpen).toBe(false);
+  }));
+
+  it('should not open on click if template is empty', inject(function() {
+    scope.template = null;
+    scope.$digest();
+
+    elm.trigger('click');
+    tooltipScope.$digest();
+    expect(tooltipScope.isOpen).toBe(false);
+
+    expect(elmBody.children().length).toBe(1);
+  }));
+
+  it('should show updated text', inject(function($sce) {
+    scope.template = $sce.trustAsHtml('<span>My template</span>');
+    scope.$digest();
+
+    elm.trigger('click');
+    tooltipScope.$digest();
+    expect(tooltipScope.isOpen).toBe(true);
+
+    expect(elmBody.children().eq(1).text().trim()).toBe('My template');
+
+    scope.template = $sce.trustAsHtml('<span>Another template</span>');
+    scope.$digest();
+
+    expect(elmBody.children().eq(1).text().trim()).toBe('Another template');
+  }));
+
+  it('should hide popover when template becomes empty', inject(function($timeout) {
+    elm.trigger('click');
+    tooltipScope.$digest();
+    expect(tooltipScope.isOpen).toBe(true);
+
+    scope.template = '';
+    scope.$digest();
+
+    expect(tooltipScope.isOpen).toBe(false);
+
+    $timeout.flush();
+    expect(elmBody.children().length).toBe(1);
+  }));
+
+
+  it('should not unbind event handlers created by other directives - issue 456', inject(function($compile) {
+    scope.click = function() {
+      scope.clicked = !scope.clicked;
+    };
+
+    elmBody = angular.element(
+      '<div><input uib-popover-html="template" ng-click="click()" popover-trigger="mouseenter"/></div>'
+    );
+    $compile(elmBody)(scope);
+    scope.$digest();
+
+    elm = elmBody.find('input');
+
+    elm.trigger('mouseenter');
+    tooltipScope.$digest();
+    elm.trigger('mouseleave');
+    tooltipScope.$digest();
+    expect(scope.clicked).toBeFalsy();
+
+    elm.click();
+    expect(scope.clicked).toBeTruthy();
+  }));
+
+  it('should popup with animate class by default', inject(function() {
+    elm.trigger('click');
+    tooltipScope.$digest();
+    expect(tooltipScope.isOpen).toBe(true);
+
+    expect(elmBody.children().eq(1)).toHaveClass('fade');
+  }));
+
+  it('should popup without animate class when animation disabled', inject(function($compile) {
+    elmBody = angular.element(
+      '<div><span uib-popover-html="template" popover-animation="false">Selector Text</span></div>'
+    );
+
+    $compile(elmBody)(scope);
+    scope.$digest();
+    elm = elmBody.find('span');
+    elmScope = elm.scope();
+    tooltipScope = elmScope.$$childTail;
+
+    elm.trigger('click');
+    tooltipScope.$digest();
+    expect(tooltipScope.isOpen).toBe(true);
+    expect(elmBody.children().eq(1)).not.toHaveClass('fade');
+  }));
+
+  describe('supports options', function() {
+    describe('placement', function() {
+      it('can specify an alternative, valid placement', inject(function($compile) {
+        elmBody = angular.element(
+          '<div><span uib-popover-html="template" popover-placement="left">Trigger here</span></div>'
+        );
+        $compile(elmBody)(scope);
+        scope.$digest();
+        elm = elmBody.find('span');
+        elmScope = elm.scope();
+        tooltipScope = elmScope.$$childTail;
+
+        elm.trigger('click');
+        tooltipScope.$digest();
+        expect(tooltipScope.isOpen).toBe(true);
+
+        expect(elmBody.children().length).toBe(2);
+        var ttipElement = elmBody.find('div.popover');
+        expect(ttipElement).toHaveClass('left');
+      }));
+
+    });
+
+    describe('class', function() {
+      it('can specify a custom class', inject(function($compile) {
+        elmBody = angular.element(
+          '<div><span uib-popover-html="template" popover-class="custom">Trigger here</span></div>'
+        );
+        $compile(elmBody)(scope);
+        scope.$digest();
+        elm = elmBody.find('span');
+        elmScope = elm.scope();
+        tooltipScope = elmScope.$$childTail;
+
+        elm.trigger('click');
+        tooltipScope.$digest();
+        expect(tooltipScope.isOpen).toBe(true);
+
+        expect(elmBody.children().length).toBe(2);
+        var ttipElement = elmBody.find('div.popover');
+        expect(ttipElement).toHaveClass('custom');
+      }));
+    });
+  });
+});
+
+/* Deprecation tests below */
+
+describe('popover deprecation', function() {
+  beforeEach(module('ui.bootstrap.popover'));
+  beforeEach(module('template/popover/popover-html.html'));
+
+  var elm, elmBody, elmScope, tooltipScope;
+
+  function trigger(element, evt) {
+    evt = new Event(evt);
+
+    element[0].dispatchEvent(evt);
+    element.scope().$$childTail.$digest();
+  }
+
+  it('should suppress warning', function() {
+    module(function($provide) {
+      $provide.value('$popoverSuppressWarning', true);
+      $provide.value('$tooltipSuppressWarning', true);
+    });
+
+    inject(function($compile, $log, $rootScope, $sce) {
+      spyOn($log, 'warn');
+
+      $rootScope.html = 'I say: <strong class="hello">Hello!</strong>';
+      $rootScope.safeHtml = $sce.trustAsHtml($rootScope.html);
+      elmBody = angular.element('<div><span popover-html="safeHtml">Selector Text</span></div>');
+      $compile(elmBody)($rootScope);
+      $rootScope.$digest();
+      elm = elmBody.find('span');
+      elmScope = elm.scope();
+      tooltipScope = elmScope.$$childTail;
+
+      trigger(elm, 'mouseenter');
+      tooltipScope.$digest();
+
+      expect($log.warn.calls.count()).toBe(0);
+    });
+  });
+
+  it('should give warning by default', inject(function($compile, $log, $rootScope, $sce) {
+    spyOn($log, 'warn');
+
+    $rootScope.html = 'I say: <strong class="hello">Hello!</strong>';
+    $rootScope.safeHtml = $sce.trustAsHtml($rootScope.html);
+    elmBody = angular.element('<div><span popover-html="safeHtml">Selector Text</span></div>');
+    $compile(elmBody)($rootScope);
+    $rootScope.$digest();
+    elm = elmBody.find('span');
+    elmScope = elm.scope();
+    tooltipScope = elmScope.$$childTail;
+
+    elm.trigger('click');
+    tooltipScope.$digest();
+
+    expect($log.warn.calls.count()).toBe(2);
+    expect($log.warn.calls.argsFor(0)).toEqual(['$tooltip is now deprecated. Use $uibTooltip instead.']);
+    expect($log.warn.calls.argsFor(1)).toEqual(['popover-html-popup is now deprecated. Use uib-popover-html-popup instead.']);
+  }));
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/popover/test/popover-template.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/popover/test/popover-template.spec.js
new file mode 100644
index 0000000..347f869
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/popover/test/popover-template.spec.js
@@ -0,0 +1,180 @@
+describe('popover template', function() {
+  var elm,
+      elmBody,
+      scope,
+      elmScope,
+      tooltipScope;
+
+  // load the popover code
+  beforeEach(module('ui.bootstrap.popover'));
+
+  // load the template
+  beforeEach(module('template/popover/popover.html'));
+  beforeEach(module('template/popover/popover-template.html'));
+
+  beforeEach(inject(function($templateCache) {
+    $templateCache.put('myUrl', [200, '<span>{{ myTemplateText }}</span>', {}]);
+  }));
+
+  beforeEach(inject(function($rootScope, $compile) {
+    elmBody = angular.element(
+      '<div><span uib-popover-template="templateUrl">Selector Text</span></div>'
+    );
+
+    scope = $rootScope;
+    $compile(elmBody)(scope);
+    scope.templateUrl = 'myUrl';
+
+    scope.$digest();
+    elm = elmBody.find('span');
+    elmScope = elm.scope();
+    tooltipScope = elmScope.$$childTail;
+  }));
+
+  it('should open on click', inject(function() {
+    elm.trigger('click');
+    tooltipScope.$digest();
+    expect(tooltipScope.isOpen).toBe(true);
+
+    expect(elmBody.children().length ).toBe(2);
+  }));
+
+  it('should not open on click if templateUrl is empty', inject(function() {
+    scope.templateUrl = null;
+    scope.$digest();
+
+    elm.trigger('click');
+    tooltipScope.$digest();
+    expect(tooltipScope.isOpen).toBe(false);
+
+    expect(elmBody.children().length).toBe(1);
+  }));
+
+  it('should show updated text', inject(function() {
+    scope.myTemplateText = 'some text';
+
+    elm.trigger('click');
+    tooltipScope.$digest();
+    expect(tooltipScope.isOpen).toBe(true);
+
+    scope.$digest();
+    expect(elmBody.children().eq(1).text().trim()).toBe('some text');
+
+    scope.myTemplateText = 'new text';
+    scope.$digest();
+
+    expect(elmBody.children().eq(1).text().trim()).toBe('new text');
+  }));
+
+  it('should hide popover when template becomes empty', inject(function($timeout) {
+    elm.trigger('click');
+    tooltipScope.$digest();
+    expect(tooltipScope.isOpen).toBe(true);
+
+    scope.templateUrl = '';
+    scope.$digest();
+
+    expect(tooltipScope.isOpen).toBe(false);
+
+    $timeout.flush();
+    expect(elmBody.children().length).toBe(1);
+  }));
+
+  describe('supports options', function() {
+    describe('placement', function() {
+      it('can specify an alternative, valid placement', inject(function($compile) {
+        elmBody = angular.element(
+          '<div><span uib-popover-template="templateUrl" popover-placement="left">Trigger</span></div>'
+        );
+        $compile(elmBody)(scope);
+        scope.$digest();
+        elm = elmBody.find('span');
+        elmScope = elm.scope();
+        tooltipScope = elmScope.$$childTail;
+
+        elm.trigger('click');
+        tooltipScope.$digest();
+        expect(tooltipScope.isOpen).toBe(true);
+
+        expect(elmBody.children().length).toBe(2);
+        var ttipElement = elmBody.find('div.popover');
+        expect(ttipElement).toHaveClass('left');
+      }));
+
+    });
+
+    describe('class', function() {
+      it('can specify a custom class', inject(function($compile) {
+        elmBody = angular.element(
+          '<div><span uib-popover-template="templateUrl" popover-class="custom">Trigger</span></div>'
+        );
+        $compile(elmBody)(scope);
+        scope.$digest();
+        elm = elmBody.find('span');
+        elmScope = elm.scope();
+        tooltipScope = elmScope.$$childTail;
+
+        elm.trigger('click');
+        tooltipScope.$digest();
+        expect(tooltipScope.isOpen).toBe(true);
+
+        expect(elmBody.children().length).toBe(2);
+        var ttipElement = elmBody.find('div.popover');
+        expect(ttipElement).toHaveClass('custom');
+      }));
+    });
+  });
+});
+
+/* Deprecation tests below */
+
+describe('popover template deprecation', function() {
+  beforeEach(module('ui.bootstrap.popover'));
+  beforeEach(module('template/popover/popover.html'));
+  beforeEach(module('template/popover/popover-template.html'));
+
+  var elm, elmBody, elmScope, tooltipScope;
+
+  it('should suppress warning', function() {
+    module(function($provide) {
+      $provide.value('$popoverSuppressWarning', true);
+      $provide.value('$tooltipSuppressWarning', true);
+    });
+
+    inject(function($compile, $log, $rootScope, $templateCache) {
+      spyOn($log, 'warn');
+      $templateCache.put('myUrl', [200, '<span>{{ myTemplateText }}</span>', {}]);
+      $rootScope.templateUrl = 'myUrl';
+
+      elmBody = angular.element('<div><span popover-template="templateUrl">Selector Text</span></div>');
+      $compile(elmBody)($rootScope);
+      $rootScope.$digest();
+      elm = elmBody.find('span');
+      elmScope = elm.scope();
+      tooltipScope = elmScope.$$childTail;
+
+      elm.trigger('click');
+      tooltipScope.$digest();
+      expect($log.warn.calls.count()).toBe(0);
+    });
+  });
+
+  it('should give warning by default', inject(function($compile, $log, $rootScope, $templateCache) {
+    spyOn($log, 'warn');
+    $templateCache.put('myUrl', [200, '<span>{{ myTemplateText }}</span>', {}]);
+    $rootScope.templateUrl = 'myUrl';
+
+    elmBody = angular.element('<div><span popover-template="templateUrl">Selector Text</span></div>');
+    $compile(elmBody)($rootScope);
+    elm = elmBody.find('span');
+    elmScope = elm.scope();
+    tooltipScope = elmScope.$$childTail;
+
+    elm.trigger('click');
+    tooltipScope.$digest();
+
+    expect($log.warn.calls.count()).toBe(2);
+    expect($log.warn.calls.argsFor(0)).toEqual(['$tooltip is now deprecated. Use $uibTooltip instead.']);
+    expect($log.warn.calls.argsFor(1)).toEqual(['popover-template-popup is now deprecated. Use uib-popover-template-popup instead.']);
+  }));
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/popover/test/popover.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/popover/test/popover.spec.js
new file mode 100644
index 0000000..4ba0677
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/popover/test/popover.spec.js
@@ -0,0 +1,226 @@
+describe('popover', function() {
+  var elm,
+      elmBody,
+      scope,
+      elmScope,
+      tooltipScope;
+
+  // load the popover code
+  beforeEach(module('ui.bootstrap.popover'));
+
+  // load the template
+  beforeEach(module('template/popover/popover.html'));
+
+  beforeEach(inject(function($rootScope, $compile) {
+    elmBody = angular.element(
+      '<div><span uib-popover="popover text">Selector Text</span></div>'
+    );
+
+    scope = $rootScope;
+    $compile(elmBody)(scope);
+    scope.$digest();
+    elm = elmBody.find('span');
+    elmScope = elm.scope();
+    tooltipScope = elmScope.$$childTail;
+  }));
+
+  it('should not be open initially', inject(function() {
+    expect(tooltipScope.isOpen).toBe(false);
+
+    // We can only test *that* the popover-popup element wasn't created as the
+    // implementation is templated and replaced.
+    expect(elmBody.children().length).toBe(1);
+  }));
+
+  it('should open on click', inject(function() {
+    elm.trigger('click');
+    tooltipScope.$digest();
+    expect(tooltipScope.isOpen).toBe(true);
+
+    // We can only test *that* the popover-popup element was created as the
+    // implementation is templated and replaced.
+    expect(elmBody.children().length).toBe(2);
+  }));
+
+  it('should close on second click', inject(function() {
+    elm.trigger('click');
+    tooltipScope.$digest();
+    expect(tooltipScope.isOpen).toBe(true);
+    elm.trigger('click');
+    tooltipScope.$digest();
+    expect(tooltipScope.isOpen).toBe(false);
+  }));
+
+  it('should not unbind event handlers created by other directives - issue 456', inject(function($compile) {
+    scope.click = function() {
+      scope.clicked = !scope.clicked;
+    };
+
+    elmBody = angular.element(
+      '<div><input uib-popover="Hello!" ng-click="click()" popover-trigger="mouseenter"/></div>'
+    );
+    $compile(elmBody)(scope);
+    scope.$digest();
+
+    elm = elmBody.find('input');
+
+    elm.trigger('mouseenter');
+    elm.trigger('mouseleave');
+    expect(scope.clicked).toBeFalsy();
+
+    elm.click();
+    expect(scope.clicked).toBeTruthy();
+  }));
+
+  it('should popup with animate class by default', inject(function() {
+    elm.trigger('click');
+    tooltipScope.$digest();
+    expect(tooltipScope.isOpen).toBe(true);
+
+    expect(elmBody.children().eq(1)).toHaveClass('fade');
+  }));
+
+  it('should popup without animate class when animation disabled', inject(function($compile) {
+    elmBody = angular.element(
+      '<div><span uib-popover="popover text" popover-animation="false">Selector Text</span></div>'
+    );
+
+    $compile(elmBody)(scope);
+    scope.$digest();
+    elm = elmBody.find('span');
+    elmScope = elm.scope();
+    tooltipScope = elmScope.$$childTail;
+
+    elm.trigger('click');
+    tooltipScope.$digest();
+    expect(tooltipScope.isOpen).toBe(true);
+    expect(elmBody.children().eq(1)).not.toHaveClass('fade');
+  }));
+
+  describe('supports options', function() {
+    describe('placement', function() {
+      it('can specify an alternative, valid placement', inject(function($compile) {
+        elmBody = angular.element(
+          '<div><span uib-popover="popover text" popover-placement="left">Trigger here</span></div>'
+        );
+        $compile(elmBody)(scope);
+        scope.$digest();
+        elm = elmBody.find('span');
+        elmScope = elm.scope();
+        tooltipScope = elmScope.$$childTail;
+
+        elm.trigger('click');
+        tooltipScope.$digest();
+        expect(tooltipScope.isOpen).toBe(true);
+
+        expect(elmBody.children().length).toBe(2);
+        var ttipElement = elmBody.find('div.popover');
+        expect(ttipElement).toHaveClass('left');
+      }));
+    });
+
+    describe('class', function() {
+      it('can specify a custom class', inject(function($compile) {
+        elmBody = angular.element(
+          '<div><span uib-popover="popover text" popover-class="custom">Trigger here</span></div>'
+        );
+        $compile(elmBody)(scope);
+        scope.$digest();
+        elm = elmBody.find('span');
+        elmScope = elm.scope();
+        tooltipScope = elmScope.$$childTail;
+
+        elm.trigger('click');
+        tooltipScope.$digest();
+        expect(tooltipScope.isOpen).toBe(true);
+
+        expect(elmBody.children().length).toBe(2);
+        var ttipElement = elmBody.find('div.popover');
+        expect(ttipElement).toHaveClass('custom');
+      }));
+    });
+
+    describe('is-open', function() {
+      beforeEach(inject(function ($compile) {
+        scope.isOpen = false;
+        elmBody = angular.element(
+          '<div><span uib-popover="popover text" popover-placement="left" popover-is-open="isOpen">Trigger here</span></div>'
+        );
+        $compile(elmBody)(scope);
+        scope.$digest();
+        elm = elmBody.find('span');
+        elmScope = elm.scope();
+        tooltipScope = elmScope.$$childTail;
+      }));
+
+      it('should show and hide with the controller value', function() {
+        expect(tooltipScope.isOpen).toBe(false);
+        elmScope.isOpen = true;
+        elmScope.$digest();
+        expect(tooltipScope.isOpen).toBe(true);
+        elmScope.isOpen = false;
+        elmScope.$digest();
+        expect(tooltipScope.isOpen).toBe(false);
+      });
+
+      it('should update the controller value', function() {
+        elm.trigger('click');
+        tooltipScope.$digest();
+        expect(elmScope.isOpen).toBe(true);
+        elm.trigger('click');
+        tooltipScope.$digest();
+        expect(elmScope.isOpen).toBe(false);
+      });
+    });
+  });
+});
+
+/* Deprecation tests below */
+
+describe('popover deprecation', function() {
+  beforeEach(module('ui.bootstrap.popover'));
+  beforeEach(module('template/popover/popover.html'));
+
+  var elm, elmBody, elmScope, tooltipScope;
+
+  it('should suppress warning', function() {
+    module(function($provide) {
+      $provide.value('$popoverSuppressWarning', true);
+      $provide.value('$tooltipSuppressWarning', true);
+    });
+
+    inject(function($compile, $log, $rootScope) {
+      spyOn($log, 'warn');
+
+      elmBody = angular.element('<div><span popover="popover text">Selector Text</span></div>');
+      $compile(elmBody)($rootScope);
+      $rootScope.$digest();
+      elm = elmBody.find('span');
+      elmScope = elm.scope();
+      tooltipScope = elmScope.$$childTail;
+
+      elm.trigger('click');
+      tooltipScope.$digest();
+
+      expect($log.warn.calls.count()).toBe(0);
+    });
+  });
+
+  it('should give warning by default', inject(function($compile, $log, $rootScope) {
+    spyOn($log, 'warn');
+
+    elmBody = angular.element('<div><span popover="popover text">Selector Text</span></div>');
+    $compile(elmBody)($rootScope);
+    $rootScope.$digest();
+    elm = elmBody.find('span');
+    elmScope = elm.scope();
+    tooltipScope = elmScope.$$childTail;
+
+    elm.trigger('click');
+    tooltipScope.$digest();
+
+    expect($log.warn.calls.count()).toBe(2);
+    expect($log.warn.calls.argsFor(0)).toEqual(['$tooltip is now deprecated. Use $uibTooltip instead.']);
+    expect($log.warn.calls.argsFor(1)).toEqual(['popover-popup is now deprecated. Use uib-popover-popup instead.']);
+  }));
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/position/position.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/position/position.js
new file mode 100644
index 0000000..0502053
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/position/position.js
@@ -0,0 +1,164 @@
+angular.module('ui.bootstrap.position', [])
+
+/**
+ * A set of utility methods that can be use to retrieve position of DOM elements.
+ * It is meant to be used where we need to absolute-position DOM elements in
+ * relation to other, existing elements (this is the case for tooltips, popovers,
+ * typeahead suggestions etc.).
+ */
+  .factory('$uibPosition', ['$document', '$window', function($document, $window) {
+    function getStyle(el, cssprop) {
+      if (el.currentStyle) { //IE
+        return el.currentStyle[cssprop];
+      } else if ($window.getComputedStyle) {
+        return $window.getComputedStyle(el)[cssprop];
+      }
+      // finally try and get inline style
+      return el.style[cssprop];
+    }
+
+    /**
+     * Checks if a given element is statically positioned
+     * @param element - raw DOM element
+     */
+    function isStaticPositioned(element) {
+      return (getStyle(element, 'position') || 'static' ) === 'static';
+    }
+
+    /**
+     * returns the closest, non-statically positioned parentOffset of a given element
+     * @param element
+     */
+    var parentOffsetEl = function(element) {
+      var docDomEl = $document[0];
+      var offsetParent = element.offsetParent || docDomEl;
+      while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) {
+        offsetParent = offsetParent.offsetParent;
+      }
+      return offsetParent || docDomEl;
+    };
+
+    return {
+      /**
+       * Provides read-only equivalent of jQuery's position function:
+       * http://api.jquery.com/position/
+       */
+      position: function(element) {
+        var elBCR = this.offset(element);
+        var offsetParentBCR = { top: 0, left: 0 };
+        var offsetParentEl = parentOffsetEl(element[0]);
+        if (offsetParentEl != $document[0]) {
+          offsetParentBCR = this.offset(angular.element(offsetParentEl));
+          offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
+          offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
+        }
+
+        var boundingClientRect = element[0].getBoundingClientRect();
+        return {
+          width: boundingClientRect.width || element.prop('offsetWidth'),
+          height: boundingClientRect.height || element.prop('offsetHeight'),
+          top: elBCR.top - offsetParentBCR.top,
+          left: elBCR.left - offsetParentBCR.left
+        };
+      },
+
+      /**
+       * Provides read-only equivalent of jQuery's offset function:
+       * http://api.jquery.com/offset/
+       */
+      offset: function(element) {
+        var boundingClientRect = element[0].getBoundingClientRect();
+        return {
+          width: boundingClientRect.width || element.prop('offsetWidth'),
+          height: boundingClientRect.height || element.prop('offsetHeight'),
+          top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop),
+          left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)
+        };
+      },
+
+      /**
+       * Provides coordinates for the targetEl in relation to hostEl
+       */
+      positionElements: function(hostEl, targetEl, positionStr, appendToBody) {
+        var positionStrParts = positionStr.split('-');
+        var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center';
+
+        var hostElPos,
+          targetElWidth,
+          targetElHeight,
+          targetElPos;
+
+        hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl);
+
+        targetElWidth = targetEl.prop('offsetWidth');
+        targetElHeight = targetEl.prop('offsetHeight');
+
+        var shiftWidth = {
+          center: function() {
+            return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2;
+          },
+          left: function() {
+            return hostElPos.left;
+          },
+          right: function() {
+            return hostElPos.left + hostElPos.width;
+          }
+        };
+
+        var shiftHeight = {
+          center: function() {
+            return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2;
+          },
+          top: function() {
+            return hostElPos.top;
+          },
+          bottom: function() {
+            return hostElPos.top + hostElPos.height;
+          }
+        };
+
+        switch (pos0) {
+          case 'right':
+            targetElPos = {
+              top: shiftHeight[pos1](),
+              left: shiftWidth[pos0]()
+            };
+            break;
+          case 'left':
+            targetElPos = {
+              top: shiftHeight[pos1](),
+              left: hostElPos.left - targetElWidth
+            };
+            break;
+          case 'bottom':
+            targetElPos = {
+              top: shiftHeight[pos0](),
+              left: shiftWidth[pos1]()
+            };
+            break;
+          default:
+            targetElPos = {
+              top: hostElPos.top - targetElHeight,
+              left: shiftWidth[pos1]()
+            };
+            break;
+        }
+
+        return targetElPos;
+      }
+    };
+  }]);
+
+/* Deprecated position below */
+
+angular.module('ui.bootstrap.position')
+
+.value('$positionSuppressWarning', false)
+
+.service('$position', ['$log', '$positionSuppressWarning', '$uibPosition', function($log, $positionSuppressWarning, $uibPosition) {
+  if (!$positionSuppressWarning) {
+    $log.warn('$position is now deprecated. Use $uibPosition instead.');
+  }
+
+  angular.extend(this, $uibPosition);
+}]);
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/position/test/position.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/position/test/position.spec.js
new file mode 100644
index 0000000..9b0f5a0
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/position/test/position.spec.js
@@ -0,0 +1,164 @@
+describe('position elements', function () {
+  var TargetElMock = function(width, height) {
+    this.width = width;
+    this.height = height;
+
+    this.prop = function(propName) {
+      return propName === 'offsetWidth' ? width : height;
+    };
+  };
+
+  var $position;
+
+  beforeEach(module('ui.bootstrap.position'));
+  beforeEach(inject(function($uibPosition) {
+    $position = $uibPosition;
+  }));
+  beforeEach(function () {
+    jasmine.addMatchers({
+      toBePositionedAt: function(util, customEqualityTesters) {
+        return {
+          compare: function(actual, top, left) {
+            var result = {
+              pass: util.equals(actual.top, top, customEqualityTesters) &&
+                      util.equals(actual.left, left, customEqualityTesters)
+            };
+
+            if (result.pass) {
+              result.message = 'Expected "('  + actual.top + ', ' + actual.left +  ')" not to be positioned at (' + top + ', ' + left + ')';
+            } else {
+              result.message = 'Expected "('  + actual.top + ', ' + actual.left +  ')" to be positioned at (' + top + ', ' + left + ')';
+            }
+
+            return result;
+          }
+        };
+      }
+    });
+  });
+
+  describe('append-to-body: false', function() {
+    beforeEach(function() {
+      //mock position info normally queried from the DOM
+      $position.position = function() {
+        return {
+          width: 20,
+          height: 20,
+          top: 100,
+          left: 100
+        };
+      };
+    });
+
+    it('should position element on top-center by default', function() {
+      expect($position.positionElements({}, new TargetElMock(10, 10), 'other')).toBePositionedAt(90, 105);
+      expect($position.positionElements({}, new TargetElMock(10, 10), 'top')).toBePositionedAt(90, 105);
+      expect($position.positionElements({}, new TargetElMock(10, 10), 'top-center')).toBePositionedAt(90, 105);
+    });
+
+    it('should position on top-left', function() {
+      expect($position.positionElements({}, new TargetElMock(10, 10), 'top-left')).toBePositionedAt(90, 100);
+    });
+
+    it('should position on top-right', function() {
+      expect($position.positionElements({}, new TargetElMock(10, 10), 'top-right')).toBePositionedAt(90, 120);
+    });
+
+    it('should position elements on bottom-center when "bottom" specified', function() {
+      expect($position.positionElements({}, new TargetElMock(10, 10), 'bottom')).toBePositionedAt(120, 105);
+      expect($position.positionElements({}, new TargetElMock(10, 10), 'bottom-center')).toBePositionedAt(120, 105);
+    });
+
+    it('should position elements on bottom-left', function() {
+      expect($position.positionElements({}, new TargetElMock(10, 10), 'bottom-left')).toBePositionedAt(120, 100);
+    });
+
+    it('should position elements on bottom-right', function() {
+      expect($position.positionElements({}, new TargetElMock(10, 10), 'bottom-right')).toBePositionedAt(120, 120);
+    });
+
+    it('should position elements on left-center when "left" specified', function() {
+      expect($position.positionElements({}, new TargetElMock(10, 10), 'left')).toBePositionedAt(105, 90);
+      expect($position.positionElements({}, new TargetElMock(10, 10), 'left-center')).toBePositionedAt(105, 90);
+    });
+
+    it('should position elements on left-top when "left-top" specified', function() {
+      expect($position.positionElements({}, new TargetElMock(10, 10), 'left-top')).toBePositionedAt(100, 90);
+    });
+
+    it('should position elements on left-bottom when "left-bottom" specified', function() {
+      expect($position.positionElements({}, new TargetElMock(10, 10), 'left-bottom')).toBePositionedAt(120, 90);
+    });
+
+    it('should position elements on right-center when "right" specified', function() {
+      expect($position.positionElements({}, new TargetElMock(10, 10), 'right')).toBePositionedAt(105, 120);
+      expect($position.positionElements({}, new TargetElMock(10, 10), 'right-center')).toBePositionedAt(105, 120);
+    });
+
+    it('should position elements on right-top when "right-top" specified', function() {
+      expect($position.positionElements({}, new TargetElMock(10, 10), 'right-top')).toBePositionedAt(100, 120);
+    });
+
+    it('should position elements on right-top when "right-top" specified', function() {
+      expect($position.positionElements({}, new TargetElMock(10, 10), 'right-bottom')).toBePositionedAt(120, 120);
+    });
+  });
+});
+
+/* Deprecation tests below */
+
+describe('position deprecation', function() {
+  var TargetElMock = function(width, height) {
+    this.width = width;
+    this.height = height;
+
+    this.prop = function(propName) {
+      return propName === 'offsetWidth' ? width : height;
+    };
+  };
+  beforeEach(module('ui.bootstrap.position'));
+
+  it('should suppress warning', function() {
+    module(function($provide) {
+      $provide.value('$positionSuppressWarning', true);
+    });
+
+    inject(function($log, $position) {
+      spyOn($log, 'warn');
+      //mock position info normally queried from the DOM
+      $position.position = function() {
+        return {
+          width: 20,
+          height: 20,
+          top: 100,
+          left: 100
+        };
+      };
+
+      $position.positionElements({}, new TargetElMock(10, 10), 'other');
+
+      expect($log.warn.calls.count()).toBe(0);
+    });
+  });
+
+  it('should give warning by default', inject(function($log) {
+    spyOn($log, 'warn');
+
+    inject(function($position) {
+      //mock position info normally queried from the DOM
+      $position.position = function() {
+        return {
+          width: 20,
+          height: 20,
+          top: 100,
+          left: 100
+        };
+      };
+
+      $position.positionElements({}, new TargetElMock(10, 10), 'other');
+
+      expect($log.warn.calls.count()).toBe(1);
+      expect($log.warn.calls.argsFor(0)).toEqual(['$position is now deprecated. Use $uibPosition instead.']);
+    });
+  }));
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/position/test/test.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/position/test/test.html
new file mode 100644
index 0000000..3ce65b8
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/position/test/test.html
@@ -0,0 +1,144 @@
+<!DOCTYPE html>
+<html lang="en" ng-app="position">
+<head>
+    <meta charset="utf-8">
+    <script src="../../../node_modules/angular/angular.js"></script>
+    <script src="../position.js"></script>
+    <style type="text/css">
+        .container {
+            border: 1px solid red;
+        }
+
+        .container-relative {
+            border: 1px solid red;
+            position: relative;
+        }
+
+        .content {
+            border: 5px solid #808080;
+            background-color: dodgerblue;
+            width: 200px;
+        }
+
+        .positioned {
+            border: 5px solid #808080;
+            background-color: green;
+            position: absolute;
+        }
+    </style>
+    <script type="text/javascript">
+        angular.module('position', ['ui.bootstrap.position']).directive('position', function ($compile, $position) {
+            return {
+                link: function (scope, element, attrs) {
+
+                    var positionedEl = angular.element('<div class="positioned">Positioned</div>');
+                    var elPosition = $position.position(element);
+                    elPosition.left += elPosition.width;
+
+                    positionedEl.css({left: elPosition.left + 'px', top: elPosition.top + 'px'});
+
+                    if (attrs['position'] === 'body') {
+                        angular.element(document.getElementsByTagName('body')[0]).after($compile(positionedEl)(scope));
+                    } else {
+                        element.after($compile(positionedEl)(scope));
+                    }
+                }
+            };
+        });
+    </script>
+</head>
+<body class="container">
+<h3>Within body</h3>
+
+<div class="content" position>Content</div>
+
+<h3>Within statically positioned DIV</h3>
+
+<div class="container">
+    <div class="content" position>Content</div>
+</div>
+
+<h3>Within relative-positioned DIV - position specified in CSS</h3>
+
+<div class="container-relative">
+    <div class="content" position>Content</div>
+</div>
+
+<h3>Within relative-positioned DIV</h3>
+
+<div style="position: relative; left: 200px" class="container">
+    <div class="content" position>Content</div>
+</div>
+
+<h3>Within absolute-positioned DIV</h3>
+
+<div style="position: absolute; left: 400px; top: 400px" class="container">
+    <div class="content" position>Content - absolute</div>
+</div>
+
+<h3>Within overflowing absolute-positioned DIV</h3>
+<div class="container" style="height: 50px; overflow: scroll;overflow-x: hidden; position: absolute;">
+    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur non velit nulla. Suspendisse sit amet tempus diam. Sed at ultricies neque. Suspendisse id felis a sem placerat ornare. Donec auctor, purus at molestie tempor, arcu enim molestie lacus, ac imperdiet massa urna eu massa. Praesent velit tellus, scelerisque a fermentum ut, ornare in diam. Phasellus egestas molestie feugiat. Vivamus sit amet viverra metus.
+    <div class="content" position>Content absolute overflow</div>
+</div>
+
+<br>
+<br>
+<br>
+
+<h3>Next to a float element</h3>
+
+<div class="container">
+    <div class="content" style="float: right" position>Content</div>
+</div>
+
+<h3>Within a table</h3>
+<table class="container">
+    <tr>
+        <td>Some other content</td>
+        <td>
+            <div class="content" position>Content</div>
+        </td>
+    </tr>
+</table>
+
+<h3>Within a table that is inside a relative-positioned DIV</h3>
+
+<div style="position: relative; left: 200px" class="container">
+    <table class="container">
+        <tr>
+            <td>Some other content</td>
+            <td>
+                <div class="content" position>Content</div>
+            </td>
+        </tr>
+    </table>
+</div>
+
+<h3>Inside svg</h3>
+
+<svg height="300px" width="300px">
+  <rect x="0" y="0" height="300" width="300" fill="aliceblue"></rect>
+  <rect x="50" y="50" height="200" width="200" position="body" fill="white" stroke="red">
+  </rect>
+</svg>
+
+
+<h3>Inside looong text</h3>
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur non velit nulla. Suspendisse sit amet tempus diam. Sed at ultricies neque. Suspendisse id felis a sem placerat ornare. Donec auctor, purus at molestie tempor, arcu enim molestie lacus, ac imperdiet massa urna eu massa. Praesent velit tellus, scelerisque a fermentum ut, ornare in diam. Phasellus egestas molestie feugiat. Vivamus sit amet viverra metus.</p>
+<p>Etiam ultricies odio commodo erat ullamcorper sodales. Nullam ac dui ac libero dictum mollis. Quisque convallis adipiscing facilisis. In nec nisi velit, id auctor lectus. Cras interdum urna non felis lacinia vulputate. Integer dignissim, mi aliquam gravida auctor, massa odio cursus lorem, eu ultrices eros nisl tempus diam. Maecenas tristique pellentesque nisi sed adipiscing. Aenean hendrerit sapien quis arcu lobortis vitae pulvinar ante volutpat. Morbi consectetur erat eu lacus facilisis eu ullamcorper orci euismod. Quisque diam dui, interdum in suscipit et, fringilla non justo. Pellentesque non nibh odio. Proin sit amet massa sem.</p>
+<p>Nam in urna erat, at congue nisi. Donec eu tellus lorem, sed facilisis tellus. Aliquam suscipit faucibus ipsum, at hendrerit metus interdum at. Integer et eros ac lacus vulputate sagittis quis quis erat. Suspendisse consectetur vehicula purus vitae imperdiet. Suspendisse in augue magna, quis imperdiet enim. Nullam non diam ac erat auctor bibendum. Praesent ante mauris, egestas sit amet molestie sed, tristique at lorem. Nam at mi ac nisl venenatis semper nec eget mi. Pellentesque a lectus ac leo feugiat suscipit. Quisque tristique dui nec urna placerat a viverra mi iaculis. Ut et tellus et turpis sagittis iaculis nec eu magna. Sed quis nunc non arcu tincidunt ultricies viverra id mauris.</p>
+<p>Curabitur luctus rutrum ultricies. Aenean ut rutrum orci. Sed molestie lorem in leo cursus id feugiat nisi scelerisque. Maecenas pulvinar neque nec lacus feugiat dictum. Donec viverra felis nec nisi mollis feugiat. Phasellus vehicula, ligula at mattis porttitor, sapien urna hendrerit quam, at fringilla nisl quam vel elit. In eu lacus ligula. Praesent eget gravida nisl. Suspendisse velit diam, pellentesque a tempus quis, vestibulum vel leo.</p>
+<p>Maecenas feugiat ultrices laoreet. Sed congue posuere diam ac faucibus. Pellentesque eget leo ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed nec quam eu tellus sagittis cursus a sit amet eros. Mauris sit amet orci at orci vulputate commodo ut ut nunc. Etiam sagittis erat ut nisi ultricies feugiat. Morbi sed eros nisi. Cras vitae augue in risus aliquet commodo non id est.</p>
+<div class="content" position>HERE</div>
+<p>Maecenas laoreet nisi pretium elit bibendum eget tempor nunc aliquet. Vivamus interdum nisi sit amet tortor fermentum congue. Suspendisse at posuere erat. Aliquam hendrerit ultricies nunc non adipiscing. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Duis molestie viverra nulla a aliquet. Nullam non eros vel sem vehicula suscipit. Ut sit amet arcu ac tortor dignissim viverra in a ligula.</p>
+
+
+<div style="position: fixed; bottom: 0" class="container">
+    <h3>Within fixed div</h3>
+    <div class="content" position>Content</div>
+</div>
+
+
+</body>
+</html>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/progressbar/docs/demo.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/progressbar/docs/demo.html
new file mode 100644
index 0000000..3073be7
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/progressbar/docs/demo.html
@@ -0,0 +1,24 @@
+<div ng-controller="ProgressDemoCtrl">
+
+    <h3>Static</h3>
+    <div class="row">
+        <div class="col-sm-4"><uib-progressbar value="55"></uib-progressbar></div>
+        <div class="col-sm-4"><uib-progressbar class="progress-striped" value="22" type="warning">22%</uib-progressbar></div>
+        <div class="col-sm-4"><uib-progressbar class="progress-striped active" max="200" value="166" type="danger"><i>166 / 200</i></uib-progressbar></div>
+    </div>
+
+    <hr />
+    <h3>Dynamic <button type="button" class="btn btn-sm btn-primary" ng-click="random()">Randomize</button></h3>
+    <uib-progressbar max="max" value="dynamic"><span style="color:white; white-space:nowrap;">{{dynamic}} / {{max}}</span></uib-progressbar>
+    
+    <small><em>No animation</em></small>
+    <uib-progressbar animate="false" value="dynamic" type="success"><b>{{dynamic}}%</b></uib-progressbar>
+
+    <small><em>Object (changes type based on value)</em></small>
+    <uib-progressbar class="progress-striped active" value="dynamic" type="{{type}}">{{type}} <i ng-show="showWarning">!!! Watch out !!!</i></uib-progressbar>
+    
+    <hr />
+    <h3>Stacked <button type="button" class="btn btn-sm btn-primary" ng-click="randomStacked()">Randomize</button></h3>
+    <uib-progress><uib-bar ng-repeat="bar in stacked track by $index" value="bar.value" type="{{bar.type}}"><span ng-hide="bar.value < 5">{{bar.value}}%</span></uib-bar></uib-progress>
+
+</div>
\ No newline at end of file
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/progressbar/docs/demo.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/progressbar/docs/demo.js
new file mode 100644
index 0000000..c06561c
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/progressbar/docs/demo.js
@@ -0,0 +1,38 @@
+angular.module('ui.bootstrap.demo').controller('ProgressDemoCtrl', function ($scope) {
+  $scope.max = 200;
+
+  $scope.random = function() {
+    var value = Math.floor((Math.random() * 100) + 1);
+    var type;
+
+    if (value < 25) {
+      type = 'success';
+    } else if (value < 50) {
+      type = 'info';
+    } else if (value < 75) {
+      type = 'warning';
+    } else {
+      type = 'danger';
+    }
+
+    $scope.showWarning = (type === 'danger' || type === 'warning');
+
+    $scope.dynamic = value;
+    $scope.type = type;
+  };
+  $scope.random();
+
+  $scope.randomStacked = function() {
+    $scope.stacked = [];
+    var types = ['success', 'info', 'warning', 'danger'];
+
+    for (var i = 0, n = Math.floor((Math.random() * 4) + 1); i < n; i++) {
+        var index = Math.floor((Math.random() * 4));
+        $scope.stacked.push({
+          value: Math.floor((Math.random() * 30) + 1),
+          type: types[index]
+        });
+    }
+  };
+  $scope.randomStacked();
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/progressbar/docs/readme.md b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/progressbar/docs/readme.md
new file mode 100644
index 0000000..45a4fe5
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/progressbar/docs/readme.md
@@ -0,0 +1,32 @@
+A progress bar directive that is focused on providing feedback on the progress of a workflow or action.
+
+It supports multiple (stacked) bars into the same `<uib-progress>` element or a single `<progressbar>` element with optional `max` attribute and transition animations.
+
+### Settings ###
+
+#### `<uib-progressbar>` ####
+
+ * `value` <i class="glyphicon glyphicon-eye-open"></i>
+ 	:
+ 	The current value of progress completed.
+
+ * `type`
+ 	_(Default: null)_ :
+ 	Style type. Possible values are 'success', 'warning' etc.
+
+ * `max`
+ 	_(Default: 100)_ :
+ 	A number that specifies the total value of bars that is required.
+
+ * `animate`
+ 	_(Default: true)_ :
+ 	Whether bars use transitions to achieve the width change.
+
+ * `title`
+  _(Default: progressbar)_ :
+  Title to use as label (for accessibility)
+
+### Stacked ###
+
+Place multiple `<uib-bar>`s into the same `<uib-progress>` element to stack them.
+`<uib-progress>` supports `max`, `animate`, and `title` &  `<uib-bar>` supports  `value`, `title`, and `type` attributes.
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/progressbar/progressbar.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/progressbar/progressbar.js
new file mode 100644
index 0000000..366aadb
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/progressbar/progressbar.js
@@ -0,0 +1,224 @@
+angular.module('ui.bootstrap.progressbar', [])
+
+.constant('uibProgressConfig', {
+  animate: true,
+  max: 100
+})
+
+.controller('UibProgressController', ['$scope', '$attrs', 'uibProgressConfig', function($scope, $attrs, progressConfig) {
+  var self = this,
+      animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
+
+  this.bars = [];
+  $scope.max = angular.isDefined($scope.max) ? $scope.max : progressConfig.max;
+
+  this.addBar = function(bar, element, attrs) {
+    if (!animate) {
+      element.css({'transition': 'none'});
+    }
+
+    this.bars.push(bar);
+
+    bar.max = $scope.max;
+    bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar';
+
+    bar.$watch('value', function(value) {
+      bar.recalculatePercentage();
+    });
+
+    bar.recalculatePercentage = function() {
+      var totalPercentage = self.bars.reduce(function(total, bar) {
+        bar.percent = +(100 * bar.value / bar.max).toFixed(2);
+        return total + bar.percent;
+      }, 0);
+
+      if (totalPercentage > 100) {
+        bar.percent -= totalPercentage - 100;
+      }
+    };
+
+    bar.$on('$destroy', function() {
+      element = null;
+      self.removeBar(bar);
+    });
+  };
+
+  this.removeBar = function(bar) {
+    this.bars.splice(this.bars.indexOf(bar), 1);
+    this.bars.forEach(function (bar) {
+      bar.recalculatePercentage();
+    });
+  };
+
+  $scope.$watch('max', function(max) {
+    self.bars.forEach(function(bar) {
+      bar.max = $scope.max;
+      bar.recalculatePercentage();
+    });
+  });
+}])
+
+.directive('uibProgress', function() {
+  return {
+    replace: true,
+    transclude: true,
+    controller: 'UibProgressController',
+    require: 'uibProgress',
+    scope: {
+      max: '=?'
+    },
+    templateUrl: 'template/progressbar/progress.html'
+  };
+})
+
+.directive('uibBar', function() {
+  return {
+    replace: true,
+    transclude: true,
+    require: '^uibProgress',
+    scope: {
+      value: '=',
+      type: '@'
+    },
+    templateUrl: 'template/progressbar/bar.html',
+    link: function(scope, element, attrs, progressCtrl) {
+      progressCtrl.addBar(scope, element, attrs);
+    }
+  };
+})
+
+.directive('uibProgressbar', function() {
+  return {
+    replace: true,
+    transclude: true,
+    controller: 'UibProgressController',
+    scope: {
+      value: '=',
+      max: '=?',
+      type: '@'
+    },
+    templateUrl: 'template/progressbar/progressbar.html',
+    link: function(scope, element, attrs, progressCtrl) {
+      progressCtrl.addBar(scope, angular.element(element.children()[0]), {title: attrs.title});
+    }
+  };
+});
+
+/* Deprecated progressbar below */
+
+angular.module('ui.bootstrap.progressbar')
+
+.value('$progressSuppressWarning', false)
+
+.controller('ProgressController', ['$scope', '$attrs', 'uibProgressConfig', '$log', '$progressSuppressWarning', function($scope, $attrs, progressConfig, $log, $progressSuppressWarning) {
+  if (!$progressSuppressWarning) {
+    $log.warn('ProgressController is now deprecated. Use UibProgressController instead.');
+  }
+
+  var self = this,
+    animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
+
+  this.bars = [];
+  $scope.max = angular.isDefined($scope.max) ? $scope.max : progressConfig.max;
+
+  this.addBar = function(bar, element, attrs) {
+    if (!animate) {
+      element.css({'transition': 'none'});
+    }
+
+    this.bars.push(bar);
+
+    bar.max = $scope.max;
+    bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar';
+
+    bar.$watch('value', function(value) {
+      bar.recalculatePercentage();
+    });
+
+    bar.recalculatePercentage = function() {
+      bar.percent = +(100 * bar.value / bar.max).toFixed(2);
+
+      var totalPercentage = self.bars.reduce(function(total, bar) {
+        return total + bar.percent;
+      }, 0);
+
+      if (totalPercentage > 100) {
+        bar.percent -= totalPercentage - 100;
+      }
+    };
+
+    bar.$on('$destroy', function() {
+      element = null;
+      self.removeBar(bar);
+    });
+  };
+
+  this.removeBar = function(bar) {
+    this.bars.splice(this.bars.indexOf(bar), 1);
+  };
+
+  $scope.$watch('max', function(max) {
+    self.bars.forEach(function(bar) {
+      bar.max = $scope.max;
+      bar.recalculatePercentage();
+    });
+  });
+}])
+
+.directive('progress', ['$log', '$progressSuppressWarning', function($log, $progressSuppressWarning) {
+  return {
+    replace: true,
+    transclude: true,
+    controller: 'ProgressController',
+    require: 'progress',
+    scope: {
+      max: '=?',
+      title: '@?'
+    },
+    templateUrl: 'template/progressbar/progress.html',
+    link: function() {
+      if (!$progressSuppressWarning) {
+        $log.warn('progress is now deprecated. Use uib-progress instead.');
+      }
+    }
+  };
+}])
+
+.directive('bar', ['$log', '$progressSuppressWarning', function($log, $progressSuppressWarning) {
+  return {
+    replace: true,
+    transclude: true,
+    require: '^progress',
+    scope: {
+      value: '=',
+      type: '@'
+    },
+    templateUrl: 'template/progressbar/bar.html',
+    link: function(scope, element, attrs, progressCtrl) {
+      if (!$progressSuppressWarning) {
+        $log.warn('bar is now deprecated. Use uib-bar instead.');
+      }
+      progressCtrl.addBar(scope, element);
+    }
+  };
+}])
+
+.directive('progressbar', ['$log', '$progressSuppressWarning', function($log, $progressSuppressWarning) {
+  return {
+    replace: true,
+    transclude: true,
+    controller: 'ProgressController',
+    scope: {
+      value: '=',
+      max: '=?',
+      type: '@'
+    },
+    templateUrl: 'template/progressbar/progressbar.html',
+    link: function(scope, element, attrs, progressCtrl) {
+      if (!$progressSuppressWarning) {
+        $log.warn('progressbar is now deprecated. Use uib-progressbar instead.');
+      }
+      progressCtrl.addBar(scope, angular.element(element.children()[0]), {title: attrs.title});
+    }
+  };
+}]);
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/progressbar/test/progressbar.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/progressbar/test/progressbar.spec.js
new file mode 100644
index 0000000..8c487b2
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/progressbar/test/progressbar.spec.js
@@ -0,0 +1,421 @@
+describe('progressbar directive', function() {
+  var $rootScope, $compile, element;
+  beforeEach(module('ui.bootstrap.progressbar'));
+  beforeEach(module('template/progressbar/progressbar.html', 'template/progressbar/progress.html', 'template/progressbar/bar.html'));
+  beforeEach(inject(function(_$compile_, _$rootScope_) {
+    $compile = _$compile_;
+    $rootScope = _$rootScope_;
+    $rootScope.value = 22;
+    element = $compile('<uib-progressbar animate="false" value="value" title="foo">{{value}} %</uib-progressbar>')($rootScope);
+    $rootScope.$digest();
+  }));
+
+  var BAR_CLASS = 'progress-bar';
+
+  function getBar(i) {
+    return element.children().eq(i);
+  }
+
+  it('has a "progress" css class', function() {
+    expect(element).toHaveClass('progress');
+  });
+
+  it('contains one child element with "bar" css class', function() {
+    expect(element.children().length).toBe(1);
+    expect(getBar(0)).toHaveClass(BAR_CLASS);
+  });
+
+  it('has a "bar" element with expected width', function() {
+    expect(getBar(0).css('width')).toBe('22%');
+  });
+
+  it('has the appropriate aria markup', function() {
+    var bar = getBar(0);
+    expect(bar.attr('role')).toBe('progressbar');
+    expect(bar.attr('aria-valuemin')).toBe('0');
+    expect(bar.attr('aria-valuemax')).toBe('100');
+    expect(bar.attr('aria-valuenow')).toBe('22');
+    expect(bar.attr('aria-valuetext')).toBe('22%');
+    expect(bar.attr('aria-labelledby')).toBe('foo');
+  });
+
+  it('has the default aria-labelledby value of `progressbar`', function() {
+    element = $compile('<uib-progressbar animate="false" value="value">{{value}} %</uib-progressbar>')($rootScope);
+    $rootScope.$digest();
+    var bar = getBar(0);
+    expect(bar.attr('aria-labelledby')).toBe('progressbar');
+  });
+
+  it('transcludes "bar" text', function() {
+    expect(getBar(0).text()).toBe('22 %');
+  });
+
+  it('it should be possible to add additional classes', function() {
+    element = $compile('<progress class="progress-striped active" max="200"><bar class="pizza"></bar></progress>')($rootScope);
+    $rootScope.$digest();
+
+    expect(element).toHaveClass('progress-striped');
+    expect(element).toHaveClass('active');
+
+    expect(getBar(0)).toHaveClass('pizza');
+  });
+
+  it('adjusts the "bar" width and aria when value changes', function() {
+    $rootScope.value = 60;
+    $rootScope.$digest();
+
+    var bar = getBar(0);
+    expect(bar.css('width')).toBe('60%');
+
+    expect(bar.attr('aria-valuemin')).toBe('0');
+    expect(bar.attr('aria-valuemax')).toBe('100');
+    expect(bar.attr('aria-valuenow')).toBe('60');
+    expect(bar.attr('aria-valuetext')).toBe('60%');
+  });
+
+  it('allows fractional "bar" width values, rounded to two places', function() {
+    $rootScope.value = 5.625;
+    $rootScope.$digest();
+    expect(getBar(0).css('width')).toBe('5.63%');
+
+    $rootScope.value = 1.3;
+    $rootScope.$digest();
+    expect(getBar(0).css('width')).toBe('1.3%');
+  });
+
+  it('does not include decimals in aria values', function() {
+    $rootScope.value = 50.34;
+    $rootScope.$digest();
+
+    var bar = getBar(0);
+    expect(bar.css('width')).toBe('50.34%');
+    expect(bar.attr('aria-valuetext')).toBe('50%');
+  });
+
+  describe('"max" attribute', function() {
+    beforeEach(inject(function() {
+      $rootScope.max = 200;
+      element = $compile('<uib-progressbar max="max" animate="false" value="value">{{value}}/{{max}}</uib-progressbar>')($rootScope);
+      $rootScope.$digest();
+    }));
+
+    it('has the appropriate aria markup', function() {
+      expect(getBar(0).attr('aria-valuemax')).toBe('200');
+    });
+
+    it('adjusts the "bar" width', function() {
+      expect(element.children().eq(0).css('width')).toBe('11%');
+    });
+
+    it('adjusts the "bar" width when value changes', function() {
+      $rootScope.value = 60;
+      $rootScope.$digest();
+      expect(getBar(0).css('width')).toBe('30%');
+
+      $rootScope.value += 12;
+      $rootScope.$digest();
+      expect(getBar(0).css('width')).toBe('36%');
+
+      $rootScope.value = 0;
+      $rootScope.$digest();
+      expect(getBar(0).css('width')).toBe('0%');
+    });
+
+    it('transcludes "bar" text', function() {
+      expect(getBar(0).text()).toBe('22/200');
+    });
+
+    it('adjusts the valuemax when it changes', function() {
+      expect(getBar(0).attr('aria-valuemax')).toBe('200');
+      $rootScope.max = 300;
+      $rootScope.$digest();
+      expect(getBar(0).attr('aria-valuemax')).toBe('300');
+    });
+  });
+
+  describe('"type" attribute', function() {
+    beforeEach(inject(function() {
+      $rootScope.type = 'success';
+      element = $compile('<uib-progressbar value="value" type="{{type}}"></uib-progressbar>')($rootScope);
+      $rootScope.$digest();
+    }));
+
+    it('should use correct classes', function() {
+      expect(getBar(0)).toHaveClass(BAR_CLASS);
+      expect(getBar(0)).toHaveClass(BAR_CLASS + '-success');
+    });
+
+    it('should change classes if type changed', function() {
+      $rootScope.type = 'warning';
+      $rootScope.value += 1;
+      $rootScope.$digest();
+
+      var barEl = getBar(0);
+      expect(barEl).toHaveClass(BAR_CLASS);
+      expect(barEl).not.toHaveClass(BAR_CLASS + '-success');
+      expect(barEl).toHaveClass(BAR_CLASS + '-warning');
+    });
+  });
+
+  describe('stacked', function() {
+    beforeEach(inject(function() {
+      $rootScope.objects = [
+        { value: 10, title: 'foo', type: 'success' },
+        { value: 50, title: 'bar', type: 'warning' },
+        { value: 20, title: 'baz' }
+      ];
+      element = $compile('<uib-progress animate="false"><uib-bar ng-repeat="o in objects" value="o.value" type="{{o.type}}" title="{{o.title}}">{{o.value}}</uib-bar></uib-progress>')($rootScope);
+      $rootScope.$digest();
+    }));
+
+    it('contains the right number of bars', function() {
+      expect(element.children().length).toBe(3);
+      for (var i = 0; i < 3; i++) {
+        expect(getBar(i)).toHaveClass(BAR_CLASS);
+      }
+    });
+
+    it('renders each bar with the appropriate width', function() {
+      expect(getBar(0).css('width')).toBe('10%');
+      expect(getBar(1).css('width')).toBe('50%');
+      expect(getBar(2).css('width')).toBe('20%');
+    });
+
+    it('uses correct classes', function() {
+      expect(getBar(0)).toHaveClass(BAR_CLASS + '-success');
+      expect(getBar(0)).not.toHaveClass(BAR_CLASS + '-warning');
+
+      expect(getBar(1)).not.toHaveClass(BAR_CLASS + '-success');
+      expect(getBar(1)).toHaveClass(BAR_CLASS + '-warning');
+
+      expect(getBar(2)).not.toHaveClass(BAR_CLASS + '-success');
+      expect(getBar(2)).not.toHaveClass(BAR_CLASS + '-warning');
+    });
+
+    it('should change classes if type changed', function() {
+      $rootScope.objects = [
+        { value: 20, type: 'warning' },
+        { value: 50 },
+        { value: 30, type: 'info' }
+      ];
+      $rootScope.$digest();
+
+      expect(getBar(0)).not.toHaveClass(BAR_CLASS + '-success');
+      expect(getBar(0)).toHaveClass(BAR_CLASS + '-warning');
+
+      expect(getBar(1)).not.toHaveClass(BAR_CLASS + '-success');
+      expect(getBar(1)).not.toHaveClass(BAR_CLASS + '-warning');
+
+      expect(getBar(2)).toHaveClass(BAR_CLASS + '-info');
+      expect(getBar(2)).not.toHaveClass(BAR_CLASS + '-success');
+      expect(getBar(2)).not.toHaveClass(BAR_CLASS + '-warning');
+    });
+
+    it('should change classes if type changed', function() {
+      $rootScope.objects = [
+        { value: 70, type: 'info' }
+      ];
+      $rootScope.$digest();
+
+      expect(element.children().length).toBe(1);
+
+      expect(getBar(0)).toHaveClass(BAR_CLASS + '-info');
+      expect(getBar(0)).not.toHaveClass(BAR_CLASS + '-success');
+      expect(getBar(0)).not.toHaveClass(BAR_CLASS + '-warning');
+    });
+
+    it('should have the correct aria markup', function() {
+      expect(getBar(0).attr('aria-valuenow')).toBe('10');
+      expect(getBar(0).attr('aria-valuemin')).toBe('0');
+      expect(getBar(0).attr('aria-valuemax')).toBe('100');
+      expect(getBar(0).attr('aria-valuetext')).toBe('10%');
+      expect(getBar(0).attr('aria-labelledby')).toBe('foo');
+
+      expect(getBar(1).attr('aria-valuenow')).toBe('50');
+      expect(getBar(1).attr('aria-valuemin')).toBe('0');
+      expect(getBar(1).attr('aria-valuemax')).toBe('100');
+      expect(getBar(1).attr('aria-valuetext')).toBe('50%');
+      expect(getBar(1).attr('aria-labelledby')).toBe('bar');
+
+      expect(getBar(2).attr('aria-valuenow')).toBe('20');
+      expect(getBar(2).attr('aria-valuemin')).toBe('0');
+      expect(getBar(2).attr('aria-valuemax')).toBe('100');
+      expect(getBar(2).attr('aria-valuetext')).toBe('20%');
+      expect(getBar(2).attr('aria-labelledby')).toBe('baz');
+    });
+
+    it('should default to `progressbar`', function() {
+      $rootScope.objects = [
+        { value: 10, title: 'foo', type: 'success' },
+        { value: 50, title: 'bar', type: 'warning' },
+        { value: 20, title: 'baz' }
+      ];
+      element = $compile('<uib-progress animate="false"><uib-bar ng-repeat="o in objects" value="o.value" type="{{o.type}}">{{o.value}}</uib-bar></uib-progress>')($rootScope);
+      $rootScope.$digest();
+
+      expect(getBar(0).attr('aria-labelledby')).toBe('progressbar');
+      expect(getBar(1).attr('aria-labelledby')).toBe('progressbar');
+      expect(getBar(2).attr('aria-labelledby')).toBe('progressbar');
+    });
+
+    describe('"max" attribute', function() {
+      beforeEach(inject(function() {
+        $rootScope.max = 200;
+        element = $compile('<uib-progress max="max" animate="false"><uib-bar ng-repeat="o in objects" value="o.value">{{o.value}}/{{max}}</uib-bar></uib-progress>')($rootScope);
+        $rootScope.$digest();
+      }));
+
+      it('has the appropriate aria markup', function() {
+        expect(getBar(0).attr('aria-valuemax')).toBe('200');
+      });
+
+      it('adjusts the "bar" width when it changes', function() {
+        expect(getBar(0).css('width')).toBe('5%');
+        $rootScope.max = 250;
+        $rootScope.$digest();
+        expect(getBar(0).css('width')).toBe('4%');
+      });
+
+      it('adjusts the "bar" width when value changes', function() {
+        $rootScope.objects[0].value = 60;
+        $rootScope.$digest();
+        expect(getBar(0).css('width')).toBe('30%');
+
+        $rootScope.objects[0].value += 12;
+        $rootScope.$digest();
+        expect(getBar(0).css('width')).toBe('36%');
+
+        $rootScope.objects[0].value = 0;
+        $rootScope.$digest();
+        expect(getBar(0).css('width')).toBe('0%');
+      });
+
+      it('transcludes "bar" text', function() {
+        expect(getBar(0).text()).toBe('10/200');
+      });
+
+      it('adjusts the valuemax when it changes', function() {
+        expect(getBar(0).attr('aria-valuemax')).toBe('200');
+        $rootScope.max = 300;
+        $rootScope.$digest();
+        expect(getBar(0).attr('aria-valuemax')).toBe('300');
+      });
+
+      it('should not have a total width over 100%', function() {
+        $rootScope.objects = [
+          { value: 60, type: 'warning' },
+          { value: 103 },
+          { value: 270, type: 'info' }
+        ];
+        $rootScope.max = 433;
+        $rootScope.$digest();
+        var totalWidth = 0;
+        for (var i = 0; i < 3; i++) {
+          totalWidth += parseFloat(getBar(i).css('width'));
+        }
+        expect(totalWidth.toFixed(2)).toBe('100.00');
+      });
+
+      it('should not have a total width over 37.65% when removing bar', function() {
+        $rootScope.objects = [
+          { value: 60, type: 'warning' },
+          { value: 103 },
+          { value: 270, type: 'info' }
+        ];
+        $rootScope.max = 433;
+        $rootScope.$digest();
+        var totalWidth = 0;
+        for (var i = 0; i < 3; i++) {
+          totalWidth += parseFloat(getBar(i).css('width'));
+        }
+        expect(totalWidth.toFixed(2)).toBe('100.00');
+
+        $rootScope.objects.splice(2, 1);
+        $rootScope.$digest();
+        totalWidth = 0;
+        for (i = 0; i < 2; i++) {
+          totalWidth += parseFloat(getBar(i).css('width'));
+        }
+        expect(totalWidth.toFixed(2)).toBe('37.65');
+      });
+    });
+  });
+});
+
+/* Deprecation tests below */
+
+describe('progressbar deprecation', function() {
+  beforeEach(module('ui.bootstrap.progressbar'));
+  beforeEach(module('template/progressbar/progress.html', 'template/progressbar/bar.html', 'template/progressbar/progressbar.html'));
+
+  describe('progress & bar directives', function() {
+    it('should suppress warning', function() {
+      module(function($provide) {
+        $provide.value('$progressSuppressWarning', true);
+      });
+
+      inject(function($compile, $log, $rootScope) {
+        spyOn($log, 'warn');
+
+        $rootScope.objects = [
+          { value: 10, type: 'success' },
+          { value: 50, type: 'warning' },
+          { value: 20 }
+        ];
+        var element = $compile('<progress animate="false"><bar ng-repeat="o in objects" value="o.value" type="{{o.type}}">{{o.value}}</bar></progress>')($rootScope);
+        $rootScope.$digest();
+
+        expect($log.warn.calls.count()).toBe(0);
+      });
+    });
+
+    it('should give warning by default', inject(function($compile, $log, $rootScope) {
+      spyOn($log, 'warn');
+
+      $rootScope.objects = [
+        { value: 10, type: 'success' },
+        { value: 50, type: 'warning' },
+        { value: 20 }
+      ];
+      var element = $compile('<progress animate="false"><bar ng-repeat="o in objects" value="o.value" type="{{o.type}}">{{o.value}}</bar></progress>')($rootScope);
+      $rootScope.$digest();
+
+      expect($log.warn.calls.count()).toBe(5);
+      expect($log.warn.calls.argsFor(0)).toEqual(['ProgressController is now deprecated. Use UibProgressController instead.']);
+      expect($log.warn.calls.argsFor(1)).toEqual(['progress is now deprecated. Use uib-progress instead.']);
+      expect($log.warn.calls.argsFor(2)).toEqual(['bar is now deprecated. Use uib-bar instead.']);
+      expect($log.warn.calls.argsFor(3)).toEqual(['bar is now deprecated. Use uib-bar instead.']);
+      expect($log.warn.calls.argsFor(4)).toEqual(['bar is now deprecated. Use uib-bar instead.']);
+    }));
+  });
+
+  describe('progressbar directive', function() {
+    it('should suppress warning', function() {
+      module(function($provide) {
+        $provide.value('$progressSuppressWarning', true);
+      });
+
+      inject(function($compile, $log, $rootScope) {
+        spyOn($log, 'warn');
+
+        $rootScope.value = 22;
+        var element = $compile('<progressbar animate="false" value="value" title="foo">{{value}} %</progressbar>')($rootScope);
+        $rootScope.$digest();
+
+        expect($log.warn.calls.count()).toBe(0);
+      });
+    });
+
+    it('should give warning by default', inject(function($compile, $log, $rootScope) {
+      spyOn($log, 'warn');
+
+      $rootScope.value = 22;
+      var element = $compile('<progressbar animate="false" value="value" title="foo">{{value}} %</progressbar>')($rootScope);
+      $rootScope.$digest();
+
+      expect($log.warn.calls.count()).toBe(2);
+      expect($log.warn.calls.argsFor(0)).toEqual(['ProgressController is now deprecated. Use UibProgressController instead.']);
+      expect($log.warn.calls.argsFor(1)).toEqual(['progressbar is now deprecated. Use uib-progressbar instead.']);
+    }));
+  });
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/rating/docs/demo.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/rating/docs/demo.html
new file mode 100644
index 0000000..1ad69f7
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/rating/docs/demo.html
@@ -0,0 +1,15 @@
+<div ng-controller="RatingDemoCtrl">
+    <h4>Default</h4>
+    <uib-rating ng-model="rate" max="max" readonly="isReadonly" on-hover="hoveringOver(value)" on-leave="overStar = null" titles="['one','two','three']" aria-labelledby="default-rating"></uib-rating>
+    <span class="label" ng-class="{'label-warning': percent<30, 'label-info': percent>=30 && percent<70, 'label-success': percent>=70}" ng-show="overStar && !isReadonly">{{percent}}%</span>
+
+    <pre style="margin:15px 0;">Rate: <b>{{rate}}</b> - Readonly is: <i>{{isReadonly}}</i> - Hovering over: <b>{{overStar || "none"}}</b></pre>
+
+    <button type="button" class="btn btn-sm btn-danger" ng-click="rate = 0" ng-disabled="isReadonly">Clear</button>
+    <button type="button" class="btn btn-sm btn-default" ng-click="isReadonly = ! isReadonly">Toggle Readonly</button>
+    <hr />
+
+    <h4>Custom icons</h4>
+    <div ng-init="x = 5"><uib-rating ng-model="x" max="15" state-on="'glyphicon-ok-sign'" state-off="'glyphicon-ok-circle'" aria-labelledby="custom-icons-1"></uib-rating> <b>(<i>Rate:</i> {{x}})</b></div>
+    <div ng-init="y = 2"><uib-rating ng-model="y" rating-states="ratingStates" aria-labelledby="custom-icons-2"></uib-rating> <b>(<i>Rate:</i> {{y}})</b></div>
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/rating/docs/demo.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/rating/docs/demo.js
new file mode 100644
index 0000000..af6b4e9
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/rating/docs/demo.js
@@ -0,0 +1,18 @@
+angular.module('ui.bootstrap.demo').controller('RatingDemoCtrl', function ($scope) {
+  $scope.rate = 7;
+  $scope.max = 10;
+  $scope.isReadonly = false;
+
+  $scope.hoveringOver = function(value) {
+    $scope.overStar = value;
+    $scope.percent = 100 * (value / $scope.max);
+  };
+
+  $scope.ratingStates = [
+    {stateOn: 'glyphicon-ok-sign', stateOff: 'glyphicon-ok-circle'},
+    {stateOn: 'glyphicon-star', stateOff: 'glyphicon-star-empty'},
+    {stateOn: 'glyphicon-heart', stateOff: 'glyphicon-ban-circle'},
+    {stateOn: 'glyphicon-heart'},
+    {stateOff: 'glyphicon-off'}
+  ];
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/rating/docs/readme.md b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/rating/docs/readme.md
new file mode 100644
index 0000000..45cbeb9
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/rating/docs/readme.md
@@ -0,0 +1,41 @@
+Rating directive that will take care of visualising a star rating bar.
+
+### Settings ###
+
+#### `<uib-rating>` ####
+
+ * `ng-model` <i class="glyphicon glyphicon-eye-open"></i>
+ 	:
+ 	The current rate.
+
+ * `max`
+ 	_(Defaults: 5)_ :
+ 	Changes the number of icons.
+
+ * `readonly` <i class="icon-eye-open"></i>
+ 	_(Defaults: false)_ :
+ 	Prevent user's interaction.
+
+ * `titles`
+ 	_(Defaults: ["one", "two", "three", "four", "five"])_ :
+ 	An array of Strings defining titles for all icons 
+
+ * `on-hover(value)`
+ 	:
+ 	An optional expression called when user's mouse is over a particular icon.
+
+ * `on-leave()`
+ 	:
+ 	An optional expression called when user's mouse leaves the control altogether.
+
+ * `state-on`
+ 	_(Defaults: null)_ :
+ 	A variable used in template to specify the state (class, src, etc) for selected icons.
+
+ * `state-off`
+ 	_(Defaults: null)_ :
+ 	A variable used in template to specify the state for unselected icons.
+
+ * `rating-states`
+ 	_(Defaults: null)_ :
+ 	An array of objects defining properties for all icons. In default template, `stateOn` & `stateOff` property is used to specify the icon's class.
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/rating/rating.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/rating/rating.js
new file mode 100644
index 0000000..b32a1a3
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/rating/rating.js
@@ -0,0 +1,137 @@
+angular.module('ui.bootstrap.rating', [])
+
+.constant('uibRatingConfig', {
+  max: 5,
+  stateOn: null,
+  stateOff: null,
+  titles : ['one', 'two', 'three', 'four', 'five']
+})
+
+.controller('UibRatingController', ['$scope', '$attrs', 'uibRatingConfig', function($scope, $attrs, ratingConfig) {
+  var ngModelCtrl  = { $setViewValue: angular.noop };
+
+  this.init = function(ngModelCtrl_) {
+    ngModelCtrl = ngModelCtrl_;
+    ngModelCtrl.$render = this.render;
+
+    ngModelCtrl.$formatters.push(function(value) {
+      if (angular.isNumber(value) && value << 0 !== value) {
+        value = Math.round(value);
+      }
+      return value;
+    });
+
+    this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
+    this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
+    var tmpTitles = angular.isDefined($attrs.titles)  ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles ;
+    this.titles = angular.isArray(tmpTitles) && tmpTitles.length > 0 ?
+      tmpTitles : ratingConfig.titles;
+
+    var ratingStates = angular.isDefined($attrs.ratingStates) ?
+      $scope.$parent.$eval($attrs.ratingStates) :
+      new Array(angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max);
+    $scope.range = this.buildTemplateObjects(ratingStates);
+  };
+
+  this.buildTemplateObjects = function(states) {
+    for (var i = 0, n = states.length; i < n; i++) {
+      states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff, title: this.getTitle(i) }, states[i]);
+    }
+    return states;
+  };
+
+  this.getTitle = function(index) {
+    if (index >= this.titles.length) {
+      return index + 1;
+    } else {
+      return this.titles[index];
+    }
+  };
+
+  $scope.rate = function(value) {
+    if (!$scope.readonly && value >= 0 && value <= $scope.range.length) {
+      ngModelCtrl.$setViewValue(ngModelCtrl.$viewValue === value ? 0 : value);
+      ngModelCtrl.$render();
+    }
+  };
+
+  $scope.enter = function(value) {
+    if (!$scope.readonly) {
+      $scope.value = value;
+    }
+    $scope.onHover({value: value});
+  };
+
+  $scope.reset = function() {
+    $scope.value = ngModelCtrl.$viewValue;
+    $scope.onLeave();
+  };
+
+  $scope.onKeydown = function(evt) {
+    if (/(37|38|39|40)/.test(evt.which)) {
+      evt.preventDefault();
+      evt.stopPropagation();
+      $scope.rate($scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1));
+    }
+  };
+
+  this.render = function() {
+    $scope.value = ngModelCtrl.$viewValue;
+  };
+}])
+
+.directive('uibRating', function() {
+  return {
+    require: ['uibRating', 'ngModel'],
+    scope: {
+      readonly: '=?',
+      onHover: '&',
+      onLeave: '&'
+    },
+    controller: 'UibRatingController',
+    templateUrl: 'template/rating/rating.html',
+    replace: true,
+    link: function(scope, element, attrs, ctrls) {
+      var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1];
+      ratingCtrl.init(ngModelCtrl);
+    }
+  };
+});
+
+/* Deprecated rating below */
+
+angular.module('ui.bootstrap.rating')
+
+.value('$ratingSuppressWarning', false)
+
+.controller('RatingController', ['$scope', '$attrs', '$controller', '$log', '$ratingSuppressWarning', function($scope, $attrs, $controller, $log, $ratingSuppressWarning) {
+  if (!$ratingSuppressWarning) {
+    $log.warn('RatingController is now deprecated. Use UibRatingController instead.');
+  }
+
+  angular.extend(this, $controller('UibRatingController', {
+    $scope: $scope,
+    $attrs: $attrs
+  }));
+}])
+
+.directive('rating', ['$log', '$ratingSuppressWarning', function($log, $ratingSuppressWarning) {
+  return {
+    require: ['rating', 'ngModel'],
+    scope: {
+      readonly: '=?',
+      onHover: '&',
+      onLeave: '&'
+    },
+    controller: 'RatingController',
+    templateUrl: 'template/rating/rating.html',
+    replace: true,
+    link: function(scope, element, attrs, ctrls) {
+      if (!$ratingSuppressWarning) {
+        $log.warn('rating is now deprecated. Use uib-rating instead.');
+      }
+      var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1];
+      ratingCtrl.init(ngModelCtrl);
+    }
+  };
+}]);
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/rating/test/rating.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/rating/test/rating.spec.js
new file mode 100644
index 0000000..e51bc69
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/rating/test/rating.spec.js
@@ -0,0 +1,361 @@
+describe('rating directive', function() {
+  var $rootScope, $compile, element;
+  beforeEach(module('ui.bootstrap.rating'));
+  beforeEach(module('template/rating/rating.html'));
+  beforeEach(inject(function(_$compile_, _$rootScope_) {
+    $compile = _$compile_;
+    $rootScope = _$rootScope_;
+    $rootScope.rate = 3;
+    element = $compile('<uib-rating ng-model="rate"></uib-rating>')($rootScope);
+    $rootScope.$digest();
+  }));
+
+  function getStars() {
+    return element.find('i');
+  }
+
+  function getStar(number) {
+    return getStars().eq(number - 1);
+  }
+
+  function getState(classOn, classOff) {
+    var stars = getStars();
+    var state = [];
+    for (var i = 0, n = stars.length; i < n; i++) {
+      state.push((stars.eq(i).hasClass(classOn || 'glyphicon-star') && ! stars.eq(i).hasClass(classOff || 'glyphicon-star-empty')));
+    }
+    return state;
+  }
+
+  function getTitles() {
+    var stars = getStars();
+    return stars.toArray().map(function(star) {
+      return angular.element(star).attr('title');
+    });
+  }
+  
+  function triggerKeyDown(keyCode) {
+    var e = $.Event('keydown');
+    e.which = keyCode;
+    element.trigger(e);
+  }
+
+  it('contains the default number of icons', function() {
+    expect(getStars().length).toBe(5);
+    expect(element.attr('aria-valuemax')).toBe('5');
+  });
+
+  it('initializes the default star icons as selected', function() {
+    expect(getState()).toEqual([true, true, true, false, false]);
+    expect(element.attr('aria-valuenow')).toBe('3');
+  });
+
+  it('handles correctly the click event', function() {
+    getStar(2).click();
+    $rootScope.$digest();
+    expect(getState()).toEqual([true, true, false, false, false]);
+    expect($rootScope.rate).toBe(2);
+    expect(element.attr('aria-valuenow')).toBe('2');
+
+    getStar(5).click();
+    $rootScope.$digest();
+    expect(getState()).toEqual([true, true, true, true, true]);
+    expect($rootScope.rate).toBe(5);
+    expect(element.attr('aria-valuenow')).toBe('5');
+
+    getStar(5).click();
+    $rootScope.$digest();
+    expect(getState()).toEqual([false, false, false, false, false]);
+    expect($rootScope.rate).toBe(0);
+    expect(element.attr('aria-valuenow')).toBe('0');
+  });
+
+  it('handles correctly the hover event', function() {
+    getStar(2).trigger('mouseover');
+    $rootScope.$digest();
+    expect(getState()).toEqual([true, true, false, false, false]);
+    expect($rootScope.rate).toBe(3);
+
+    getStar(5).trigger('mouseover');
+    $rootScope.$digest();
+    expect(getState()).toEqual([true, true, true, true, true]);
+    expect($rootScope.rate).toBe(3);
+
+    element.trigger('mouseout');
+    expect(getState()).toEqual([true, true, true, false, false]);
+    expect($rootScope.rate).toBe(3);
+  });
+
+  it('rounds off the number of stars shown with decimal values', function() {
+    $rootScope.rate = 2.1;
+    $rootScope.$digest();
+
+    expect(getState()).toEqual([true, true, false, false, false]);
+    expect(element.attr('aria-valuenow')).toBe('2');
+
+    $rootScope.rate = 2.5;
+    $rootScope.$digest();
+
+    expect(getState()).toEqual([true, true, true, false, false]);
+    expect(element.attr('aria-valuenow')).toBe('3');
+  });
+
+  it('changes the number of selected icons when value changes', function() {
+    $rootScope.rate = 2;
+    $rootScope.$digest();
+
+    expect(getState()).toEqual([true, true, false, false, false]);
+    expect(element.attr('aria-valuenow')).toBe('2');
+  });
+
+  it('shows different number of icons when `max` attribute is set', function() {
+    element = $compile('<uib-rating ng-model="rate" max="7"></uib-rating>')($rootScope);
+    $rootScope.$digest();
+
+    expect(getStars().length).toBe(7);
+    expect(element.attr('aria-valuemax')).toBe('7');
+  });
+
+  it('shows different number of icons when `max` attribute is from scope variable', function() {
+    $rootScope.max = 15;
+    element = $compile('<uib-rating ng-model="rate" max="max"></uib-rating>')($rootScope);
+    $rootScope.$digest();
+    expect(getStars().length).toBe(15);
+    expect(element.attr('aria-valuemax')).toBe('15');
+  });
+
+  it('handles readonly attribute', function() {
+    $rootScope.isReadonly = true;
+    element = $compile('<uib-rating ng-model="rate" readonly="isReadonly"></uib-rating>')($rootScope);
+    $rootScope.$digest();
+
+    expect(getState()).toEqual([true, true, true, false, false]);
+
+    var star5 = getStar(5);
+    star5.trigger('mouseover');
+    $rootScope.$digest();
+    expect(getState()).toEqual([true, true, true, false, false]);
+
+    $rootScope.isReadonly = false;
+    $rootScope.$digest();
+
+    star5.trigger('mouseover');
+    $rootScope.$digest();
+    expect(getState()).toEqual([true, true, true, true, true]);
+  });
+
+  it('should fire onHover', function() {
+    $rootScope.hoveringOver = jasmine.createSpy('hoveringOver');
+    element = $compile('<uib-rating ng-model="rate" on-hover="hoveringOver(value)"></uib-rating>')($rootScope);
+    $rootScope.$digest();
+
+    getStar(3).trigger('mouseover');
+    $rootScope.$digest();
+    expect($rootScope.hoveringOver).toHaveBeenCalledWith(3);
+  });
+
+  it('should fire onLeave', function() {
+    $rootScope.leaving = jasmine.createSpy('leaving');
+    element = $compile('<uib-rating ng-model="rate" on-leave="leaving()"></uib-rating>')($rootScope);
+    $rootScope.$digest();
+
+    element.trigger('mouseleave');
+    $rootScope.$digest();
+    expect($rootScope.leaving).toHaveBeenCalled();
+  });
+
+  describe('keyboard navigation', function() {
+    it('supports arrow keys', function() {
+      triggerKeyDown(38);
+      expect($rootScope.rate).toBe(4);
+
+      triggerKeyDown(37);
+      expect($rootScope.rate).toBe(3);
+      triggerKeyDown(40);
+      expect($rootScope.rate).toBe(2);
+
+      triggerKeyDown(39);
+      expect($rootScope.rate).toBe(3);
+    });
+
+    it('supports only arrow keys', function() {
+      $rootScope.rate = undefined;
+      $rootScope.$digest();
+
+      triggerKeyDown(36);
+      expect($rootScope.rate).toBe(undefined);
+
+      triggerKeyDown(41);
+      expect($rootScope.rate).toBe(undefined);
+    });
+
+    it('can get zero value but not negative', function() {
+      $rootScope.rate = 1;
+      $rootScope.$digest();
+
+      triggerKeyDown(37);
+      expect($rootScope.rate).toBe(0);
+
+      triggerKeyDown(37);
+      expect($rootScope.rate).toBe(0);
+    });
+
+    it('cannot get value above max', function() {
+      $rootScope.rate = 4;
+      $rootScope.$digest();
+
+      triggerKeyDown(38);
+      expect($rootScope.rate).toBe(5);
+
+      triggerKeyDown(38);
+      expect($rootScope.rate).toBe(5);
+    });
+  });
+
+  describe('custom states', function() {
+    beforeEach(inject(function() {
+      $rootScope.classOn = 'icon-ok-sign';
+      $rootScope.classOff = 'icon-ok-circle';
+      element = $compile('<uib-rating ng-model="rate" state-on="classOn" state-off="classOff"></uib-rating>')($rootScope);
+      $rootScope.$digest();
+    }));
+
+    it('changes the default icons', function() {
+      expect(getState($rootScope.classOn, $rootScope.classOff)).toEqual([true, true, true, false, false]);
+    });
+  });
+
+  describe('`rating-states`', function() {
+    beforeEach(inject(function() {
+      $rootScope.states = [
+        {stateOn: 'sign', stateOff: 'circle'},
+        {stateOn: 'heart', stateOff: 'ban'},
+        {stateOn: 'heart'},
+        {stateOff: 'off'}
+      ];
+      element = $compile('<uib-rating ng-model="rate" rating-states="states"></uib-rating>')($rootScope);
+      $rootScope.$digest();
+    }));
+
+    it('should define number of icon elements', function() {
+      expect(getStars().length).toBe(4);
+      expect(element.attr('aria-valuemax')).toBe('4');
+    });
+
+    it('handles each icon', function() {
+      var stars = getStars();
+
+      for (var i = 0; i < stars.length; i++) {
+        var star = stars.eq(i);
+        var state = $rootScope.states[i];
+        var isOn = i < $rootScope.rate;
+
+        expect(star.hasClass(state.stateOn)).toBe(isOn);
+        expect(star.hasClass(state.stateOff)).toBe(!isOn);
+      }
+    });
+  });
+
+  describe('setting uibRatingConfig', function() {
+    var originalConfig = {};
+    beforeEach(inject(function(uibRatingConfig) {
+      $rootScope.rate = 5;
+      angular.extend(originalConfig, uibRatingConfig);
+      uibRatingConfig.max = 10;
+      uibRatingConfig.stateOn = 'on';
+      uibRatingConfig.stateOff = 'off';
+      element = $compile('<uib-rating ng-model="rate"></uib-rating>')($rootScope);
+      $rootScope.$digest();
+    }));
+    afterEach(inject(function(uibRatingConfig) {
+      // return it to the original state
+      angular.extend(uibRatingConfig, originalConfig);
+    }));
+
+    it('should change number of icon elements', function() {
+      expect(getStars().length).toBe(10);
+    });
+
+    it('should change icon states', function() {
+      expect(getState('on', 'off')).toEqual([true, true, true, true, true, false, false, false, false, false]);
+    });
+  });
+  
+  describe('Default title', function() {
+    it('should return the default title for each star', function() {
+      expect(getTitles()).toEqual(['one', 'two', 'three', 'four', 'five']);
+    });
+  });
+  
+  describe('shows different title when `max` attribute is greater than the titles array ', function() {
+    var originalConfig = {};
+    beforeEach(inject(function(uibRatingConfig) {
+      $rootScope.rate = 5;
+      angular.extend(originalConfig, uibRatingConfig);
+      uibRatingConfig.max = 10;
+      element = $compile('<uib-rating ng-model="rate"></uib-rating>')($rootScope);
+      $rootScope.$digest();
+    }));
+    afterEach(inject(function(uibRatingConfig) {
+      // return it to the original state
+      angular.extend(uibRatingConfig, originalConfig);
+    }));
+ 
+   it('should return the default title for each star', function() {
+      expect(getTitles()).toEqual(['one', 'two', 'three', 'four', 'five', '6', '7', '8', '9', '10']);
+    });
+  });
+  
+  describe('shows custom titles ', function() {
+    it('should return the custom title for each star', function() {
+      $rootScope.titles = [44,45,46];
+      element = $compile('<uib-rating ng-model="rate" titles="titles"></uib-rating>')($rootScope);
+      $rootScope.$digest();
+      expect(getTitles()).toEqual(['44', '45', '46', '4', '5']);
+    });
+    it('should return the default title if the custom title is empty', function() {
+      $rootScope.titles = [];
+      element = $compile('<uib-rating ng-model="rate" titles="titles"></uib-rating>')($rootScope);
+      $rootScope.$digest();
+      expect(getTitles()).toEqual(['one', 'two', 'three', 'four', 'five']);
+    });
+   it('should return the default title if the custom title is not an array', function() {
+      element = $compile('<uib-rating ng-model="rate" titles="test"></uib-rating>')($rootScope);
+      $rootScope.$digest();
+      expect(getTitles()).toEqual(['one', 'two', 'three', 'four', 'five']);
+    });
+  });
+});
+
+/* Deprecation tests below */
+
+describe('rating deprecation', function() {
+  beforeEach(module('ui.bootstrap.rating'));
+  beforeEach(module('template/rating/rating.html'));
+
+  it('should suppress warning', function() {
+    module(function($provide) {
+      $provide.value('$ratingSuppressWarning', true);
+    });
+
+    inject(function($compile, $log, $rootScope) {
+      spyOn($log, 'warn');
+
+      var element = $compile('<rating ng-model="rate"></rating>')($rootScope);
+      $rootScope.$digest();
+
+      expect($log.warn.calls.count()).toBe(0);
+    });
+  });
+
+  it('should give warning by default', inject(function($compile, $log, $rootScope) {
+    spyOn($log, 'warn');
+
+    var element = $compile('<rating ng-model="rate"></rating>')($rootScope);
+    $rootScope.$digest();
+
+    expect($log.warn.calls.count()).toBe(2);
+    expect($log.warn.calls.argsFor(0)).toEqual(['RatingController is now deprecated. Use UibRatingController instead.']);
+    expect($log.warn.calls.argsFor(1)).toEqual(['rating is now deprecated. Use uib-rating instead.']);
+  }));
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/stackedMap/stackedMap.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/stackedMap/stackedMap.js
new file mode 100644
index 0000000..bd97fb6
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/stackedMap/stackedMap.js
@@ -0,0 +1,54 @@
+angular.module('ui.bootstrap.stackedMap', [])
+/**
+ * A helper, internal data structure that acts as a map but also allows getting / removing
+ * elements in the LIFO order
+ */
+  .factory('$$stackedMap', function() {
+    return {
+      createNew: function() {
+        var stack = [];
+
+        return {
+          add: function(key, value) {
+            stack.push({
+              key: key,
+              value: value
+            });
+          },
+          get: function(key) {
+            for (var i = 0; i < stack.length; i++) {
+              if (key == stack[i].key) {
+                return stack[i];
+              }
+            }
+          },
+          keys: function() {
+            var keys = [];
+            for (var i = 0; i < stack.length; i++) {
+              keys.push(stack[i].key);
+            }
+            return keys;
+          },
+          top: function() {
+            return stack[stack.length - 1];
+          },
+          remove: function(key) {
+            var idx = -1;
+            for (var i = 0; i < stack.length; i++) {
+              if (key == stack[i].key) {
+                idx = i;
+                break;
+              }
+            }
+            return stack.splice(idx, 1)[0];
+          },
+          removeTop: function() {
+            return stack.splice(stack.length - 1, 1)[0];
+          },
+          length: function() {
+            return stack.length;
+          }
+        };
+      }
+    };
+  });
\ No newline at end of file
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/stackedMap/test/stackedMap.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/stackedMap/test/stackedMap.spec.js
new file mode 100644
index 0000000..62ad5d6
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/stackedMap/test/stackedMap.spec.js
@@ -0,0 +1,52 @@
+describe('stacked map', function() {
+  var stackedMap;
+
+  beforeEach(module('ui.bootstrap.modal'));
+  beforeEach(inject(function ($$stackedMap) {
+    stackedMap = $$stackedMap.createNew();
+  }));
+
+  it('should add and remove objects by key', function() {
+    stackedMap.add('foo', 'foo_value');
+    expect(stackedMap.length()).toEqual(1);
+    expect(stackedMap.get('foo').key).toEqual('foo');
+    expect(stackedMap.get('foo').value).toEqual('foo_value');
+
+    stackedMap.remove('foo');
+    expect(stackedMap.length()).toEqual(0);
+    expect(stackedMap.get('foo')).toBeUndefined();
+  });
+
+  it('should support listing keys', function() {
+    stackedMap.add('foo', 'foo_value');
+    stackedMap.add('bar', 'bar_value');
+
+    expect(stackedMap.keys()).toEqual(['foo', 'bar']);
+  });
+
+  it('should get topmost element', function() {
+    stackedMap.add('foo', 'foo_value');
+    stackedMap.add('bar', 'bar_value');
+    expect(stackedMap.length()).toEqual(2);
+
+    expect(stackedMap.top().key).toEqual('bar');
+    expect(stackedMap.length()).toEqual(2);
+  });
+
+  it('should remove topmost element', function() {
+    stackedMap.add('foo', 'foo_value');
+    stackedMap.add('bar', 'bar_value');
+
+    expect(stackedMap.removeTop().key).toEqual('bar');
+    expect(stackedMap.removeTop().key).toEqual('foo');
+  });
+
+  it('should preserve semantic of an empty stackedMap', function() {
+    expect(stackedMap.length()).toEqual(0);
+    expect(stackedMap.top()).toBeUndefined();
+  });
+
+  it('should ignore removal of non-existing elements', function() {
+    expect(stackedMap.remove('non-existing')).toBeUndefined();
+  });
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tabs/docs/demo.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tabs/docs/demo.html
new file mode 100644
index 0000000..8a848d3
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tabs/docs/demo.html
@@ -0,0 +1,39 @@
+<div ng-controller="TabsDemoCtrl">
+  <p>Select a tab by setting active binding to true:</p>
+  <p>
+    <button type="button" class="btn btn-default btn-sm" ng-click="tabs[0].active = true">Select second tab</button>
+    <button type="button" class="btn btn-default btn-sm" ng-click="tabs[1].active = true">Select third tab</button>
+  </p>
+  <p>
+    <button type="button" class="btn btn-default btn-sm" ng-click="tabs[1].disabled = ! tabs[1].disabled">Enable / Disable third tab</button>
+  </p>
+  <hr />
+
+  <uib-tabset>
+    <uib-tab heading="Static title">Static content</uib-tab>
+    <uib-tab ng-repeat="tab in tabs" heading="{{tab.title}}" active="tab.active" disable="tab.disabled">
+      {{tab.content}}
+    </uib-tab>
+    <uib-tab select="alertMe()">
+      <uib-tab-heading>
+        <i class="glyphicon glyphicon-bell"></i> Alert!
+      </uib-tab-heading>
+      I've got an HTML heading, and a select callback. Pretty cool!
+    </uib-tab>
+  </uib-tabset>
+
+  <hr />
+
+  <uib-tabset vertical="true" type="pills">
+    <uib-tab heading="Vertical 1">Vertical content 1</uib-tab>
+    <uib-tab heading="Vertical 2">Vertical content 2</uib-tab>
+  </uib-tabset>
+
+  <hr />
+
+  <uib-tabset justified="true">
+    <uib-tab heading="Justified">Justified content</uib-tab>
+    <uib-tab heading="SJ">Short Labeled Justified content</uib-tab>
+    <uib-tab heading="Long Justified">Long Labeled Justified content</uib-tab>
+  </uib-tabset>
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tabs/docs/demo.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tabs/docs/demo.js
new file mode 100644
index 0000000..f3d64fd
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tabs/docs/demo.js
@@ -0,0 +1,12 @@
+angular.module('ui.bootstrap.demo').controller('TabsDemoCtrl', function ($scope, $window) {
+  $scope.tabs = [
+    { title:'Dynamic Title 1', content:'Dynamic content 1' },
+    { title:'Dynamic Title 2', content:'Dynamic content 2', disabled: true }
+  ];
+
+  $scope.alertMe = function() {
+    setTimeout(function() {
+      $window.alert('You\'ve selected the alert tab!');
+    });
+  };
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tabs/docs/readme.md b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tabs/docs/readme.md
new file mode 100644
index 0000000..d00461c
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tabs/docs/readme.md
@@ -0,0 +1,40 @@
+AngularJS version of the tabs directive.
+
+### Settings ###
+
+#### `<uib-tabset>` ####
+
+ * `vertical`
+ 	_(Defaults: false)_ :
+ 	Whether tabs appear vertically stacked.
+
+ * `justified`
+ 	_(Defaults: false)_ :
+ 	Whether tabs fill the container and have a consistent width.
+
+ * `type`
+ 	_(Defaults: 'tabs')_ :
+ 	Navigation type. Possible values are 'tabs' and 'pills'.
+
+#### `<uib-tab>` ####
+
+ * `heading` or `<uib-tab-heading>`
+ 	:
+ 	Heading text or HTML markup.
+
+ * `active` <i class="glyphicon glyphicon-eye-open"></i>
+ 	_(Defaults: false)_ :
+ 	Whether tab is currently selected.
+
+ * `disable` <i class="glyphicon glyphicon-eye-open"></i>
+ 	_(Defaults: false)_ :
+ 	Whether tab is clickable and can be activated.
+ 	Note that this was previously the `disabled` attribute, which is now deprecated.
+
+ * `select()`
+ 	_(Defaults: null)_ :
+ 	An optional expression called when tab is activated.
+    
+ * `deselect()`
+ 	_(Defaults: null)_ :
+ 	An optional expression called when tab is deactivated.
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tabs/tabs.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tabs/tabs.js
new file mode 100644
index 0000000..cb68c8d
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tabs/tabs.js
@@ -0,0 +1,439 @@
+
+/**
+ * @ngdoc overview
+ * @name ui.bootstrap.tabs
+ *
+ * @description
+ * AngularJS version of the tabs directive.
+ */
+
+angular.module('ui.bootstrap.tabs', [])
+
+.controller('UibTabsetController', ['$scope', function ($scope) {
+  var ctrl = this,
+      tabs = ctrl.tabs = $scope.tabs = [];
+
+  ctrl.select = function(selectedTab) {
+    angular.forEach(tabs, function(tab) {
+      if (tab.active && tab !== selectedTab) {
+        tab.active = false;
+        tab.onDeselect();
+        selectedTab.selectCalled = false;
+      }
+    });
+    selectedTab.active = true;
+    // only call select if it has not already been called
+    if (!selectedTab.selectCalled) {
+      selectedTab.onSelect();
+      selectedTab.selectCalled = true;
+    }
+  };
+
+  ctrl.addTab = function addTab(tab) {
+    tabs.push(tab);
+    // we can't run the select function on the first tab
+    // since that would select it twice
+    if (tabs.length === 1 && tab.active !== false) {
+      tab.active = true;
+    } else if (tab.active) {
+      ctrl.select(tab);
+    } else {
+      tab.active = false;
+    }
+  };
+
+  ctrl.removeTab = function removeTab(tab) {
+    var index = tabs.indexOf(tab);
+    //Select a new tab if the tab to be removed is selected and not destroyed
+    if (tab.active && tabs.length > 1 && !destroyed) {
+      //If this is the last tab, select the previous tab. else, the next tab.
+      var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1;
+      ctrl.select(tabs[newActiveIndex]);
+    }
+    tabs.splice(index, 1);
+  };
+
+  var destroyed;
+  $scope.$on('$destroy', function() {
+    destroyed = true;
+  });
+}])
+
+/**
+ * @ngdoc directive
+ * @name ui.bootstrap.tabs.directive:tabset
+ * @restrict EA
+ *
+ * @description
+ * Tabset is the outer container for the tabs directive
+ *
+ * @param {boolean=} vertical Whether or not to use vertical styling for the tabs.
+ * @param {boolean=} justified Whether or not to use justified styling for the tabs.
+ *
+ * @example
+<example module="ui.bootstrap">
+  <file name="index.html">
+    <uib-tabset>
+      <uib-tab heading="Tab 1"><b>First</b> Content!</uib-tab>
+      <uib-tab heading="Tab 2"><i>Second</i> Content!</uib-tab>
+    </uib-tabset>
+    <hr />
+    <uib-tabset vertical="true">
+      <uib-tab heading="Vertical Tab 1"><b>First</b> Vertical Content!</uib-tab>
+      <uib-tab heading="Vertical Tab 2"><i>Second</i> Vertical Content!</uib-tab>
+    </uib-tabset>
+    <uib-tabset justified="true">
+      <uib-tab heading="Justified Tab 1"><b>First</b> Justified Content!</uib-tab>
+      <uib-tab heading="Justified Tab 2"><i>Second</i> Justified Content!</uib-tab>
+    </uib-tabset>
+  </file>
+</example>
+ */
+.directive('uibTabset', function() {
+  return {
+    restrict: 'EA',
+    transclude: true,
+    replace: true,
+    scope: {
+      type: '@'
+    },
+    controller: 'UibTabsetController',
+    templateUrl: 'template/tabs/tabset.html',
+    link: function(scope, element, attrs) {
+      scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
+      scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false;
+    }
+  };
+})
+
+/**
+ * @ngdoc directive
+ * @name ui.bootstrap.tabs.directive:tab
+ * @restrict EA
+ *
+ * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}.
+ * @param {string=} select An expression to evaluate when the tab is selected.
+ * @param {boolean=} active A binding, telling whether or not this tab is selected.
+ * @param {boolean=} disabled A binding, telling whether or not this tab is disabled.
+ *
+ * @description
+ * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}.
+ *
+ * @example
+<example module="ui.bootstrap">
+  <file name="index.html">
+    <div ng-controller="TabsDemoCtrl">
+      <button class="btn btn-small" ng-click="items[0].active = true">
+        Select item 1, using active binding
+      </button>
+      <button class="btn btn-small" ng-click="items[1].disabled = !items[1].disabled">
+        Enable/disable item 2, using disabled binding
+      </button>
+      <br />
+      <uib-tabset>
+        <uib-tab heading="Tab 1">First Tab</uib-tab>
+        <uib-tab select="alertMe()">
+          <uib-tab-heading><i class="icon-bell"></i> Alert me!</tab-heading>
+          Second Tab, with alert callback and html heading!
+        </uib-tab>
+        <uib-tab ng-repeat="item in items"
+          heading="{{item.title}}"
+          disabled="item.disabled"
+          active="item.active">
+          {{item.content}}
+        </uib-tab>
+      </uib-tabset>
+    </div>
+  </file>
+  <file name="script.js">
+    function TabsDemoCtrl($scope) {
+      $scope.items = [
+        { title:"Dynamic Title 1", content:"Dynamic Item 0" },
+        { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true }
+      ];
+
+      $scope.alertMe = function() {
+        setTimeout(function() {
+          alert("You've selected the alert tab!");
+        });
+      };
+    };
+  </file>
+</example>
+ */
+
+/**
+ * @ngdoc directive
+ * @name ui.bootstrap.tabs.directive:tabHeading
+ * @restrict EA
+ *
+ * @description
+ * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element.
+ *
+ * @example
+<example module="ui.bootstrap">
+  <file name="index.html">
+    <uib-tabset>
+      <uib-tab>
+        <uib-tab-heading><b>HTML</b> in my titles?!</tab-heading>
+        And some content, too!
+      </uib-tab>
+      <uib-tab>
+        <uib-tab-heading><i class="icon-heart"></i> Icon heading?!?</tab-heading>
+        That's right.
+      </uib-tab>
+    </uib-tabset>
+  </file>
+</example>
+ */
+.directive('uibTab', ['$parse', function($parse) {
+  return {
+    require: '^uibTabset',
+    restrict: 'EA',
+    replace: true,
+    templateUrl: 'template/tabs/tab.html',
+    transclude: true,
+    scope: {
+      active: '=?',
+      heading: '@',
+      onSelect: '&select', //This callback is called in contentHeadingTransclude
+                          //once it inserts the tab's content into the dom
+      onDeselect: '&deselect'
+    },
+    controller: function() {
+      //Empty controller so other directives can require being 'under' a tab
+    },
+    link: function(scope, elm, attrs, tabsetCtrl, transclude) {
+      scope.$watch('active', function(active) {
+        if (active) {
+          tabsetCtrl.select(scope);
+        }
+      });
+
+      scope.disabled = false;
+      if (attrs.disable) {
+        scope.$parent.$watch($parse(attrs.disable), function(value) {
+          scope.disabled = !! value;
+        });
+      }
+
+      scope.select = function() {
+        if (!scope.disabled) {
+          scope.active = true;
+        }
+      };
+
+      tabsetCtrl.addTab(scope);
+      scope.$on('$destroy', function() {
+        tabsetCtrl.removeTab(scope);
+      });
+
+      //We need to transclude later, once the content container is ready.
+      //when this link happens, we're inside a tab heading.
+      scope.$transcludeFn = transclude;
+    }
+  };
+}])
+
+.directive('uibTabHeadingTransclude', function() {
+  return {
+    restrict: 'A',
+    require: ['?^uibTab', '?^tab'], // TODO: change to '^uibTab' after deprecation removal
+    link: function(scope, elm) {
+      scope.$watch('headingElement', function updateHeadingElement(heading) {
+        if (heading) {
+          elm.html('');
+          elm.append(heading);
+        }
+      });
+    }
+  };
+})
+
+.directive('uibTabContentTransclude', function() {
+  return {
+    restrict: 'A',
+    require: ['?^uibTabset', '?^tabset'], // TODO: change to '^uibTabset' after deprecation removal
+    link: function(scope, elm, attrs) {
+      var tab = scope.$eval(attrs.uibTabContentTransclude);
+
+      //Now our tab is ready to be transcluded: both the tab heading area
+      //and the tab content area are loaded.  Transclude 'em both.
+      tab.$transcludeFn(tab.$parent, function(contents) {
+        angular.forEach(contents, function(node) {
+          if (isTabHeading(node)) {
+            //Let tabHeadingTransclude know.
+            tab.headingElement = node;
+          } else {
+            elm.append(node);
+          }
+        });
+      });
+    }
+  };
+
+  function isTabHeading(node) {
+    return node.tagName && (
+      node.hasAttribute('tab-heading') || // TODO: remove after deprecation removal
+      node.hasAttribute('data-tab-heading') || // TODO: remove after deprecation removal
+      node.hasAttribute('x-tab-heading') || // TODO: remove after deprecation removal
+      node.hasAttribute('uib-tab-heading') ||
+      node.hasAttribute('data-uib-tab-heading') ||
+      node.hasAttribute('x-uib-tab-heading') ||
+      node.tagName.toLowerCase() === 'tab-heading' || // TODO: remove after deprecation removal
+      node.tagName.toLowerCase() === 'data-tab-heading' || // TODO: remove after deprecation removal
+      node.tagName.toLowerCase() === 'x-tab-heading' || // TODO: remove after deprecation removal
+      node.tagName.toLowerCase() === 'uib-tab-heading' ||
+      node.tagName.toLowerCase() === 'data-uib-tab-heading' ||
+      node.tagName.toLowerCase() === 'x-uib-tab-heading'
+    );
+  }
+});
+
+/* deprecated tabs below */
+
+angular.module('ui.bootstrap.tabs')
+
+  .value('$tabsSuppressWarning', false)
+
+  .controller('TabsetController', ['$scope', '$controller', '$log', '$tabsSuppressWarning', function($scope, $controller, $log, $tabsSuppressWarning) {
+    if (!$tabsSuppressWarning) {
+      $log.warn('TabsetController is now deprecated. Use UibTabsetController instead.');
+    }
+
+    angular.extend(this, $controller('UibTabsetController', {
+      $scope: $scope
+    }));
+  }])
+
+  .directive('tabset', ['$log', '$tabsSuppressWarning', function($log, $tabsSuppressWarning) {
+    return {
+      restrict: 'EA',
+      transclude: true,
+      replace: true,
+      scope: {
+        type: '@'
+      },
+      controller: 'TabsetController',
+      templateUrl: 'template/tabs/tabset.html',
+      link: function(scope, element, attrs) {
+
+        if (!$tabsSuppressWarning) {
+          $log.warn('tabset is now deprecated. Use uib-tabset instead.');
+        }
+        scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
+        scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false;
+      }
+    };
+  }])
+
+  .directive('tab', ['$parse', '$log', '$tabsSuppressWarning', function($parse, $log, $tabsSuppressWarning) {
+    return {
+      require: '^tabset',
+      restrict: 'EA',
+      replace: true,
+      templateUrl: 'template/tabs/tab.html',
+      transclude: true,
+      scope: {
+        active: '=?',
+        heading: '@',
+        onSelect: '&select', //This callback is called in contentHeadingTransclude
+        //once it inserts the tab's content into the dom
+        onDeselect: '&deselect'
+      },
+      controller: function() {
+        //Empty controller so other directives can require being 'under' a tab
+      },
+      link: function(scope, elm, attrs, tabsetCtrl, transclude) {
+        if (!$tabsSuppressWarning) {
+          $log.warn('tab is now deprecated. Use uib-tab instead.');
+        }
+
+        scope.$watch('active', function(active) {
+          if (active) {
+            tabsetCtrl.select(scope);
+          }
+        });
+
+        scope.disabled = false;
+        if (attrs.disable) {
+          scope.$parent.$watch($parse(attrs.disable), function(value) {
+            scope.disabled = !!value;
+          });
+        }
+
+        scope.select = function() {
+          if (!scope.disabled) {
+            scope.active = true;
+          }
+        };
+
+        tabsetCtrl.addTab(scope);
+        scope.$on('$destroy', function() {
+          tabsetCtrl.removeTab(scope);
+        });
+
+        //We need to transclude later, once the content container is ready.
+        //when this link happens, we're inside a tab heading.
+        scope.$transcludeFn = transclude;
+      }
+    };
+  }])
+
+  .directive('tabHeadingTransclude', ['$log', '$tabsSuppressWarning', function($log, $tabsSuppressWarning) {
+    return {
+      restrict: 'A',
+      require: '^tab',
+      link: function(scope, elm) {
+        if (!$tabsSuppressWarning) {
+          $log.warn('tab-heading-transclude is now deprecated. Use uib-tab-heading-transclude instead.');
+        }
+
+        scope.$watch('headingElement', function updateHeadingElement(heading) {
+          if (heading) {
+            elm.html('');
+            elm.append(heading);
+          }
+        });
+      }
+    };
+  }])
+
+  .directive('tabContentTransclude', ['$log', '$tabsSuppressWarning', function($log, $tabsSuppressWarning) {
+    return {
+      restrict: 'A',
+      require: '^tabset',
+      link: function(scope, elm, attrs) {
+        if (!$tabsSuppressWarning) {
+          $log.warn('tab-content-transclude is now deprecated. Use uib-tab-content-transclude instead.');
+        }
+
+        var tab = scope.$eval(attrs.tabContentTransclude);
+
+        //Now our tab is ready to be transcluded: both the tab heading area
+        //and the tab content area are loaded.  Transclude 'em both.
+        tab.$transcludeFn(tab.$parent, function(contents) {
+          angular.forEach(contents, function(node) {
+            if (isTabHeading(node)) {
+              //Let tabHeadingTransclude know.
+              tab.headingElement = node;
+            }
+            else {
+              elm.append(node);
+            }
+          });
+        });
+      }
+    };
+
+    function isTabHeading(node) {
+      return node.tagName && (
+          node.hasAttribute('tab-heading') ||
+          node.hasAttribute('data-tab-heading') ||
+          node.hasAttribute('x-tab-heading') ||
+          node.tagName.toLowerCase() === 'tab-heading' ||
+          node.tagName.toLowerCase() === 'data-tab-heading' ||
+          node.tagName.toLowerCase() === 'x-tab-heading'
+        );
+    }
+  }]);
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tabs/test/tabs.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tabs/test/tabs.spec.js
new file mode 100644
index 0000000..1de3c25
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tabs/test/tabs.spec.js
@@ -0,0 +1,923 @@
+describe('tabs', function() {
+  var elm, scope;
+
+  beforeEach(module('ui.bootstrap.tabs'));
+  beforeEach(module('template/tabs/tabset.html'));
+  beforeEach(module('template/tabs/tab.html'));
+
+  function titles() {
+    return elm.find('ul.nav-tabs li');
+  }
+  function contents() {
+    return elm.find('div.tab-content div.tab-pane');
+  }
+
+  function expectTitles(titlesArray) {
+    var t = titles();
+    expect(t.length).toEqual(titlesArray.length);
+    for (var i=0; i<t.length; i++) {
+      expect(t.eq(i).text().trim()).toEqual(titlesArray[i]);
+    }
+  }
+
+  function expectContents(contentsArray) {
+    var c = contents();
+    expect(c.length).toEqual(contentsArray.length);
+    for (var i=0; i<c.length; i++) {
+      expect(c.eq(i).text().trim()).toEqual(contentsArray[i]);
+    }
+  }
+
+  describe('basics', function() {
+    beforeEach(inject(function($compile, $rootScope) {
+      scope = $rootScope.$new();
+      scope.first = '1';
+      scope.second = '2';
+      scope.actives = {};
+      scope.selectFirst = jasmine.createSpy();
+      scope.selectSecond = jasmine.createSpy();
+      scope.deselectFirst = jasmine.createSpy();
+      scope.deselectSecond = jasmine.createSpy();
+      elm = $compile([
+        '<uib-tabset class="hello" data-pizza="pepperoni">',
+        '  <uib-tab heading="First Tab {{first}}" active="actives.one" select="selectFirst()" deselect="deselectFirst()">',
+        '    first content is {{first}}',
+        '  </uib-tab>',
+        '  <uib-tab active="actives.two" select="selectSecond()" deselect="deselectSecond()">',
+        '    <uib-tab-heading><b>Second</b> Tab {{second}}</uib-tab-heading>',
+        '    second content is {{second}}',
+        '  </uib-tab>',
+        '</uib-tabset>'
+      ].join('\n'))(scope);
+      scope.$apply();
+      return elm;
+    }));
+
+    it('should pass class and other attributes on to tab template', function() {
+      expect(elm).toHaveClass('hello');
+      expect(elm.attr('data-pizza')).toBe('pepperoni');
+    });
+
+    it('should create clickable titles', function() {
+      var t = titles();
+      expect(t.length).toBe(2);
+      expect(t.find('a').eq(0).text()).toBe('First Tab 1');
+      //It should put the uib-tab-heading element into the 'a' title
+      expect(t.find('a').eq(1).children().is('uib-tab-heading')).toBe(true);
+      expect(t.find('a').eq(1).children().html()).toBe('<b>Second</b> Tab 2');
+    });
+
+    it('should bind tabs content and set first tab active', function() {
+      expectContents(['first content is 1', 'second content is 2']);
+      expect(titles().eq(0)).toHaveClass('active');
+      expect(titles().eq(1)).not.toHaveClass('active');
+      expect(scope.actives.one).toBe(true);
+      expect(scope.actives.two).toBeFalsy();
+    });
+
+    it('should change active on click', function() {
+      titles().eq(1).find('a').click();
+      expect(contents().eq(1)).toHaveClass('active');
+      expect(titles().eq(0)).not.toHaveClass('active');
+      expect(titles().eq(1)).toHaveClass('active');
+      expect(scope.actives.one).toBe(false);
+      expect(scope.actives.two).toBe(true);
+    });
+
+    it('should call select callback on select', function() {
+      titles().eq(1).find('a').click();
+      expect(scope.selectSecond).toHaveBeenCalled();
+      titles().eq(0).find('a').click();
+      expect(scope.selectFirst).toHaveBeenCalled();
+    });
+
+    it('should call deselect callback on deselect', function() {
+      titles().eq(1).find('a').click();
+      titles().eq(0).find('a').click();
+      expect(scope.deselectSecond).toHaveBeenCalled();
+      titles().eq(1).find('a').click();
+      expect(scope.deselectFirst).toHaveBeenCalled();
+    });
+  });
+
+  describe('basics with initial active tab', function() {
+    beforeEach(inject(function($compile, $rootScope) {
+      scope = $rootScope.$new();
+
+      function makeTab(active) {
+        return {
+          active: !!active,
+          select: jasmine.createSpy()
+        };
+      }
+      scope.tabs = [
+        makeTab(), makeTab(), makeTab(true), makeTab()
+      ];
+      elm = $compile([
+        '<uib-tabset>',
+        '  <uib-tab active="tabs[0].active" select="tabs[0].select()">',
+        '  </uib-tab>',
+        '  <uib-tab active="tabs[1].active" select="tabs[1].select()">',
+        '  </uib-tab>',
+        '  <uib-tab active="tabs[2].active" select="tabs[2].select()">',
+        '  </uib-tab>',
+        '  <uib-tab active="tabs[3].active" select="tabs[3].select()">',
+        '  </uib-tab>',
+        '</uib-tabset>'
+      ].join('\n'))(scope);
+      scope.$apply();
+    }));
+
+    function expectTabActive(activeTab) {
+      var _titles = titles();
+      angular.forEach(scope.tabs, function(tab, i) {
+        if (activeTab === tab) {
+          expect(tab.active).toBe(true);
+          //It should only call select ONCE for each select
+          expect(tab.select).toHaveBeenCalled();
+          expect(_titles.eq(i)).toHaveClass('active');
+          expect(contents().eq(i)).toHaveClass('active');
+        } else {
+          expect(tab.active).toBe(false);
+          expect(_titles.eq(i)).not.toHaveClass('active');
+        }
+      });
+    }
+
+    it('should make tab titles and set active tab active', function() {
+      expect(titles().length).toBe(scope.tabs.length);
+      expectTabActive(scope.tabs[2]);
+    });
+  });
+
+  describe('tab callback order', function() {
+    var execOrder;
+    beforeEach(inject(function($compile, $rootScope) {
+      scope = $rootScope.$new();
+      execOrder = [];
+      scope.actives = {};
+
+      scope.execute = function(id) {
+        execOrder.push(id);
+      };
+
+      elm = $compile([
+        '<div>',
+        '  <uib-tabset class="hello" data-pizza="pepperoni">',
+        '    <uib-tab heading="First Tab" active="actives.one" select="execute(\'select1\')" deselect="execute(\'deselect1\')"></uib-tab>',
+        '    <uib-tab select="execute(\'select2\')" deselect="execute(\'deselect2\')"></uib-tab>',
+        '  </uib-tabset>',
+        '</div>'
+      ].join('\n'))(scope);
+      scope.$apply();
+      return elm;
+    }));
+
+    it('should call select  for the first tab', function() {
+      expect(execOrder).toEqual([ 'select1' ]);
+    });
+
+    it('should call deselect, then select', function() {
+      execOrder = [];
+
+      // Select second tab
+      titles().eq(1).find('a').click();
+      expect(execOrder).toEqual([ 'deselect1', 'select2' ]);
+
+      execOrder = [];
+
+      // Select again first tab
+      titles().eq(0).find('a').click();
+      expect(execOrder).toEqual([ 'deselect2', 'select1' ]);
+    });
+  });
+
+  describe('ng-repeat', function() {
+    var $compile, $rootScope;
+    beforeEach(inject(function(_$compile_, _$rootScope_) {
+      $compile = _$compile_;
+      $rootScope = _$rootScope_;
+      scope = $rootScope.$new();
+
+      scope.tabs = [
+        makeTab(), makeTab(), makeTab(true), makeTab()
+      ];
+      elm = $compile([
+        '<uib-tabset>',
+        '  <uib-tab ng-repeat="t in tabs" active="t.active" select="t.select()">',
+        '    <uib-tab-heading><b>heading</b> {{index}}</uib-tab-heading>',
+        '    content {{$index}}',
+        '  </uib-tab>',
+        '</uib-tabset>'
+      ].join('\n'))(scope);
+      scope.$apply();
+    }));
+
+    function makeTab(active) {
+      return {
+        active: !!active,
+        select: jasmine.createSpy()
+      };
+    }
+
+    function titles() {
+      return elm.find('ul.nav-tabs li');
+    }
+    function contents() {
+      return elm.find('div.tab-content div.tab-pane');
+    }
+
+    function expectTabActive(activeTab) {
+      var _titles = titles();
+      angular.forEach(scope.tabs, function(tab, i) {
+        if (activeTab === tab) {
+          expect(tab.active).toBe(true);
+          //It should only call select ONCE for each select
+          expect(tab.select).toHaveBeenCalled();
+          expect(_titles.eq(i)).toHaveClass('active');
+          expect(contents().eq(i).text().trim()).toBe('content ' + i);
+          expect(contents().eq(i)).toHaveClass('active');
+        } else {
+          expect(tab.active).toBe(false);
+          expect(_titles.eq(i)).not.toHaveClass('active');
+        }
+      });
+    }
+
+    it('should make tab titles and set active tab active', function() {
+      expect(titles().length).toBe(scope.tabs.length);
+      expectTabActive(scope.tabs[2]);
+    });
+
+    it('should switch active when clicking', function() {
+      titles().eq(3).find('a').click();
+      expectTabActive(scope.tabs[3]);
+    });
+
+    it('should switch active when setting active=true', function() {
+      scope.$apply('tabs[2].active = true');
+      expectTabActive(scope.tabs[2]);
+    });
+
+    it('should deselect all when no tabs are active', function() {
+      angular.forEach(scope.tabs, function(t) { t.active = false; });
+      scope.$apply();
+      expectTabActive(null);
+      expect(contents().filter('.active').length).toBe(0);
+
+      scope.tabs[2].active = true;
+      scope.$apply();
+      expectTabActive(scope.tabs[2]);
+    });
+
+    it('should not select twice', function() {
+      elm.remove();
+      elm = null;
+      scope = $rootScope.$new();
+
+      scope.tabs = [
+        makeTab(), makeTab(), makeTab(true), makeTab()
+      ];
+      scope.foo = {active: true};
+      scope.select = jasmine.createSpy();
+      elm = $compile([
+        '<uib-tabset>',
+        '  <uib-tab ng-repeat="t in tabs" active="t.active" select="select()">',
+        '    <uib-tab-heading><b>heading</b> {{index}}</uib-tab-heading>',
+        '    content {{$index}}',
+        '  </uib-tab>',
+        '  <uib-tab active="foo.active" select="select()">',
+        '    <uib-tab-heading><b>heading</b> foo</uib-tab-heading>',
+        '    content foo',
+        '  </uib-tab>',
+        '</uib-tabset>'
+      ].join('\n'))(scope);
+      scope.$apply();
+
+      expect(scope.select.calls.count()).toBe(1);
+    });
+  });
+
+  describe('advanced uib-tab-heading element', function() {
+    beforeEach(inject(function($compile, $rootScope, $sce) {
+      scope = $rootScope.$new();
+      scope.myHtml = $sce.trustAsHtml('<b>hello</b>, there!');
+      scope.value = true;
+      elm = $compile([
+        '<uib-tabset>',
+        '  <uib-tab>',
+        '    <uib-tab-heading ng-bind-html="myHtml" ng-show="value"></uib-tab-heading>',
+        '  </uib-tab>',
+        '  <uib-tab><data-uib-tab-heading>1</data-uib-tab-heading></uib-tab>',
+        '  <uib-tab><div data-uib-tab-heading>2</div></uib-tab>',
+        '  <uib-tab><div uib-tab-heading>3</div></uib-tab>',
+        '</uib-tabset>'
+      ].join('\n'))(scope);
+      scope.$apply();
+    }));
+
+    function heading() {
+      return elm.find('ul li a').children();
+    }
+
+    it('should create a heading bound to myHtml', function() {
+      expect(heading().eq(0).html()).toBe('<b>hello</b>, there!');
+    });
+
+    it('should hide and show the heading depending on value', function() {
+      expect(heading().eq(0)).not.toBeHidden();
+      scope.$apply('value = false');
+      expect(heading().eq(0)).toBeHidden();
+      scope.$apply('value = true');
+      expect(heading().eq(0)).not.toBeHidden();
+    });
+
+    it('should have a uib-tab-heading no matter what syntax was used', function() {
+      expect(heading().eq(1).text()).toBe('1');
+      expect(heading().eq(2).text()).toBe('2');
+      expect(heading().eq(3).text()).toBe('3');
+    });
+
+  });
+
+  //Tests that http://git.io/lG6I9Q is fixed
+  describe('tab ordering', function() {
+    beforeEach(inject(function($compile, $rootScope) {
+      scope = $rootScope.$new();
+      scope.tabs = [
+        { title:'Title 1', available:true },
+        { title:'Title 2', available:true },
+        { title:'Title 3', available:true }
+      ];
+      elm = $compile([
+        '<uib-tabset>',
+        '  <!-- a comment -->',
+        '  <div>div that makes troubles</div>',
+        '  <uib-tab heading="first">First Static</uib-tab>',
+        '  <div>another div that may do evil</div>',
+        '  <uib-tab ng-repeat="tab in tabs | filter:tabIsAvailable" active="tab.active" heading="{{tab.title}}">some content</uib-tab>',
+        '  <!-- another comment -->',
+        '  <uib-tab heading="mid">Mid Static</uib-tab>',
+        '  a text node',
+        '  <!-- another comment -->',
+        '  <span>yet another span that may do evil</span>',
+        '  <uib-tab ng-repeat="tab in tabs | filter:tabIsAvailable" active="tab.active" heading="Second {{tab.title}}">some content</uib-tab>',
+        '  a text node',
+        '  <span>yet another span that may do evil</span>',
+        '  <!-- another comment -->',
+        '  <uib-tab heading="last">Last Static</uib-tab>',
+        '  a text node',
+        '  <span>yet another span that may do evil</span>',
+        '  <!-- another comment -->',
+        '</uib-tabset>'
+      ].join('\n'))(scope);
+
+      scope.tabIsAvailable = function(tab) {
+        return tab.available;
+      };
+    }));
+
+    it('should preserve correct ordering', function() {
+      function titles() {
+        return elm.find('ul.nav-tabs li a');
+      }
+      scope.$apply();
+      expect(titles().length).toBe(9);
+      scope.$apply('tabs[1].available=false');
+      scope.$digest();
+      expect(titles().length).toBe(7);
+      scope.$apply('tabs[0].available=false');
+      scope.$digest();
+      expect(titles().length).toBe(5);
+      scope.$apply('tabs[2].available=false');
+      scope.$digest();
+      expect(titles().length).toBe(3);
+      scope.$apply('tabs[0].available=true');
+      scope.$digest();
+      expect(titles().length).toBe(5);
+      scope.$apply('tabs[1].available=true');
+      scope.$apply('tabs[2].available=true');
+      scope.$digest();
+      expect(titles().length).toBe(9);
+      expect(titles().eq(0).text().trim()).toBe('first');
+      expect(titles().eq(1).text().trim()).toBe('Title 1');
+      expect(titles().eq(2).text().trim()).toBe('Title 2');
+      expect(titles().eq(3).text().trim()).toBe('Title 3');
+      expect(titles().eq(4).text().trim()).toBe('mid');
+      expect(titles().eq(5).text().trim()).toBe('Second Title 1');
+      expect(titles().eq(6).text().trim()).toBe('Second Title 2');
+      expect(titles().eq(7).text().trim()).toBe('Second Title 3');
+      expect(titles().eq(8).text().trim()).toBe('last');
+    });
+  });
+
+  describe('uib-tabset controller', function() {
+    function mockTab(isActive) {
+      var _isActive;
+      if (isActive || isActive === false) {
+        _isActive = isActive;
+      }
+
+      return {
+        active: _isActive,
+        onSelect : angular.noop,
+        onDeselect : angular.noop
+      };
+    }
+
+    var ctrl;
+    beforeEach(inject(function($controller, $rootScope) {
+      scope = $rootScope;
+      //instantiate the controller stand-alone, without the directive
+      ctrl = $controller('UibTabsetController', {$scope: scope});
+    }));
+
+    describe('select', function() {
+      it('should mark given tab selected', function() {
+        var tab = mockTab();
+
+        ctrl.select(tab);
+        expect(tab.active).toBe(true);
+      });
+
+
+      it('should deselect other tabs', function() {
+        var tab1 = mockTab(), tab2 = mockTab(), tab3 = mockTab();
+
+        ctrl.addTab(tab1);
+        ctrl.addTab(tab2);
+        ctrl.addTab(tab3);
+
+        ctrl.select(tab1);
+        expect(tab1.active).toBe(true);
+        expect(tab2.active).toBe(false);
+        expect(tab3.active).toBe(false);
+
+        ctrl.select(tab2);
+        expect(tab1.active).toBe(false);
+        expect(tab2.active).toBe(true);
+        expect(tab3.active).toBe(false);
+
+        ctrl.select(tab3);
+        expect(tab1.active).toBe(false);
+        expect(tab2.active).toBe(false);
+        expect(tab3.active).toBe(true);
+      });
+    });
+
+
+    describe('addTab', function() {
+      it('should append tab', function() {
+        var tab1 = mockTab(), tab2 = mockTab();
+
+        expect(ctrl.tabs).toEqual([]);
+
+        ctrl.addTab(tab1);
+        expect(ctrl.tabs).toEqual([tab1]);
+
+        ctrl.addTab(tab2);
+        expect(ctrl.tabs).toEqual([tab1, tab2]);
+      });
+
+      it('should select the first one', function() {
+        var tab1 = mockTab(), tab2 = mockTab();
+
+        ctrl.addTab(tab1);
+        expect(tab1.active).toBe(true);
+
+        ctrl.addTab(tab2);
+        expect(tab1.active).toBe(true);
+      });
+
+      it('should not select first active === false tab as selected', function() {
+        var tab = mockTab(false);
+
+        ctrl.addTab(tab);
+        expect(tab.active).toBe(false);
+      });
+
+      it('should select a tab added that\'s already active', function() {
+        var tab1 = mockTab(), tab2 = mockTab(true);
+        ctrl.addTab(tab1);
+        expect(tab1.active).toBe(true);
+
+        ctrl.addTab(tab2);
+        expect(tab1.active).toBe(false);
+        expect(tab2.active).toBe(true);
+      });
+    });
+  });
+
+  describe('remove', function() {
+    it('should remove title tabs when elements are destroyed and change selection', inject(function($controller, $compile, $rootScope) {
+      scope = $rootScope.$new();
+      elm = $compile('<uib-tabset><uib-tab heading="1">Hello</uib-tab><uib-tab ng-repeat="i in list" heading="tab {{i}}">content {{i}}</uib-tab></uib-tabset>')(scope);
+      scope.$apply();
+
+      expectTitles(['1']);
+      expectContents(['Hello']);
+
+      scope.$apply('list = [1,2,3]');
+      expectTitles(['1', 'tab 1', 'tab 2', 'tab 3']);
+      expectContents(['Hello', 'content 1', 'content 2', 'content 3']);
+
+      // Select last tab
+      titles().find('a').eq(3).click();
+      expect(contents().eq(3)).toHaveClass('active');
+      expect(titles().eq(3)).toHaveClass('active');
+
+      // Remove last tab
+      scope.$apply('list = [1,2]');
+      expectTitles(['1', 'tab 1', 'tab 2']);
+      expectContents(['Hello', 'content 1', 'content 2']);
+
+      // "tab 2" is now selected
+      expect(titles().eq(2)).toHaveClass('active');
+      expect(contents().eq(2)).toHaveClass('active');
+
+      // Select 2nd tab ("tab 1")
+      titles().find('a').eq(1).click();
+      expect(titles().eq(1)).toHaveClass('active');
+      expect(contents().eq(1)).toHaveClass('active');
+
+      // Remove 2nd tab
+      scope.$apply('list = [2]');
+      expectTitles(['1', 'tab 2']);
+      expectContents(['Hello', 'content 2']);
+
+      // New 2nd tab is now selected
+      expect(titles().eq(1)).toHaveClass('active');
+      expect(contents().eq(1)).toHaveClass('active');
+    }));
+
+    it('should not select tabs when being destroyed', inject(function($controller, $compile, $rootScope) {
+      var selectList = [],
+          deselectList = [],
+          getTab = function(active) {
+            return {
+              active: active,
+              select : function() {
+                selectList.push('select');
+              },
+              deselect : function() {
+                deselectList.push('deselect');
+              }
+            };
+          };
+
+      scope = $rootScope.$new();
+      scope.tabs = [
+        getTab(true),
+        getTab(false)
+      ];
+      elm = $compile([
+        '<uib-tabset>',
+        '  <uib-tab ng-repeat="t in tabs" active="t.active" select="t.select()" deselect="t.deselect()">',
+        '    <uib-tab-heading><b>heading</b> {{index}}</uib-tab-heading>',
+        '    content {{$index}}',
+        '  </uib-tab>',
+        '</uib-tabset>'
+      ].join('\n'))(scope);
+      scope.$apply();
+
+      // The first tab is selected the during the initial $digest.
+      expect(selectList.length).toEqual(1);
+
+      // Destroy the tabs - we should not trigger selection/deselection any more.
+      scope.$destroy();
+      expect(selectList.length).toEqual(1);
+      expect(deselectList.length).toEqual(0);
+    }));
+  });
+
+  describe('disable', function() {
+    beforeEach(inject(function($compile, $rootScope) {
+      scope = $rootScope.$new();
+
+      function makeTab(disable) {
+        return {
+          active: false,
+          select: jasmine.createSpy(),
+          disable: disable
+        };
+      }
+      scope.tabs = [
+        makeTab(false), makeTab(true), makeTab(false), makeTab(true)
+      ];
+      elm = $compile([
+        '<uib-tabset>',
+        '  <uib-tab ng-repeat="t in tabs" active="t.active" select="t.select()" disable="t.disable">',
+        '    <uib-tab-heading><b>heading</b> {{index}}</uib-tab-heading>',
+        '    content {{$index}}',
+        '  </uib-tab>',
+        '</uib-tabset>'
+      ].join('\n'))(scope);
+      scope.$apply();
+    }));
+
+    function expectTabActive(activeTab) {
+      var _titles = titles();
+      angular.forEach(scope.tabs, function(tab, i) {
+        if (activeTab === tab) {
+          expect(tab.active).toBe(true);
+          expect(tab.select.calls.count()).toBe( (tab.disable) ? 0 : 1 );
+          expect(_titles.eq(i)).toHaveClass('active');
+          expect(contents().eq(i).text().trim()).toBe('content ' + i);
+          expect(contents().eq(i)).toHaveClass('active');
+        } else {
+          expect(tab.active).toBe(false);
+          expect(_titles.eq(i)).not.toHaveClass('active');
+        }
+      });
+    }
+
+    it('should not switch active when clicking on title', function() {
+      titles().eq(2).find('a').click();
+      expectTabActive(scope.tabs[2]);
+
+      titles().eq(3).find('a').click();
+      expectTabActive(scope.tabs[2]);
+    });
+
+    it('should toggle between states', function() {
+      expect(titles().eq(3)).toHaveClass('disabled');
+      scope.$apply('tabs[3].disable = false');
+      expect(titles().eq(3)).not.toHaveClass('disabled');
+
+      expect(titles().eq(2)).not.toHaveClass('disabled');
+      scope.$apply('tabs[2].disable = true');
+      expect(titles().eq(2)).toHaveClass('disabled');
+    });
+  });
+
+  describe('vertical', function() {
+    beforeEach(inject(function($compile, $rootScope) {
+      scope = $rootScope.$new();
+      scope.vertical = true;
+      elm = $compile('<uib-tabset vertical="vertical"></uib-tabset>')(scope);
+      scope.$apply();
+    }));
+
+    it('to stack tabs', function() {
+      expect(elm.find('ul.nav-tabs')).toHaveClass('nav-stacked');
+    });
+  });
+
+  describe('justified', function() {
+      beforeEach(inject(function($compile, $rootScope) {
+          scope = $rootScope.$new();
+          scope.justified = true;
+          elm = $compile('<uib-tabset justified="justified"></uib-tabset>')(scope);
+          scope.$apply();
+      }));
+
+      it('to justify tabs', function() {
+          expect(elm.find('ul.nav-tabs')).toHaveClass('nav-justified');
+      });
+  });
+
+  describe('type', function() {
+    beforeEach(inject(function($compile, $rootScope) {
+      scope = $rootScope.$new();
+      scope.navType = 'pills';
+
+      elm = $compile('<uib-tabset type="{{navType}}"></uib-tabset>')(scope);
+      scope.$apply();
+    }));
+
+    it('to show pills', function() {
+      expect(elm.find('ul')).toHaveClass('nav-pills');
+      expect(elm.find('ul')).not.toHaveClass('nav-tabs');
+    });
+  });
+
+  //https://github.com/angular-ui/bootstrap/issues/524
+  describe('child compilation', function() {
+    var elm;
+    beforeEach(inject(function($compile, $rootScope) {
+      elm = $compile('<uib-tabset><uib-tab><div></div></uib-tab></uib-tabset></div>')($rootScope.$new());
+      $rootScope.$apply();
+    }));
+
+    it('should hookup the tab\'s children to the tab with $compile', function() {
+      var tabChild = $('.tab-pane', elm).children().first();
+      expect(tabChild.inheritedData('$uibTabsetController')).toBeTruthy();
+    });
+  });
+
+  //https://github.com/angular-ui/bootstrap/issues/631
+  describe('ng-options in content', function() {
+    var elm;
+    it('should render correct amount of options', inject(function($compile, $rootScope) {
+      var scope = $rootScope.$new();
+      elm = $compile('<uib-tabset><uib-tab><select ng-model="foo" ng-options="i for i in [1,2,3]"></uib-tab></uib-tabset>')(scope);
+      scope.$apply();
+
+      var select = elm.find('select');
+      scope.$apply();
+      expect(select.children().length).toBe(4);
+    }));
+  });
+
+  //https://github.com/angular-ui/bootstrap/issues/599
+  describe('ng-repeat in content', function() {
+    var elm;
+    it('should render ng-repeat', inject(function($compile, $rootScope) {
+      var scope = $rootScope.$new();
+      scope.tabs = [
+        {title:'a', array:[1,2,3]},
+        {title:'b', array:[2,3,4]},
+        {title:'c', array:[3,4,5]}
+      ];
+      elm = $compile('<div><uib-tabset>' +
+        '<uib-tab ng-repeat="tab in tabs" heading="{{tab.title}}">' +
+          '<uib-tab-heading>{{$index}}</uib-tab-heading>' +
+          '<span ng-repeat="a in tab.array">{{a}},</span>' +
+        '</uib-tab>' +
+      '</uib-tabset></div>')(scope);
+      scope.$apply();
+
+      var contents = elm.find('.tab-pane');
+      expect(contents.eq(0).text().trim()).toEqual('1,2,3,');
+      expect(contents.eq(1).text().trim()).toEqual('2,3,4,');
+      expect(contents.eq(2).text().trim()).toEqual('3,4,5,');
+    }));
+  });
+
+  //https://github.com/angular-ui/bootstrap/issues/783
+  describe('nested tabs', function() {
+    var elm;
+    it('should render without errors', inject(function($compile, $rootScope) {
+      var scope = $rootScope.$new();
+      elm = $compile([
+        '<div>',
+        '  <uib-tabset class="tabbable">',
+        '    <uib-tab heading="Tab 1">',
+        '      <uib-tabset class="tabbable">',
+        '        <uib-tab heading="Tab 1A">',
+        '        </uib-tab>',
+        '      </uib-tabset>',
+        '    </uib-tab>',
+        '    <uib-tab heading="Tab 2">',
+        '      <uib-tabset class="tabbable">',
+        '        <uib-tab heading="Tab 2A">',
+        '        </uib-tab>',
+        '      </uib-tabset>',
+        '    </uib-tab>',
+        '  </uib-tabset>',
+        '</div>'
+      ].join('\n'))(scope);
+      scope.$apply();
+
+      // 1 outside tabset, 2 nested tabsets
+      expect(elm.find('.tabbable').length).toEqual(3);
+    }));
+
+    it('should render with the correct scopes', inject(function($compile, $rootScope) {
+      var scope = $rootScope.$new();
+      scope.tab1Text = 'abc';
+      scope.tab1aText = '123';
+      scope.tab1aHead = '123';
+      scope.tab2aaText = '456';
+      elm = $compile([
+        '<div>',
+        '  <uib-tabset class="tabbable">',
+        '    <uib-tab heading="Tab 1">',
+        '      <uib-tabset class="tabbable">',
+        '        <uib-tab heading="{{ tab1aHead }}">',
+        '          {{ tab1aText }}',
+        '        </uib-tab>',
+        '      </uib-tabset>',
+        '      <span class="tab-1">{{ tab1Text }}</span>',
+        '    </uib-tab>',
+        '    <uib-tab heading="Tab 2">',
+        '      <uib-tabset class="tabbable">',
+        '        <uib-tab heading="Tab 2A">',
+        '          <uib-tabset class="tabbable">',
+        '            <uib-tab heading="Tab 2AA">',
+        '              <span class="tab-2aa">{{ tab2aaText }}</span>',
+        '            </uib-tab>',
+        '          </uib-tabset>',
+        '        </uib-tab>',
+        '      </uib-tabset>',
+        '    </uib-tab>',
+        '  </uib-tabset>',
+        '</div>'
+      ].join('\n'))(scope);
+      scope.$apply();
+
+      var outsideTabset = elm.find('.tabbable').eq(0);
+      var nestedTabset = outsideTabset.find('.tabbable');
+
+      expect(elm.find('.tabbable').length).toEqual(4);
+      expect(outsideTabset.find('.tab-pane').eq(0).find('.tab-1').text().trim()).toEqual(scope.tab1Text);
+      expect(nestedTabset.find('.tab-pane').eq(0).text().trim()).toEqual(scope.tab1aText);
+      expect(nestedTabset.find('ul.nav-tabs li').eq(0).text().trim()).toEqual(scope.tab1aHead);
+      expect(nestedTabset.eq(2).find('.tab-pane').eq(0).find('.tab-2aa').text().trim()).toEqual(scope.tab2aaText);
+    }));
+
+    it('ng-repeat works with nested tabs', inject(function($compile, $rootScope) {
+      var scope = $rootScope.$new();
+      scope.tabs = [
+        {
+          tabs: [
+          {
+            content: 'tab1a'
+          },
+          {
+            content: 'tab2a'
+          }
+          ],
+          content: 'tab1'
+        }
+      ];
+      elm = $compile([
+        '<div>',
+        '  <uib-tabset>',
+        '    <uib-tab ng-repeat="tab in tabs">',
+        '      <uib-tabset>',
+        '        <uib-tab ng-repeat="innerTab in tab.tabs">',
+        '          <span class="inner-tab-content">{{ innerTab.content }}</span>',
+        '        </uib-tab>',
+        '      </uib-tabset>',
+        '      <span class="outer-tab-content">{{ tab.content }}</span>',
+        '    </uib-tab>',
+        '  </uib-tabset>',
+        '</div>'
+      ].join('\n'))(scope);
+      scope.$apply();
+
+      expect(elm.find('.inner-tab-content').eq(0).text().trim()).toEqual(scope.tabs[0].tabs[0].content);
+      expect(elm.find('.inner-tab-content').eq(1).text().trim()).toEqual(scope.tabs[0].tabs[1].content);
+      expect(elm.find('.outer-tab-content').eq(0).text().trim()).toEqual(scope.tabs[0].content);
+    }));
+  });
+});
+
+/* deprecation tests below */
+
+describe('tab deprecation', function() {
+  beforeEach(module('ui.bootstrap.tabs'));
+  beforeEach(module('template/tabs/tabset.html'));
+  beforeEach(module('template/tabs/tab.html'));
+
+  it('should suppress warning', function() {
+    module(function($provide) {
+      $provide.value('$tabsSuppressWarning', true);
+    });
+
+    inject(function($compile, $log, $rootScope) {
+      spyOn($log, 'warn');
+
+      var element =
+        '<tabset>' +
+          '<tab>' +
+            '<tab-heading>Tab Heading One</tab-heading>' +
+            '<div>Tab One Contents</div>' +
+          '</tab>' +
+          '<tab heading="Tab Heading Two">' +
+            '<div>Tab Two Contents</div>' +
+          '</tab>' +
+        '</tabset>';
+      $compile(element)($rootScope);
+      $rootScope.$digest();
+      expect($log.warn.calls.count()).toBe(0);
+    });
+  });
+
+  it('should give warning by default', inject(function($templateCache, $compile, $log, $rootScope) {
+    spyOn($log, 'warn');
+
+    var tabSetTemplate =
+     '<div>' +
+       '<ul class="nav nav-{{type || \'tabs\'}}" ng-class="{\'nav-stacked\': vertical, \'nav-justified\': justified}" ng-transclude></ul>' +
+       '<div class="tab-content">' +
+         '<div class="tab-pane" ng-repeat="tab in tabs" ng-class="{active: tab.active}" tab-content-transclude="tab"></div>' +
+       '</div>' +
+     '</div>';
+    $templateCache.put('template/tabs/tabset.html', tabSetTemplate);
+
+    var tabTemplate =
+      '<li ng-class="{active: active, disabled: disabled}">' +
+      '<a href ng-click="select()" tab-heading-transclude>{{heading}}</a>' +
+      '</li>';
+    $templateCache.put('template/tabs/tab.html', tabTemplate);
+
+    var element =
+      '<tabset>' +
+        '<tab>' +
+          '<tab-heading>Tab Heading One</tab-heading>' +
+          '<div>Tab One Contents</div>' +
+        '</tab>' +
+      '</tabset>';
+    $compile(element)($rootScope);
+    $rootScope.$digest();
+
+    expect($log.warn.calls.count()).toBe(5);
+    expect($log.warn.calls.argsFor(0)).toEqual(['TabsetController is now deprecated. Use UibTabsetController instead.']);
+    expect($log.warn.calls.argsFor(1)).toEqual(['tab-heading-transclude is now deprecated. Use uib-tab-heading-transclude instead.']);
+    expect($log.warn.calls.argsFor(2)).toEqual(['tab is now deprecated. Use uib-tab instead.']);
+    expect($log.warn.calls.argsFor(3)).toEqual(['tabset is now deprecated. Use uib-tabset instead.']);
+    expect($log.warn.calls.argsFor(4)).toEqual(['tab-content-transclude is now deprecated. Use uib-tab-content-transclude instead.']);
+  }));
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/timepicker/docs/demo.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/timepicker/docs/demo.html
new file mode 100644
index 0000000..8bf9e9f
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/timepicker/docs/demo.html
@@ -0,0 +1,24 @@
+<div ng-controller="TimepickerDemoCtrl">
+
+  <uib-timepicker ng-model="mytime" ng-change="changed()" hour-step="hstep" minute-step="mstep" show-meridian="ismeridian"></uib-timepicker>
+
+  <pre class="alert alert-info">Time is: {{mytime | date:'shortTime' }}</pre>
+
+  <div class="row"> 
+    <div class="col-xs-6">
+        Hours step is:
+      <select class="form-control" ng-model="hstep" ng-options="opt for opt in options.hstep"></select>
+    </div>
+    <div class="col-xs-6">
+        Minutes step is:
+      <select class="form-control" ng-model="mstep" ng-options="opt for opt in options.mstep"></select>
+    </div>
+  </div>
+
+  <hr>
+
+  <button type="button" class="btn btn-info" ng-click="toggleMode()">12H / 24H</button>
+  <button type="button" class="btn btn-default" ng-click="update()">Set to 14:00</button>
+  <button type="button" class="btn btn-danger" ng-click="clear()">Clear</button>
+
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/timepicker/docs/demo.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/timepicker/docs/demo.js
new file mode 100644
index 0000000..8c9ade2
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/timepicker/docs/demo.js
@@ -0,0 +1,31 @@
+angular.module('ui.bootstrap.demo').controller('TimepickerDemoCtrl', function ($scope, $log) {
+  $scope.mytime = new Date();
+
+  $scope.hstep = 1;
+  $scope.mstep = 15;
+
+  $scope.options = {
+    hstep: [1, 2, 3],
+    mstep: [1, 5, 10, 15, 25, 30]
+  };
+
+  $scope.ismeridian = true;
+  $scope.toggleMode = function() {
+    $scope.ismeridian = ! $scope.ismeridian;
+  };
+
+  $scope.update = function() {
+    var d = new Date();
+    d.setHours( 14 );
+    d.setMinutes( 0 );
+    $scope.mytime = d;
+  };
+
+  $scope.changed = function () {
+    $log.log('Time changed to: ' + $scope.mytime);
+  };
+
+  $scope.clear = function() {
+    $scope.mytime = null;
+  };
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/timepicker/docs/readme.md b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/timepicker/docs/readme.md
new file mode 100644
index 0000000..4e2a231
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/timepicker/docs/readme.md
@@ -0,0 +1,56 @@
+A lightweight & configurable timepicker directive.
+
+### Settings ###
+
+All settings can be provided as attributes in the `<uib-timepicker>` or globally configured through the `uibTimepickerConfig`.
+
+ * `ng-model` <i class="glyphicon glyphicon-eye-open"></i>
+ 	:
+ 	The Date object that provides the time state.
+
+  * `template-url` (Defaults: `template/timepicker/timepicker.html`) :
+    Add the ability to override the template used on the component.
+
+ * `hour-step` <i class="glyphicon glyphicon-eye-open"></i>
+ 	_(Defaults: 1)_ :
+ 	 Number of hours to increase or decrease when using a button.
+
+ * `minute-step` <i class="glyphicon glyphicon-eye-open"></i>
+ 	_(Defaults: 1)_ :
+ 	 Number of minutes to increase or decrease when using a button.
+
+ * `show-meridian` <i class="glyphicon glyphicon-eye-open"></i>
+ 	_(Defaults: true)_ :
+ 	Whether to display 12H or 24H mode.
+
+ * `meridians`
+ 	_(Defaults: null)_ :
+ 	 Meridian labels based on locale. To override you must supply an array like ['AM', 'PM'].
+
+ * `readonly-input`
+ 	_(Defaults: false)_ :
+ 	 Whether user can type inside the hours & minutes input.
+
+ * `mousewheel`
+    _(Defaults: true)_ :
+     Whether user can scroll inside the hours & minutes input to increase or decrease it's values.
+
+ * `arrowkeys`
+    _(Defaults: true)_ :
+     Whether user can use up/down arrowkeys inside the hours & minutes input to increase or decrease it's values.
+
+ * `show-spinners`
+    _(Defaults: true)_ :
+     Shows spinner arrows above and below the inputs
+
+ * `min`
+    _(Defaults: undefined)_ :
+     Minimum time a user can select
+
+ * `max`
+    _(Defaults: undefined)_ :
+     Maximum time a user can select
+
+ * `tabindex`
+    _(Defaults: 0)_ :
+     Sets tabindex for each control in timepicker
\ No newline at end of file
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/timepicker/test/timepicker.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/timepicker/test/timepicker.spec.js
new file mode 100644
index 0000000..8e13cea
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/timepicker/test/timepicker.spec.js
@@ -0,0 +1,1774 @@
+describe('timepicker directive', function() {
+  var $rootScope, $compile, $templateCache, element;
+
+  beforeEach(module('ui.bootstrap.timepicker'));
+  beforeEach(module('template/timepicker/timepicker.html'));
+  beforeEach(inject(function(_$compile_, _$rootScope_, _$templateCache_) {
+    $compile = _$compile_;
+    $rootScope = _$rootScope_;
+    $rootScope.time = newTime(14, 40);
+    $templateCache = _$templateCache_;
+
+    element = $compile('<uib-timepicker ng-model="time"></uib-timepicker>')($rootScope);
+    $rootScope.$digest();
+  }));
+
+  function newTime(hours, minutes) {
+    var time = new Date();
+    time.setHours(hours, minutes, 0, 0);
+    return time;
+  }
+
+  function getTimeState(withoutMeridian) {
+    var inputs = element.find('input');
+
+    var state = [];
+    for (var i = 0; i < 2; i ++) {
+      state.push(inputs.eq(i).val());
+    }
+    if (withoutMeridian !== true) {
+      state.push(getMeridianButton().text());
+    }
+    return state;
+  }
+
+  function getModelState() {
+    return [ $rootScope.time.getHours(), $rootScope.time.getMinutes() ];
+  }
+
+  function getArrow(isUp, tdIndex) {
+    return element.find('tr').eq(isUp ? 0 : 2).find('td').eq(tdIndex).find('a').eq(0);
+  }
+
+  function getHoursButton(isUp) {
+    return getArrow(isUp, 0);
+  }
+
+  function getMinutesButton(isUp) {
+    return getArrow(isUp, 2);
+  }
+
+  function getMeridianButton() {
+    return element.find('button').eq(0);
+  }
+
+  function doClick(button, n) {
+    for (var i = 0, max = n || 1; i < max; i++) {
+      button.click();
+      $rootScope.$digest();
+    }
+  }
+
+  function wheelThatMouse(delta) {
+    var e = $.Event('mousewheel');
+    e.wheelDelta = delta;
+    return e;
+  }
+
+  function wheelThatOtherMouse(delta) {
+    var e = $.Event('wheel');
+    e.deltaY = delta;
+    return e;
+  }
+
+  function keydown(key) {
+    var e = $.Event('keydown');
+    switch(key) {
+      case 'left':
+        e.which = 37;
+        break;
+      case 'up':
+        e.which = 38;
+        break;
+      case 'right':
+        e.which = 39;
+        break;
+      case 'down':
+        e.which = 40;
+        break;
+    }
+    return e;
+  }
+
+  it('contains three row & three input elements', function() {
+    expect(element.find('tr').length).toBe(3);
+    expect(element.find('input').length).toBe(2);
+    expect(element.find('button').length).toBe(1);
+  });
+
+  it('has initially the correct time & meridian', function() {
+    expect(getTimeState()).toEqual(['02', '40', 'PM']);
+    expect(getModelState()).toEqual([14, 40]);
+  });
+
+  it('should be pristine', function() {
+    expect(element.controller('ngModel').$pristine).toBe(true);
+  });
+
+  it('has `selected` current time when model is initially cleared', function() {
+    $rootScope.time = null;
+    element = $compile('<uib-timepicker ng-model="time"></uib-timepicker>')($rootScope);
+    $rootScope.$digest();
+
+    expect($rootScope.time).toBe(null);
+    expect(getTimeState()).not.toEqual(['', '', '']);
+  });
+
+  it('changes inputs when model changes value', function() {
+    $rootScope.time = newTime(11, 50);
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['11', '50', 'AM']);
+    expect(getModelState()).toEqual([11, 50]);
+
+    $rootScope.time = newTime(16, 40);
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['04', '40', 'PM']);
+    expect(getModelState()).toEqual([16, 40]);
+  });
+
+  it('increases / decreases hours when arrows are clicked', function() {
+    var up = getHoursButton(true);
+    var down = getHoursButton(false);
+
+    doClick(up);
+    expect(getTimeState()).toEqual(['03', '40', 'PM']);
+    expect(getModelState()).toEqual([15, 40]);
+
+    doClick(down);
+    expect(getTimeState()).toEqual(['02', '40', 'PM']);
+    expect(getModelState()).toEqual([14, 40]);
+
+    doClick(down);
+    expect(getTimeState()).toEqual(['01', '40', 'PM']);
+    expect(getModelState()).toEqual([13, 40]);
+  });
+
+  it('increase / decreases minutes by default step when arrows are clicked', function() {
+    var up = getMinutesButton(true);
+    var down = getMinutesButton(false);
+
+    doClick(up);
+    expect(getTimeState()).toEqual(['02', '41', 'PM']);
+    expect(getModelState()).toEqual([14, 41]);
+
+    doClick(down);
+    expect(getTimeState()).toEqual(['02', '40', 'PM']);
+    expect(getModelState()).toEqual([14, 40]);
+
+    doClick(down);
+    expect(getTimeState()).toEqual(['02', '39', 'PM']);
+    expect(getModelState()).toEqual([14, 39]);
+  });
+
+  it('meridian button has correct type', function() {
+    var button = getMeridianButton();
+    expect(button.attr('type')).toBe('button');
+  });
+
+  it('toggles meridian when button is clicked', function() {
+    var button = getMeridianButton();
+
+    doClick(button);
+    expect(getTimeState()).toEqual(['02', '40', 'AM']);
+    expect(getModelState()).toEqual([2, 40]);
+
+    doClick(button);
+    expect(getTimeState()).toEqual(['02', '40', 'PM']);
+    expect(getModelState()).toEqual([14, 40]);
+
+    doClick(button);
+    expect(getTimeState()).toEqual(['02', '40', 'AM']);
+    expect(getModelState()).toEqual([2, 40]);
+  });
+
+  it('has minutes "connected" to hours', function() {
+    var up = getMinutesButton(true);
+    var down = getMinutesButton(false);
+
+    doClick(up, 10);
+    expect(getTimeState()).toEqual(['02', '50', 'PM']);
+    expect(getModelState()).toEqual([14, 50]);
+
+    doClick(up, 10);
+    expect(getTimeState()).toEqual(['03', '00', 'PM']);
+    expect(getModelState()).toEqual([15, 0]);
+
+    doClick(up, 10);
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['03', '10', 'PM']);
+    expect(getModelState()).toEqual([15, 10]);
+
+    doClick(down, 10);
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['03', '00', 'PM']);
+    expect(getModelState()).toEqual([15, 0]);
+
+    doClick(down, 10);
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['02', '50', 'PM']);
+    expect(getModelState()).toEqual([14, 50]);
+  });
+
+  it('has hours "connected" to meridian', function() {
+    var up = getHoursButton(true);
+    var down = getHoursButton(false);
+
+    // AM -> PM
+    $rootScope.time = newTime(11, 0);
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['11', '00', 'AM']);
+    expect(getModelState()).toEqual([11, 0]);
+
+    doClick(up);
+    expect(getTimeState()).toEqual(['12', '00', 'PM']);
+    expect(getModelState()).toEqual([12, 0]);
+
+    doClick(up);
+    expect(getTimeState()).toEqual(['01', '00', 'PM']);
+    expect(getModelState()).toEqual([13, 0]);
+
+    doClick(down);
+    expect(getTimeState()).toEqual(['12', '00', 'PM']);
+    expect(getModelState()).toEqual([12, 0]);
+
+    doClick(down);
+    expect(getTimeState()).toEqual(['11', '00', 'AM']);
+    expect(getModelState()).toEqual([11, 0]);
+
+    // PM -> AM
+    $rootScope.time = newTime(23, 0);
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['11', '00', 'PM']);
+    expect(getModelState()).toEqual([23, 0]);
+
+    doClick(up);
+    expect(getTimeState()).toEqual(['12', '00', 'AM']);
+    expect(getModelState()).toEqual([0, 0]);
+
+    doClick(up);
+    expect(getTimeState()).toEqual(['01', '00', 'AM']);
+    expect(getModelState()).toEqual([01, 0]);
+
+    doClick(down);
+    expect(getTimeState()).toEqual(['12', '00', 'AM']);
+    expect(getModelState()).toEqual([0, 0]);
+
+    doClick(down);
+    expect(getTimeState()).toEqual(['11', '00', 'PM']);
+    expect(getModelState()).toEqual([23, 0]);
+  });
+
+  it('changes only the time part when hours change', function() {
+    $rootScope.time = newTime(23, 50);
+    $rootScope.$digest();
+
+    var date =  $rootScope.time.getDate();
+    var up = getHoursButton(true);
+    doClick(up);
+
+    expect(getTimeState()).toEqual(['12', '50', 'AM']);
+    expect(getModelState()).toEqual([0, 50]);
+    expect(date).toEqual($rootScope.time.getDate());
+  });
+
+  it('changes only the time part when minutes change', function() {
+    element = $compile('<uib-timepicker ng-model="time" minute-step="15"></uib-timepicker>')($rootScope);
+    $rootScope.time = newTime(0, 0);
+    $rootScope.$digest();
+
+    var date =  $rootScope.time.getDate();
+    var up = getMinutesButton(true);
+    doClick(up, 2);
+    expect(getTimeState()).toEqual(['12', '30', 'AM']);
+    expect(getModelState()).toEqual([0, 30]);
+    expect(date).toEqual($rootScope.time.getDate());
+
+    var down = getMinutesButton(false);
+    doClick(down, 2);
+    expect(getTimeState()).toEqual(['12', '00', 'AM']);
+    expect(getModelState()).toEqual([0, 0]);
+    expect(date).toEqual($rootScope.time.getDate());
+
+    doClick(down, 2);
+    expect(getTimeState()).toEqual(['11', '30', 'PM']);
+    expect(getModelState()).toEqual([23, 30]);
+    expect(date).toEqual($rootScope.time.getDate());
+  });
+
+  it('responds properly on "mousewheel" events', function() {
+    var inputs = element.find('input');
+    var hoursEl = inputs.eq(0), minutesEl = inputs.eq(1);
+    var upMouseWheelEvent = wheelThatMouse(1);
+    var downMouseWheelEvent = wheelThatMouse(-1);
+
+    expect(getTimeState()).toEqual(['02', '40', 'PM']);
+    expect(getModelState()).toEqual([14, 40]);
+
+    // UP
+    hoursEl.trigger( upMouseWheelEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['03', '40', 'PM']);
+    expect(getModelState()).toEqual([15, 40]);
+
+    hoursEl.trigger( upMouseWheelEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['04', '40', 'PM']);
+    expect(getModelState()).toEqual([16, 40]);
+
+    minutesEl.trigger( upMouseWheelEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['04', '41', 'PM']);
+    expect(getModelState()).toEqual([16, 41]);
+
+    minutesEl.trigger( upMouseWheelEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['04', '42', 'PM']);
+    expect(getModelState()).toEqual([16, 42]);
+
+    // DOWN
+    minutesEl.trigger( downMouseWheelEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['04', '41', 'PM']);
+    expect(getModelState()).toEqual([16, 41]);
+
+    minutesEl.trigger( downMouseWheelEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['04', '40', 'PM']);
+    expect(getModelState()).toEqual([16, 40]);
+
+    hoursEl.trigger( downMouseWheelEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['03', '40', 'PM']);
+    expect(getModelState()).toEqual([15, 40]);
+
+    hoursEl.trigger( downMouseWheelEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['02', '40', 'PM']);
+    expect(getModelState()).toEqual([14, 40]);
+  });
+
+  it('responds properly on "wheel" events', function() {
+    var inputs = element.find('input');
+    var hoursEl = inputs.eq(0), minutesEl = inputs.eq(1);
+    var upMouseWheelEvent = wheelThatOtherMouse(-1);
+    var downMouseWheelEvent = wheelThatOtherMouse(1);
+
+    expect(getTimeState()).toEqual(['02', '40', 'PM']);
+    expect(getModelState()).toEqual([14, 40]);
+
+    // UP
+    hoursEl.trigger( upMouseWheelEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['03', '40', 'PM']);
+    expect(getModelState()).toEqual([15, 40]);
+
+    hoursEl.trigger( upMouseWheelEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['04', '40', 'PM']);
+    expect(getModelState()).toEqual([16, 40]);
+
+    minutesEl.trigger( upMouseWheelEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['04', '41', 'PM']);
+    expect(getModelState()).toEqual([16, 41]);
+
+    minutesEl.trigger( upMouseWheelEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['04', '42', 'PM']);
+    expect(getModelState()).toEqual([16, 42]);
+
+    // DOWN
+    minutesEl.trigger( downMouseWheelEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['04', '41', 'PM']);
+    expect(getModelState()).toEqual([16, 41]);
+
+    minutesEl.trigger( downMouseWheelEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['04', '40', 'PM']);
+    expect(getModelState()).toEqual([16, 40]);
+
+    hoursEl.trigger( downMouseWheelEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['03', '40', 'PM']);
+    expect(getModelState()).toEqual([15, 40]);
+
+    hoursEl.trigger( downMouseWheelEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['02', '40', 'PM']);
+    expect(getModelState()).toEqual([14, 40]);
+  });
+
+  it('responds properly on "keydown" events', function() {
+    var inputs = element.find('input');
+    var hoursEl = inputs.eq(0), minutesEl = inputs.eq(1);
+    var upKeydownEvent = keydown('up');
+    var downKeydownEvent = keydown('down');
+    var leftKeydownEvent = keydown('left');
+
+    expect(getTimeState()).toEqual(['02', '40', 'PM']);
+    expect(getModelState()).toEqual([14, 40]);
+
+    // UP
+    hoursEl.trigger( upKeydownEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['03', '40', 'PM']);
+    expect(getModelState()).toEqual([15, 40]);
+
+    hoursEl.trigger( upKeydownEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['04', '40', 'PM']);
+    expect(getModelState()).toEqual([16, 40]);
+
+    minutesEl.trigger( upKeydownEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['04', '41', 'PM']);
+    expect(getModelState()).toEqual([16, 41]);
+
+    minutesEl.trigger( upKeydownEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['04', '42', 'PM']);
+    expect(getModelState()).toEqual([16, 42]);
+
+    // DOWN
+    minutesEl.trigger( downKeydownEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['04', '41', 'PM']);
+    expect(getModelState()).toEqual([16, 41]);
+
+    minutesEl.trigger( downKeydownEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['04', '40', 'PM']);
+    expect(getModelState()).toEqual([16, 40]);
+
+    hoursEl.trigger( downKeydownEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['03', '40', 'PM']);
+    expect(getModelState()).toEqual([15, 40]);
+
+    hoursEl.trigger( downKeydownEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['02', '40', 'PM']);
+    expect(getModelState()).toEqual([14, 40]);
+
+    // Other keydown
+    hoursEl.trigger( leftKeydownEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['02', '40', 'PM']);
+    expect(getModelState()).toEqual([14, 40]);
+
+    minutesEl.trigger( leftKeydownEvent );
+    $rootScope.$digest();
+    expect(getTimeState()).toEqual(['02', '40', 'PM']);
+    expect(getModelState()).toEqual([14, 40]);
+  });
+
+  describe('attributes', function() {
+    beforeEach(function() {
+      $rootScope.hstep = 2;
+      $rootScope.mstep = 30;
+      $rootScope.time = newTime(14, 0);
+      element = $compile('<uib-timepicker ng-model="time" hour-step="hstep" minute-step="mstep"></uib-timepicker>')($rootScope);
+      $rootScope.$digest();
+    });
+
+    it('increases / decreases hours by configurable step', function() {
+      var up = getHoursButton(true);
+      var down = getHoursButton(false);
+
+      expect(getTimeState()).toEqual(['02', '00', 'PM']);
+      expect(getModelState()).toEqual([14, 0]);
+
+      doClick(up);
+      expect(getTimeState()).toEqual(['04', '00', 'PM']);
+      expect(getModelState()).toEqual([16, 0]);
+
+      doClick(down);
+      expect(getTimeState()).toEqual(['02', '00', 'PM']);
+      expect(getModelState()).toEqual([14, 0]);
+
+      doClick(down);
+      expect(getTimeState()).toEqual(['12', '00', 'PM']);
+      expect(getModelState()).toEqual([12, 0]);
+
+      // Change step
+      $rootScope.hstep = 3;
+      $rootScope.$digest();
+
+      doClick(up);
+      expect(getTimeState()).toEqual(['03', '00', 'PM']);
+      expect(getModelState()).toEqual([15, 0]);
+
+      doClick(down);
+      expect(getTimeState()).toEqual(['12', '00', 'PM']);
+      expect(getModelState()).toEqual([12, 0]);
+    });
+
+    it('increases / decreases minutes by configurable step', function() {
+      var up = getMinutesButton(true);
+      var down = getMinutesButton(false);
+
+      doClick(up);
+      expect(getTimeState()).toEqual(['02', '30', 'PM']);
+      expect(getModelState()).toEqual([14, 30]);
+
+      doClick(up);
+      expect(getTimeState()).toEqual(['03', '00', 'PM']);
+      expect(getModelState()).toEqual([15, 0]);
+
+      doClick(down);
+      expect(getTimeState()).toEqual(['02', '30', 'PM']);
+      expect(getModelState()).toEqual([14, 30]);
+
+      doClick(down);
+      expect(getTimeState()).toEqual(['02', '00', 'PM']);
+      expect(getModelState()).toEqual([14, 0]);
+
+      // Change step
+      $rootScope.mstep = 15;
+      $rootScope.$digest();
+
+      doClick(up);
+      expect(getTimeState()).toEqual(['02', '15', 'PM']);
+      expect(getModelState()).toEqual([14, 15]);
+
+      doClick(down);
+      expect(getTimeState()).toEqual(['02', '00', 'PM']);
+      expect(getModelState()).toEqual([14, 0]);
+
+      doClick(down);
+      expect(getTimeState()).toEqual(['01', '45', 'PM']);
+      expect(getModelState()).toEqual([13, 45]);
+    });
+
+    it('responds properly on "mousewheel" events with configurable steps', function() {
+      var inputs = element.find('input');
+      var hoursEl = inputs.eq(0), minutesEl = inputs.eq(1);
+      var upMouseWheelEvent = wheelThatMouse(1);
+      var downMouseWheelEvent = wheelThatMouse(-1);
+
+      expect(getTimeState()).toEqual(['02', '00', 'PM']);
+      expect(getModelState()).toEqual([14, 0]);
+
+      // UP
+      hoursEl.trigger( upMouseWheelEvent );
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['04', '00', 'PM']);
+      expect(getModelState()).toEqual([16, 0]);
+
+      minutesEl.trigger( upMouseWheelEvent );
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['04', '30', 'PM']);
+      expect(getModelState()).toEqual([16, 30]);
+
+      // DOWN
+      minutesEl.trigger( downMouseWheelEvent );
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['04', '00', 'PM']);
+      expect(getModelState()).toEqual([16, 0]);
+
+      hoursEl.trigger( downMouseWheelEvent );
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['02', '00', 'PM']);
+      expect(getModelState()).toEqual([14, 0]);
+    });
+
+    it('responds properly on "wheel" events with configurable steps', function() {
+      var inputs = element.find('input');
+      var hoursEl = inputs.eq(0), minutesEl = inputs.eq(1);
+      var upMouseWheelEvent = wheelThatOtherMouse(-1);
+      var downMouseWheelEvent = wheelThatOtherMouse(1);
+
+      expect(getTimeState()).toEqual(['02', '00', 'PM']);
+      expect(getModelState()).toEqual([14, 0]);
+
+      // UP
+      hoursEl.trigger( upMouseWheelEvent );
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['04', '00', 'PM']);
+      expect(getModelState()).toEqual([16, 0]);
+
+      minutesEl.trigger( upMouseWheelEvent );
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['04', '30', 'PM']);
+      expect(getModelState()).toEqual([16, 30]);
+
+      // DOWN
+      minutesEl.trigger( downMouseWheelEvent );
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['04', '00', 'PM']);
+      expect(getModelState()).toEqual([16, 0]);
+
+      hoursEl.trigger( downMouseWheelEvent );
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['02', '00', 'PM']);
+      expect(getModelState()).toEqual([14, 0]);
+    });
+
+    it('can handle strings as steps', function() {
+      var upHours = getHoursButton(true);
+      var upMinutes = getMinutesButton(true);
+
+      expect(getTimeState()).toEqual(['02', '00', 'PM']);
+      expect(getModelState()).toEqual([14, 0]);
+
+      $rootScope.hstep = '4';
+      $rootScope.mstep = '20';
+      $rootScope.$digest();
+
+      doClick(upHours);
+      expect(getTimeState()).toEqual(['06', '00', 'PM']);
+      expect(getModelState()).toEqual([18, 0]);
+
+      doClick(upMinutes);
+      expect(getTimeState()).toEqual(['06', '20', 'PM']);
+      expect(getModelState()).toEqual([18, 20]);
+    });
+
+  });
+
+  describe('12 / 24 hour mode', function() {
+    beforeEach(function() {
+      $rootScope.meridian = false;
+      $rootScope.time = newTime(14, 10);
+      element = $compile('<uib-timepicker ng-model="time" show-meridian="meridian"></uib-timepicker>')($rootScope);
+      $rootScope.$digest();
+    });
+
+    function getMeridianTd() {
+      return element.find('tr').eq(1).find('td').eq(3);
+    }
+
+    it('initially displays correct time when `show-meridian` is false', function() {
+      expect(getTimeState(true)).toEqual(['14', '10']);
+      expect(getModelState()).toEqual([14, 10]);
+      expect(getMeridianTd()).toBeHidden();
+    });
+
+    it('toggles correctly between different modes', function() {
+      expect(getTimeState(true)).toEqual(['14', '10']);
+
+      $rootScope.meridian = true;
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['02', '10', 'PM']);
+      expect(getModelState()).toEqual([14, 10]);
+      expect(getMeridianTd()).not.toBeHidden();
+
+      $rootScope.meridian = false;
+      $rootScope.$digest();
+      expect(getTimeState(true)).toEqual(['14', '10']);
+      expect(getModelState()).toEqual([14, 10]);
+      expect(getMeridianTd()).toBeHidden();
+    });
+
+    it('handles correctly initially empty model on parent element', function() {
+      $rootScope.time = null;
+      element = $compile('<span ng-model="time"><uib-timepicker show-meridian="meridian"></uib-timepicker></span>')($rootScope);
+      $rootScope.$digest();
+
+      expect($rootScope.time).toBe(null);
+    });
+  });
+
+  describe('`meridians` attribute', function() {
+    beforeEach(inject(function() {
+      $rootScope.meridiansArray = ['am', 'pm'];
+      element = $compile('<uib-timepicker ng-model="time" meridians="meridiansArray"></uib-timepicker>')($rootScope);
+      $rootScope.$digest();
+    }));
+
+    it('displays correctly', function() {
+      expect(getTimeState()[2]).toBe('pm');
+    });
+
+    it('toggles correctly', function() {
+      $rootScope.time = newTime(2, 40);
+      $rootScope.$digest();
+      expect(getTimeState()[2]).toBe('am');
+    });
+  });
+
+  describe('`readonly-input` attribute', function() {
+    beforeEach(inject(function() {
+      $rootScope.meridiansArray = ['am', 'pm'];
+      element = $compile('<uib-timepicker ng-model="time" readonly-input="true"></uib-timepicker>')($rootScope);
+      $rootScope.$digest();
+    }));
+
+    it('should make inputs readonly', function() {
+      var inputs = element.find('input');
+      expect(inputs.eq(0).attr('readonly')).toBe('readonly');
+      expect(inputs.eq(1).attr('readonly')).toBe('readonly');
+    });
+  });
+
+  describe('setting timepickerConfig steps', function() {
+    var originalConfig = {};
+    beforeEach(inject(function(_$compile_, _$rootScope_, uibTimepickerConfig) {
+      angular.extend(originalConfig, uibTimepickerConfig);
+      uibTimepickerConfig.hourStep = 2;
+      uibTimepickerConfig.minuteStep = 10;
+      uibTimepickerConfig.showMeridian = false;
+      element = $compile('<uib-timepicker ng-model="time"></uib-timepicker>')($rootScope);
+      $rootScope.$digest();
+    }));
+    afterEach(inject(function(uibTimepickerConfig) {
+      // return it to the original state
+      angular.extend(uibTimepickerConfig, originalConfig);
+    }));
+
+    it('does not affect the initial value', function() {
+      expect(getTimeState(true)).toEqual(['14', '40']);
+      expect(getModelState()).toEqual([14, 40]);
+    });
+
+    it('increases / decreases hours with configured step', function() {
+      var up = getHoursButton(true);
+      var down = getHoursButton(false);
+
+      doClick(up, 2);
+      expect(getTimeState(true)).toEqual(['18', '40']);
+      expect(getModelState()).toEqual([18, 40]);
+
+      doClick(down, 3);
+      expect(getTimeState(true)).toEqual(['12', '40']);
+      expect(getModelState()).toEqual([12, 40]);
+    });
+
+    it('increases / decreases minutes with configured step', function() {
+      var up = getMinutesButton(true);
+      var down = getMinutesButton(false);
+
+      doClick(up);
+      expect(getTimeState(true)).toEqual(['14', '50']);
+      expect(getModelState()).toEqual([14, 50]);
+
+      doClick(down, 3);
+      expect(getTimeState(true)).toEqual(['14', '20']);
+      expect(getModelState()).toEqual([14, 20]);
+    });
+  });
+
+  describe('setting timepickerConfig meridian labels', function() {
+    var originalConfig = {};
+    beforeEach(inject(function(_$compile_, _$rootScope_, uibTimepickerConfig) {
+      angular.extend(originalConfig, uibTimepickerConfig);
+      uibTimepickerConfig.meridians = ['π.μ.', 'μ.μ.'];
+      uibTimepickerConfig.showMeridian = true;
+      element = $compile('<uib-timepicker ng-model="time"></uib-timepicker>')($rootScope);
+      $rootScope.$digest();
+    }));
+    afterEach(inject(function(uibTimepickerConfig) {
+      // return it to the original state
+      angular.extend(uibTimepickerConfig, originalConfig);
+    }));
+
+    it('displays correctly', function() {
+      expect(getTimeState()).toEqual(['02', '40', 'μ.μ.']);
+      expect(getModelState()).toEqual([14, 40]);
+    });
+
+    it('toggles correctly', function() {
+      $rootScope.time = newTime(2, 40);
+      $rootScope.$digest();
+
+      expect(getTimeState()).toEqual(['02', '40', 'π.μ.']);
+      expect(getModelState()).toEqual([2, 40]);
+    });
+  });
+
+  describe('$formatter', function() {
+    var ngModel,
+      date;
+
+    beforeEach(function() {
+      ngModel = element.controller('ngModel');
+      date = new Date('Mon Mar 23 2015 14:40:11 GMT-0700 (PDT)');
+    });
+
+    it('should have one formatter', function() {
+      expect(ngModel.$formatters.length).toBe(1);
+    });
+
+    it('should convert a date to a new reference representing the same date', function() {
+      expect(ngModel.$formatters[0](date)).toEqual(date);
+    });
+
+    it('should convert a valid date string to a date object', function() {
+      expect(ngModel.$formatters[0]('Mon Mar 23 2015 14:40:11 GMT-0700 (PDT)')).toEqual(date);
+    });
+
+    it('should set falsy values as null', function() {
+      expect(ngModel.$formatters[0](undefined)).toBe(null);
+      expect(ngModel.$formatters[0](null)).toBe(null);
+      expect(ngModel.$formatters[0]('')).toBe(null);
+      expect(ngModel.$formatters[0](0)).toBe(null);
+      expect(ngModel.$formatters[0](NaN)).toBe(null);
+    });
+  });
+
+  describe('user input validation', function() {
+    var changeInputValueTo;
+
+    beforeEach(inject(function($sniffer) {
+      changeInputValueTo = function(inputEl, value) {
+        inputEl.val(value);
+        inputEl.trigger($sniffer.hasEvent('input') ? 'input' : 'change');
+        $rootScope.$digest();
+      };
+    }));
+
+    function getHoursInputEl() {
+      return element.find('input').eq(0);
+    }
+
+    function getMinutesInputEl() {
+      return element.find('input').eq(1);
+    }
+
+    it('has initially the correct time & meridian', function() {
+      expect(getTimeState()).toEqual(['02', '40', 'PM']);
+      expect(getModelState()).toEqual([14, 40]);
+    });
+
+    it('updates hours & pads on input change & pads on blur', function() {
+      var el = getHoursInputEl();
+
+      changeInputValueTo(el, 5);
+      expect(getTimeState()).toEqual(['5', '40', 'PM']);
+      expect(getModelState()).toEqual([17, 40]);
+
+      el.blur();
+      expect(getTimeState()).toEqual(['05', '40', 'PM']);
+      expect(getModelState()).toEqual([17, 40]);
+    });
+
+    it('updates minutes & pads on input change & pads on blur', function() {
+      var el = getMinutesInputEl();
+
+      changeInputValueTo(el, 9);
+      expect(getTimeState()).toEqual(['02', '9', 'PM']);
+      expect(getModelState()).toEqual([14, 9]);
+
+      el.blur();
+      expect(getTimeState()).toEqual(['02', '09', 'PM']);
+      expect(getModelState()).toEqual([14, 9]);
+    });
+
+    it('clears model when input hours is invalid & alerts the UI', function() {
+      var el = getHoursInputEl();
+
+      changeInputValueTo(el, 'pizza');
+      expect($rootScope.time).toBe(null);
+      expect(el.parent().hasClass('has-error')).toBe(true);
+      expect(element.hasClass('ng-invalid-time')).toBe(true);
+
+      changeInputValueTo(el, 8);
+      el.blur();
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['08', '40', 'PM']);
+      expect(getModelState()).toEqual([20, 40]);
+      expect(el.parent().hasClass('has-error')).toBe(false);
+      expect(element.hasClass('ng-invalid-time')).toBe(false);
+    });
+
+    it('clears model when input minutes is invalid & alerts the UI', function() {
+      var el = getMinutesInputEl();
+
+      changeInputValueTo(el, 'pizza');
+      expect($rootScope.time).toBe(null);
+      expect(el.parent().hasClass('has-error')).toBe(true);
+      expect(element.hasClass('ng-invalid-time')).toBe(true);
+
+      changeInputValueTo(el, 22);
+      expect(getTimeState()).toEqual(['02', '22', 'PM']);
+      expect(getModelState()).toEqual([14, 22]);
+      expect(el.parent().hasClass('has-error')).toBe(false);
+      expect(element.hasClass('ng-invalid-time')).toBe(false);
+    });
+
+    it('leaves view alone when hours are invalid and minutes are updated', function() {
+      var hoursEl = getHoursInputEl(),
+        minutesEl = getMinutesInputEl();
+
+      changeInputValueTo(hoursEl, '25');
+      hoursEl.blur();
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['25', '40', 'PM']);
+
+      changeInputValueTo(minutesEl, '2');
+      minutesEl.blur();
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['25', '2', 'PM']);
+    });
+
+    it('leaves view alone when minutes are invalid and hours are updated', function() {
+      var hoursEl = getHoursInputEl(),
+        minutesEl = getMinutesInputEl();
+
+      changeInputValueTo(minutesEl, '61');
+      minutesEl.blur();
+      $rootScope.$digest();
+      expect($rootScope.time).toBe(null);
+      expect(getTimeState()).toEqual(['02', '61', 'PM']);
+
+      changeInputValueTo(hoursEl, '2');
+      hoursEl.blur();
+      $rootScope.$digest();
+      expect($rootScope.time).toBe(null);
+      expect(getTimeState()).toEqual(['2', '61', 'PM']);
+    });
+
+    it('handles 12/24H mode change', function() {
+      $rootScope.meridian = true;
+      element = $compile('<uib-timepicker ng-model="time" show-meridian="meridian"></uib-timepicker>')($rootScope);
+      $rootScope.$digest();
+
+      var el = getHoursInputEl();
+
+      changeInputValueTo(el, '16');
+      expect($rootScope.time).toBe(null);
+      expect(el.parent().hasClass('has-error')).toBe(true);
+      expect(element.hasClass('ng-invalid-time')).toBe(true);
+
+      $rootScope.meridian = false;
+      $rootScope.$digest();
+      expect(getTimeState(true)).toEqual(['16', '40']);
+      expect(getModelState()).toEqual([16, 40]);
+      expect(element.hasClass('ng-invalid-time')).toBe(false);
+    });
+
+    it('should have a default tabindex of 0', function() {
+      element = $compile('<uib-timepicker ng-model="time"></uib-timepicker>')($rootScope);
+      $rootScope.$digest();
+
+      expect(element.isolateScope().tabindex).toBe(0);
+    });
+
+    it('should have the correct tabindex', function() {
+      element = $compile('<uib-timepicker ng-model="time" tabindex="5"></uib-timepicker>')($rootScope);
+      $rootScope.$digest();
+
+      expect(element.attr('tabindex')).toBe(undefined);
+      expect(element.isolateScope().tabindex).toBe('5');
+    });
+  });
+
+  describe('when model is not a Date', function() {
+    beforeEach(inject(function() {
+      element = $compile('<uib-timepicker ng-model="time"></uib-timepicker>')($rootScope);
+    }));
+
+    it('should not be invalid when the model is null', function() {
+      $rootScope.time = null;
+      $rootScope.$digest();
+      expect(element.hasClass('ng-invalid-time')).toBe(false);
+    });
+
+    it('should not be invalid when the model is undefined', function() {
+      $rootScope.time = undefined;
+      $rootScope.$digest();
+      expect(element.hasClass('ng-invalid-time')).toBe(false);
+    });
+
+    it('should not be invalid when the model is a valid string date representation', function() {
+      $rootScope.time = 'September 30, 2010 15:30:00';
+      $rootScope.$digest();
+      expect(element.hasClass('ng-invalid-time')).toBe(false);
+      expect(getTimeState()).toEqual(['03', '30', 'PM']);
+    });
+
+    it('should be invalid when the model is not a valid string date representation', function() {
+      $rootScope.time = 'pizza';
+      $rootScope.$digest();
+      expect(element.hasClass('ng-invalid-time')).toBe(true);
+    });
+
+    it('should return valid when the model becomes valid', function() {
+      $rootScope.time = 'pizza';
+      $rootScope.$digest();
+      expect(element.hasClass('ng-invalid-time')).toBe(true);
+
+      $rootScope.time = new Date();
+      $rootScope.$digest();
+      expect(element.hasClass('ng-invalid-time')).toBe(false);
+    });
+
+    it('should return valid when the model is cleared', function() {
+      $rootScope.time = 'pizza';
+      $rootScope.$digest();
+      expect(element.hasClass('ng-invalid-time')).toBe(true);
+
+      $rootScope.time = null;
+      $rootScope.$digest();
+      expect(element.hasClass('ng-invalid-time')).toBe(false);
+    });
+  });
+
+  describe('use with `ng-required` directive', function() {
+    beforeEach(inject(function() {
+      $rootScope.time = null;
+      element = $compile('<uib-timepicker ng-model="time" ng-required="true"></uib-timepicker>')($rootScope);
+      $rootScope.$digest();
+    }));
+
+    it('should be invalid initially', function() {
+      expect(element.hasClass('ng-invalid')).toBe(true);
+    });
+
+    it('should be valid if model has been specified', function() {
+      $rootScope.time = new Date();
+      $rootScope.$digest();
+      expect(element.hasClass('ng-invalid')).toBe(false);
+    });
+  });
+
+  describe('use with `ng-change` directive', function() {
+    beforeEach(inject(function() {
+      $rootScope.changeHandler = jasmine.createSpy('changeHandler');
+      $rootScope.time = new Date();
+      element = $compile('<uib-timepicker ng-model="time" ng-change="changeHandler()"></uib-timepicker>')($rootScope);
+      $rootScope.$digest();
+    }));
+
+    it('should not be called initially', function() {
+      expect($rootScope.changeHandler).not.toHaveBeenCalled();
+    });
+
+    it('should be called when hours / minutes buttons clicked', function() {
+      var btn1 = getHoursButton(true);
+      var btn2 = getMinutesButton(false);
+
+      doClick(btn1, 2);
+      doClick(btn2, 3);
+      $rootScope.$digest();
+      expect($rootScope.changeHandler.calls.count()).toBe(5);
+    });
+
+    it('should not be called when model changes programatically', function() {
+      $rootScope.time = new Date();
+      $rootScope.$digest();
+      expect($rootScope.changeHandler).not.toHaveBeenCalled();
+    });
+  });
+
+  describe('when used with min', function() {
+    var changeInputValueTo;
+    beforeEach(inject(function($sniffer) {
+      element = $compile('<uib-timepicker ng-model="time" min="min"></uib-timepicker>')($rootScope);
+      $rootScope.$digest();
+      changeInputValueTo = function(inputEl, value) {
+        inputEl.val(value);
+        inputEl.trigger($sniffer.hasEvent('input') ? 'input' : 'change');
+        $rootScope.$digest();
+      };
+    }));
+
+    it('should not decrease hours when it would result in a time earlier than min', function() {
+      var down = getHoursButton(false);
+      var inputs = element.find('input');
+      var hoursEl = inputs.eq(0);
+      var downMouseWheelEvent = wheelThatMouse(-1);
+      var downKeydownEvent = keydown('down');
+
+      $rootScope.min = newTime(13, 41);
+      $rootScope.$digest();
+
+      expect(down.hasClass('disabled')).toBe(true);
+
+      doClick(down);
+      expect(getTimeState()).toEqual(['02', '40', 'PM']);
+      expect(getModelState()).toEqual([14, 40]);
+
+      hoursEl.trigger( downMouseWheelEvent );
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['02', '40', 'PM']);
+      expect(getModelState()).toEqual([14, 40]);
+
+      hoursEl.trigger( downKeydownEvent );
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['02', '40', 'PM']);
+      expect(getModelState()).toEqual([14, 40]);
+    });
+
+    it('should decrease hours when it would not result in a time earlier than min', function() {
+      var down = getHoursButton(false);
+      var inputs = element.find('input');
+      var hoursEl = inputs.eq(0);
+      var downMouseWheelEvent = wheelThatMouse(-1);
+      var downKeydownEvent = keydown('down');
+
+      $rootScope.min = newTime(0, 0);
+      $rootScope.$digest();
+
+      expect(down.hasClass('disabled')).toBe(false);
+
+      doClick(down);
+      expect(getTimeState()).toEqual(['01', '40', 'PM']);
+      expect(getModelState()).toEqual([13, 40]);
+
+      hoursEl.trigger(downMouseWheelEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['12', '40', 'PM']);
+      expect(getModelState()).toEqual([12, 40]);
+
+      hoursEl.trigger(downKeydownEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['11', '40', 'AM']);
+      expect(getModelState()).toEqual([11, 40]);
+    });
+
+    it('should not decrease minutes when it would result in a time ealier than min', function() {
+      var down = getMinutesButton(false);
+      var inputs = element.find('input');
+      var minutesEl = inputs.eq(1);
+      var downMouseWheelEvent = wheelThatMouse(-1);
+      var downKeydownEvent = keydown('down');
+
+      $rootScope.min = newTime(14, 40);
+      $rootScope.$digest();
+
+      expect(down.hasClass('disabled')).toBe(true);
+
+      doClick(down);
+      expect(getTimeState()).toEqual(['02', '40', 'PM']);
+      expect(getModelState()).toEqual([14, 40]);
+
+      minutesEl.trigger(downMouseWheelEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['02', '40', 'PM']);
+      expect(getModelState()).toEqual([14, 40]);
+
+      minutesEl.trigger(downKeydownEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['02', '40', 'PM']);
+      expect(getModelState()).toEqual([14, 40]);
+    });
+
+    it('should decrease minutes when it would not result in a time ealier than min', function() {
+      var down = getMinutesButton(false);
+      var inputs = element.find('input');
+      var minutesEl = inputs.eq(1);
+      var downMouseWheelEvent = wheelThatMouse(-1);
+      var downKeydownEvent = keydown('down');
+
+      $rootScope.min = newTime(0, 0);
+      $rootScope.$digest();
+
+      expect(down.hasClass('disabled')).toBe(false);
+
+      doClick(down);
+      expect(getTimeState()).toEqual(['02', '39', 'PM']);
+      expect(getModelState()).toEqual([14, 39]);
+
+      minutesEl.trigger(downMouseWheelEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['02', '38', 'PM']);
+      expect(getModelState()).toEqual([14, 38]);
+
+      minutesEl.trigger(downKeydownEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['02', '37', 'PM']);
+      expect(getModelState()).toEqual([14, 37]);
+    });
+
+    it('should not increase hours when time would rollover to a time earlier than min', function() {
+      var up = getHoursButton(true);
+      var inputs = element.find('input');
+      var hoursEl = inputs.eq(0);
+      var upMouseWheelEvent = wheelThatMouse(1);
+      var upKeydownEvent = keydown('up');
+
+      $rootScope.time = newTime(23, 59);
+      $rootScope.min = newTime(13, 40);
+      $rootScope.$digest();
+
+      expect(up.hasClass('disabled')).toBe(true);
+
+      doClick(up);
+      expect(getTimeState()).toEqual(['11', '59', 'PM']);
+      expect(getModelState()).toEqual([23, 59]);
+
+      hoursEl.trigger(upMouseWheelEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['11', '59', 'PM']);
+      expect(getModelState()).toEqual([23, 59]);
+
+      hoursEl.trigger(upKeydownEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['11', '59', 'PM']);
+      expect(getModelState()).toEqual([23, 59]);
+    });
+
+    it('should increase hours when time would rollover to a time not earlier than min', function() {
+      var up = getHoursButton(true);
+      var inputs = element.find('input');
+      var hoursEl = inputs.eq(0);
+      var upMouseWheelEvent = wheelThatMouse(1);
+      var upKeydownEvent = keydown('up');
+
+      $rootScope.min = newTime(0, 0);
+
+      $rootScope.time = newTime(23, 59);
+      $rootScope.$digest();
+
+      expect(up.hasClass('disabled')).toBe(false);
+
+      doClick(up);
+      expect(getTimeState()).toEqual(['12', '59', 'AM']);
+      expect(getModelState()).toEqual([0, 59]);
+
+      $rootScope.time = newTime(23, 59);
+      $rootScope.$digest();
+
+      hoursEl.trigger(upMouseWheelEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['12', '59', 'AM']);
+      expect(getModelState()).toEqual([0, 59]);
+
+      $rootScope.time = newTime(23, 59);
+      $rootScope.$digest();
+
+      hoursEl.trigger(upKeydownEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['12', '59', 'AM']);
+      expect(getModelState()).toEqual([0, 59]);
+    });
+
+
+    it('should not increase minutes when time would rollover to a time earlier than min', function() {
+      var up = getMinutesButton(true);
+      var inputs = element.find('input');
+      var minutesEl = inputs.eq(1);
+      var upMouseWheelEvent = wheelThatMouse(1);
+      var upKeydownEvent = keydown('up');
+
+      $rootScope.time = newTime(23, 59);
+      $rootScope.min = newTime(13, 40);
+      $rootScope.$digest();
+
+      expect(up.hasClass('disabled')).toBe(true);
+
+      doClick(up);
+      expect(getTimeState()).toEqual(['11', '59', 'PM']);
+      expect(getModelState()).toEqual([23, 59]);
+
+      minutesEl.trigger(upMouseWheelEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['11', '59', 'PM']);
+      expect(getModelState()).toEqual([23, 59]);
+
+      minutesEl.trigger(upKeydownEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['11', '59', 'PM']);
+      expect(getModelState()).toEqual([23, 59]);
+    });
+
+    it('should increase minutes when time would rollover to a time not earlier than min', function() {
+      var up = getMinutesButton(true);
+      var inputs = element.find('input');
+      var minutesEl = inputs.eq(1);
+      var upMouseWheelEvent = wheelThatMouse(1);
+      var upKeydownEvent = keydown('up');
+
+      $rootScope.min = newTime(0, 0);
+
+      $rootScope.time = newTime(23, 59);
+      $rootScope.$digest();
+
+      expect(up.hasClass('disabled')).toBe(false);
+
+      doClick(up);
+      expect(getTimeState()).toEqual(['12', '00', 'AM']);
+      expect(getModelState()).toEqual([0, 0]);
+
+      $rootScope.time = newTime(23, 59);
+      $rootScope.$digest();
+
+      minutesEl.trigger(upMouseWheelEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['12', '00', 'AM']);
+      expect(getModelState()).toEqual([0, 0]);
+
+      $rootScope.time = newTime(23, 59);
+      $rootScope.$digest();
+
+      minutesEl.trigger(upKeydownEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['12', '00', 'AM']);
+      expect(getModelState()).toEqual([0, 0]);
+    });
+
+    it('should not change meridian when it would result a in time earlier than min', function() {
+      var button = getMeridianButton();
+
+      $rootScope.min = newTime(2, 41);
+      $rootScope.$digest();
+
+      expect(button.hasClass('disabled')).toBe(true);
+
+      doClick(button);
+      expect(getTimeState()).toEqual(['02', '40', 'PM']);
+      expect(getModelState()).toEqual([14, 40]);
+    });
+
+    it('should change meridian when it would not result in a time earlier than min', function() {
+      var button = getMeridianButton();
+
+      $rootScope.min = newTime(2, 39);
+      $rootScope.$digest();
+
+      expect(button.hasClass('disabled')).toBe(false);
+
+      doClick(button);
+      expect(getTimeState()).toEqual(['02', '40', 'AM']);
+      expect(getModelState()).toEqual([2, 40]);
+    });
+
+    it('should return invalid when the hours are changes such that the time is earlier than min', function() {
+      var inputs = element.find('input');
+      var hoursEl = inputs.eq(0);
+
+      $rootScope.min = newTime(14, 0);
+      $rootScope.$digest();
+
+      changeInputValueTo(hoursEl, 1);
+      expect($rootScope.time).toBe(null);
+      expect(hoursEl.parent().hasClass('has-error')).toBe(true);
+      expect(element.hasClass('ng-invalid-time')).toBe(true);
+    });
+
+    it('should return valid when the hours are changes such that the time is not earlier than min', function() {
+      var inputs = element.find('input');
+      var hoursEl = inputs.eq(0);
+
+      $rootScope.min = newTime(14, 41);
+      $rootScope.$digest();
+
+      changeInputValueTo(hoursEl, 3);
+      expect(getTimeState()).toEqual(['3', '40', 'PM']);
+      expect(getModelState()).toEqual([15, 40]);
+      expect(hoursEl.parent().hasClass('has-error')).toBe(false);
+      expect(element.hasClass('ng-invalid-time')).toBe(false);
+    });
+
+    it('should return invalid when the minutes are changes such that the time is earlier than min', function() {
+      var inputs = element.find('input');
+      var minutesEl = inputs.eq(1);
+
+      $rootScope.min = newTime(14, 30);
+      $rootScope.$digest();
+
+      changeInputValueTo(minutesEl, 1);
+      expect($rootScope.time).toBe(null);
+      expect(minutesEl.parent().hasClass('has-error')).toBe(true);
+      expect(element.hasClass('ng-invalid-time')).toBe(true);
+    });
+
+    it('should return valid when the minutes are changes such that the time is not earlier than min', function() {
+      var inputs = element.find('input');
+      var minutesEl = inputs.eq(1);
+
+      $rootScope.min = newTime(14, 41);
+      $rootScope.$digest();
+
+      changeInputValueTo(minutesEl, 42);
+      expect(getTimeState()).toEqual(['02', '42', 'PM']);
+      expect(getModelState()).toEqual([14, 42]);
+      expect(minutesEl.parent().hasClass('has-error')).toBe(false);
+      expect(element.hasClass('ng-invalid-time')).toBe(false);
+    });
+  });
+
+  describe('when used with max', function() {
+    var changeInputValueTo;
+    beforeEach(inject(function($sniffer) {
+      element = $compile('<timepicker ng-model="time" max="max"></timepicker>')($rootScope);
+      $rootScope.$digest();
+      changeInputValueTo = function (inputEl, value) {
+        inputEl.val(value);
+        inputEl.trigger($sniffer.hasEvent('input') ? 'input' : 'change');
+        $rootScope.$digest();
+      };
+    }));
+
+    it('should not increase hours when it would result in a time later than max', function() {
+      var up = getHoursButton(true);
+      var inputs = element.find('input');
+      var hoursEl = inputs.eq(0);
+      var upMouseWheelEvent = wheelThatMouse(1);
+      var upKeydownEvent = keydown('up');
+
+      $rootScope.max = newTime(15, 39);
+      $rootScope.$digest();
+
+      expect(up.hasClass('disabled')).toBe(true);
+
+      doClick(up);
+      expect(getTimeState()).toEqual(['02', '40', 'PM']);
+      expect(getModelState()).toEqual([14, 40]);
+
+      hoursEl.trigger(upMouseWheelEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['02', '40', 'PM']);
+      expect(getModelState()).toEqual([14, 40]);
+
+      hoursEl.trigger(upKeydownEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['02', '40', 'PM']);
+      expect(getModelState()).toEqual([14, 40]);
+    });
+
+    it('should increase hours when it would not result in a time later than max', function() {
+      var up = getHoursButton(true);
+      var inputs = element.find('input');
+      var hoursEl = inputs.eq(0);
+      var upMouseWheelEvent = wheelThatMouse(1);
+      var upKeydownEvent = keydown('up');
+
+      $rootScope.max = newTime(23, 59);
+      $rootScope.$digest();
+
+      expect(up.hasClass('disabled')).toBe(false);
+
+      doClick(up);
+      expect(getTimeState()).toEqual(['03', '40', 'PM']);
+      expect(getModelState()).toEqual([15, 40]);
+
+      hoursEl.trigger(upMouseWheelEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['04', '40', 'PM']);
+      expect(getModelState()).toEqual([16, 40]);
+
+      hoursEl.trigger(upKeydownEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['05', '40', 'PM']);
+      expect(getModelState()).toEqual([17, 40]);
+    });
+
+    it('should not increase minutes when it would result in a time later than max', function() {
+      var up = getMinutesButton(true);
+      var inputs = element.find('input');
+      var minutesEl = inputs.eq(1);
+      var upMouseWheelEvent = wheelThatMouse(1);
+      var upKeydownEvent = keydown('up');
+
+      $rootScope.max = newTime(14, 40);
+      $rootScope.$digest();
+
+      expect(up.hasClass('disabled')).toBe(true);
+
+      doClick(up);
+      expect(getTimeState()).toEqual(['02', '40', 'PM']);
+      expect(getModelState()).toEqual([14, 40]);
+
+      minutesEl.trigger(upMouseWheelEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['02', '40', 'PM']);
+      expect(getModelState()).toEqual([14, 40]);
+
+      minutesEl.trigger(upKeydownEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['02', '40', 'PM']);
+      expect(getModelState()).toEqual([14, 40]);
+    });
+
+    it('should increase minutes when it would not result in a time later than max', function() {
+      var up = getMinutesButton(true);
+      var inputs = element.find('input');
+      var minutesEl = inputs.eq(1);
+      var upMouseWheelEvent = wheelThatMouse(1);
+      var upKeydownEvent = keydown('up');
+
+      $rootScope.max = newTime(23, 59);
+      $rootScope.$digest();
+
+      expect(up.hasClass('disabled')).toBe(false);
+
+      doClick(up);
+      expect(getTimeState()).toEqual(['02', '41', 'PM']);
+      expect(getModelState()).toEqual([14, 41]);
+
+      minutesEl.trigger(upMouseWheelEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['02', '42', 'PM']);
+      expect(getModelState()).toEqual([14, 42]);
+
+      minutesEl.trigger(upKeydownEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['02', '43', 'PM']);
+      expect(getModelState()).toEqual([14, 43]);
+    });
+
+    it('should not decrease hours when time would rollover to a time later than max', function() {
+      var down = getHoursButton(false);
+      var inputs = element.find('input');
+      var hoursEl = inputs.eq(0);
+      var downMouseWheelEvent = wheelThatMouse(-1);
+      var downKeydownEvent = keydown('down');
+
+      $rootScope.time = newTime(0, 0);
+      $rootScope.max = newTime(13, 40);
+      $rootScope.$digest();
+
+      expect(down.hasClass('disabled')).toBe(true);
+
+      doClick(down);
+      expect(getTimeState()).toEqual(['12', '00', 'AM']);
+      expect(getModelState()).toEqual([0, 0]);
+
+      hoursEl.trigger(downMouseWheelEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['12', '00', 'AM']);
+      expect(getModelState()).toEqual([0, 0]);
+
+      hoursEl.trigger(downKeydownEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['12', '00', 'AM']);
+      expect(getModelState()).toEqual([0, 0]);
+    });
+
+    it('should decrease hours when time would rollover to a time not later than max', function() {
+      var down = getHoursButton(false);
+      var inputs = element.find('input');
+      var hoursEl = inputs.eq(0);
+      var downMouseWheelEvent = wheelThatMouse(-1);
+      var downKeydownEvent = keydown('down');
+
+      $rootScope.max = newTime(23, 59);
+
+      $rootScope.time = newTime(0, 0);
+      $rootScope.$digest();
+
+      expect(down.hasClass('disabled')).toBe(false);
+
+      doClick(down);
+      expect(getTimeState()).toEqual(['11', '00', 'PM']);
+      expect(getModelState()).toEqual([23, 0]);
+
+      $rootScope.time = newTime(0, 0);
+      $rootScope.$digest();
+
+      hoursEl.trigger(downMouseWheelEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['11', '00', 'PM']);
+      expect(getModelState()).toEqual([23, 0]);
+
+      $rootScope.time = newTime(0, 0);
+      $rootScope.$digest();
+
+      hoursEl.trigger(downKeydownEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['11', '00', 'PM']);
+      expect(getModelState()).toEqual([23, 0]);
+    });
+
+    it('should not decrease minutes when time would rollover to a time later than max', function() {
+      var down = getMinutesButton(false);
+      var inputs = element.find('input');
+      var minutesEl = inputs.eq(1);
+      var downMouseWheelEvent = wheelThatMouse(-1);
+      var downKeydownEvent = keydown('down');
+
+      $rootScope.time = newTime(0, 0);
+      $rootScope.max = newTime(13, 40);
+      $rootScope.$digest();
+
+      expect(down.hasClass('disabled')).toBe(true);
+
+      doClick(down);
+      expect(getTimeState()).toEqual(['12', '00', 'AM']);
+      expect(getModelState()).toEqual([0, 0]);
+
+      minutesEl.trigger(downMouseWheelEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['12', '00', 'AM']);
+      expect(getModelState()).toEqual([0, 0]);
+
+      minutesEl.trigger(downKeydownEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['12', '00', 'AM']);
+      expect(getModelState()).toEqual([0, 0]);
+    });
+
+    it('should decrease minutes when time would rollover to a time not later than max', function() {
+      var down = getMinutesButton(false);
+      var inputs = element.find('input');
+      var minutesEl = inputs.eq(1);
+      var downMouseWheelEvent = wheelThatMouse(-1);
+      var downKeydownEvent = keydown('down');
+
+      $rootScope.max = newTime(23, 59);
+
+      $rootScope.time = newTime(0, 0);
+      $rootScope.$digest();
+
+      expect(down.hasClass('disabled')).toBe(false);
+
+      doClick(down);
+      expect(getTimeState()).toEqual(['11', '59', 'PM']);
+      expect(getModelState()).toEqual([23, 59]);
+
+      $rootScope.time = newTime(0, 0);
+      $rootScope.$digest();
+
+      minutesEl.trigger(downMouseWheelEvent);
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['11', '59', 'PM']);
+      expect(getModelState()).toEqual([23, 59]);
+
+      $rootScope.time = newTime(0, 0);
+      $rootScope.$digest();
+
+      minutesEl.trigger( downKeydownEvent );
+      $rootScope.$digest();
+      expect(getTimeState()).toEqual(['11', '59', 'PM']);
+      expect(getModelState()).toEqual([23, 59]);
+    });
+
+    it('should not change meridian when it would result a in time later than max', function() {
+      var button = getMeridianButton();
+
+      $rootScope.time = newTime(2, 40);
+      $rootScope.max = newTime(14, 39);
+      $rootScope.$digest();
+
+      expect(button.hasClass('disabled')).toBe(true);
+
+      doClick(button);
+      expect(getTimeState()).toEqual(['02', '40', 'AM']);
+      expect(getModelState()).toEqual([2, 40]);
+    });
+
+    it('should change meridian when it would not result in a time later than max', function() {
+      var button = getMeridianButton();
+
+      $rootScope.time = newTime(2, 40);
+      $rootScope.max = newTime(14, 41);
+      $rootScope.$digest();
+
+      expect(button.hasClass('disabled')).toBe(false);
+
+      doClick(button);
+      expect(getTimeState()).toEqual(['02', '40', 'PM']);
+      expect(getModelState()).toEqual([14, 40]);
+    });
+
+    it('should return invalid when the hours are changes such that the time is later than max', function() {
+      var inputs = element.find('input');
+      var hoursEl = inputs.eq(0);
+
+      $rootScope.max = newTime(14, 0);
+      $rootScope.$digest();
+
+      changeInputValueTo(hoursEl, 3);
+      expect($rootScope.time).toBe(null);
+      expect(hoursEl.parent().hasClass('has-error')).toBe(true);
+      expect(element.hasClass('ng-invalid-time')).toBe(true);
+    });
+
+    it('should return valid when the hours are changes such that the time is not later than max', function() {
+      var inputs = element.find('input');
+      var hoursEl = inputs.eq(0);
+
+      $rootScope.max = newTime(15, 41);
+      $rootScope.$digest();
+
+      changeInputValueTo(hoursEl, 3);
+      expect(getTimeState()).toEqual(['3', '40', 'PM']);
+      expect(getModelState()).toEqual([15, 40]);
+      expect(hoursEl.parent().hasClass('has-error')).toBe(false);
+      expect(element.hasClass('ng-invalid-time')).toBe(false);
+    });
+
+    it('should return invalid when the minutes are changes such that the time is later than max', function() {
+      var inputs = element.find('input');
+      var minutesEl = inputs.eq(1);
+
+      $rootScope.max = newTime(14, 50);
+      $rootScope.$digest();
+
+      changeInputValueTo(minutesEl, 51);
+      expect($rootScope.time).toBe(null);
+      expect(minutesEl.parent().hasClass('has-error')).toBe(true);
+      expect(element.hasClass('ng-invalid-time')).toBe(true);
+    });
+
+    it('should return valid when the minutes are changes such that the time is not later than max', function() {
+      var inputs = element.find('input');
+      var minutesEl = inputs.eq(1);
+
+      $rootScope.max = newTime(14, 42);
+      $rootScope.$digest();
+
+      changeInputValueTo(minutesEl, 41);
+      expect(getTimeState()).toEqual(['02', '41', 'PM']);
+      expect(getModelState()).toEqual([14, 41]);
+      expect(minutesEl.parent().hasClass('has-error')).toBe(false);
+      expect(element.hasClass('ng-invalid-time')).toBe(false);
+    });
+  });
+
+  describe('custom template and controllerAs', function() {
+    it('should allow custom templates', function() {
+      $templateCache.put('foo/bar.html', '<div>baz</div>');
+
+      element = $compile('<uib-timepicker ng-model="time" template-url="foo/bar.html"></uib-timepicker>')($rootScope);
+      $rootScope.$digest();
+      expect(element[0].tagName.toLowerCase()).toBe('div');
+      expect(element.html()).toBe('baz');
+    });
+
+    it('should expose the controller on the view', function() {
+      $templateCache.put('template/timepicker/timepicker.html', '<div><div>{{timepicker.text}}</div></div>');
+
+      element = $compile('<uib-timepicker ng-model="time"></uib-timepicker>')($rootScope);
+      $rootScope.$digest();
+
+      var ctrl = element.controller('uibTimepicker');
+      expect(ctrl).toBeDefined();
+
+      ctrl.text = 'foo';
+      $rootScope.$digest();
+
+      expect(element.html()).toBe('<div class="ng-binding">foo</div>');
+    });
+  });
+});
+
+/* Deprecation tests below */
+
+describe('timepicker deprecation', function() {
+  beforeEach(module('ui.bootstrap.timepicker'));
+  beforeEach(module('ngAnimateMock'));
+  beforeEach(module('template/timepicker/timepicker.html'));
+
+  it('should suppress warning', function() {
+    module(function($provide) {
+      $provide.value('$timepickerSuppressWarning', true);
+    });
+
+    inject(function($compile, $log, $rootScope) {
+      spyOn($log, 'warn');
+
+      $rootScope.time = new Date().setHours(14, 40, 0, 0);
+      var element = '<timepicker ng-model="time"></timepicker>';
+      element = $compile(element)($rootScope);
+      $rootScope.$digest();
+      expect($log.warn.calls.count()).toBe(0);
+    });
+  });
+
+  it('should give warning by default', inject(function($compile, $log, $rootScope) {
+    spyOn($log, 'warn');
+
+      $rootScope.time = new Date().setHours(14, 40, 0, 0);
+    var element = '<timepicker ng-model="time"></timepicker>';
+    element = $compile(element)($rootScope);
+    $rootScope.$digest();
+
+    expect($log.warn.calls.count()).toBe(2);
+    expect($log.warn.calls.argsFor(0)).toEqual(['TimepickerController is now deprecated. Use UibTimepickerController instead.']);
+    expect($log.warn.calls.argsFor(1)).toEqual(['timepicker is now deprecated. Use uib-timepicker instead.']);
+  }));
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/timepicker/timepicker.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/timepicker/timepicker.js
new file mode 100644
index 0000000..0c5173e
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/timepicker/timepicker.js
@@ -0,0 +1,426 @@
+angular.module('ui.bootstrap.timepicker', [])
+
+.constant('uibTimepickerConfig', {
+  hourStep: 1,
+  minuteStep: 1,
+  showMeridian: true,
+  meridians: null,
+  readonlyInput: false,
+  mousewheel: true,
+  arrowkeys: true,
+  showSpinners: true
+})
+
+.controller('UibTimepickerController', ['$scope', '$element', '$attrs', '$parse', '$log', '$locale', 'uibTimepickerConfig', function($scope, $element, $attrs, $parse, $log, $locale, timepickerConfig) {
+  var selected = new Date(),
+      ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
+      meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS;
+
+  $scope.tabindex = angular.isDefined($attrs.tabindex) ? $attrs.tabindex : 0;
+  $element.removeAttr('tabindex');
+
+  this.init = function(ngModelCtrl_, inputs) {
+    ngModelCtrl = ngModelCtrl_;
+    ngModelCtrl.$render = this.render;
+
+    ngModelCtrl.$formatters.unshift(function(modelValue) {
+      return modelValue ? new Date(modelValue) : null;
+    });
+
+    var hoursInputEl = inputs.eq(0),
+        minutesInputEl = inputs.eq(1);
+
+    var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel;
+    if (mousewheel) {
+      this.setupMousewheelEvents(hoursInputEl, minutesInputEl);
+    }
+
+    var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys;
+    if (arrowkeys) {
+      this.setupArrowkeyEvents(hoursInputEl, minutesInputEl);
+    }
+
+    $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput;
+    this.setupInputEvents(hoursInputEl, minutesInputEl);
+  };
+
+  var hourStep = timepickerConfig.hourStep;
+  if ($attrs.hourStep) {
+    $scope.$parent.$watch($parse($attrs.hourStep), function(value) {
+      hourStep = parseInt(value, 10);
+    });
+  }
+
+  var minuteStep = timepickerConfig.minuteStep;
+  if ($attrs.minuteStep) {
+    $scope.$parent.$watch($parse($attrs.minuteStep), function(value) {
+      minuteStep = parseInt(value, 10);
+    });
+  }
+
+  var min;
+  $scope.$parent.$watch($parse($attrs.min), function(value) {
+    var dt = new Date(value);
+    min = isNaN(dt) ? undefined : dt;
+  });
+
+  var max;
+  $scope.$parent.$watch($parse($attrs.max), function(value) {
+    var dt = new Date(value);
+    max = isNaN(dt) ? undefined : dt;
+  });
+
+  $scope.noIncrementHours = function() {
+    var incrementedSelected = addMinutes(selected, hourStep * 60);
+    return incrementedSelected > max ||
+      (incrementedSelected < selected && incrementedSelected < min);
+  };
+
+  $scope.noDecrementHours = function() {
+    var decrementedSelected = addMinutes(selected, -hourStep * 60);
+    return decrementedSelected < min ||
+      (decrementedSelected > selected && decrementedSelected > max);
+  };
+
+  $scope.noIncrementMinutes = function() {
+    var incrementedSelected = addMinutes(selected, minuteStep);
+    return incrementedSelected > max ||
+      (incrementedSelected < selected && incrementedSelected < min);
+  };
+
+  $scope.noDecrementMinutes = function() {
+    var decrementedSelected = addMinutes(selected, -minuteStep);
+    return decrementedSelected < min ||
+      (decrementedSelected > selected && decrementedSelected > max);
+  };
+
+  $scope.noToggleMeridian = function() {
+    if (selected.getHours() < 13) {
+      return addMinutes(selected, 12 * 60) > max;
+    } else {
+      return addMinutes(selected, -12 * 60) < min;
+    }
+  };
+
+  // 12H / 24H mode
+  $scope.showMeridian = timepickerConfig.showMeridian;
+  if ($attrs.showMeridian) {
+    $scope.$parent.$watch($parse($attrs.showMeridian), function(value) {
+      $scope.showMeridian = !!value;
+
+      if (ngModelCtrl.$error.time) {
+        // Evaluate from template
+        var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate();
+        if (angular.isDefined(hours) && angular.isDefined(minutes)) {
+          selected.setHours(hours);
+          refresh();
+        }
+      } else {
+        updateTemplate();
+      }
+    });
+  }
+
+  // Get $scope.hours in 24H mode if valid
+  function getHoursFromTemplate() {
+    var hours = parseInt($scope.hours, 10);
+    var valid = $scope.showMeridian ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24);
+    if (!valid) {
+      return undefined;
+    }
+
+    if ($scope.showMeridian) {
+      if (hours === 12) {
+        hours = 0;
+      }
+      if ($scope.meridian === meridians[1]) {
+        hours = hours + 12;
+      }
+    }
+    return hours;
+  }
+
+  function getMinutesFromTemplate() {
+    var minutes = parseInt($scope.minutes, 10);
+    return (minutes >= 0 && minutes < 60) ? minutes : undefined;
+  }
+
+  function pad(value) {
+    return (angular.isDefined(value) && value.toString().length < 2) ? '0' + value : value.toString();
+  }
+
+  // Respond on mousewheel spin
+  this.setupMousewheelEvents = function(hoursInputEl, minutesInputEl) {
+    var isScrollingUp = function(e) {
+      if (e.originalEvent) {
+        e = e.originalEvent;
+      }
+      //pick correct delta variable depending on event
+      var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY;
+      return (e.detail || delta > 0);
+    };
+
+    hoursInputEl.bind('mousewheel wheel', function(e) {
+      $scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours());
+      e.preventDefault();
+    });
+
+    minutesInputEl.bind('mousewheel wheel', function(e) {
+      $scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes());
+      e.preventDefault();
+    });
+
+  };
+
+  // Respond on up/down arrowkeys
+  this.setupArrowkeyEvents = function(hoursInputEl, minutesInputEl) {
+    hoursInputEl.bind('keydown', function(e) {
+      if (e.which === 38) { // up
+        e.preventDefault();
+        $scope.incrementHours();
+        $scope.$apply();
+      } else if (e.which === 40) { // down
+        e.preventDefault();
+        $scope.decrementHours();
+        $scope.$apply();
+      }
+    });
+
+    minutesInputEl.bind('keydown', function(e) {
+      if (e.which === 38) { // up
+        e.preventDefault();
+        $scope.incrementMinutes();
+        $scope.$apply();
+      } else if (e.which === 40) { // down
+        e.preventDefault();
+        $scope.decrementMinutes();
+        $scope.$apply();
+      }
+    });
+  };
+
+  this.setupInputEvents = function(hoursInputEl, minutesInputEl) {
+    if ($scope.readonlyInput) {
+      $scope.updateHours = angular.noop;
+      $scope.updateMinutes = angular.noop;
+      return;
+    }
+
+    var invalidate = function(invalidHours, invalidMinutes) {
+      ngModelCtrl.$setViewValue(null);
+      ngModelCtrl.$setValidity('time', false);
+      if (angular.isDefined(invalidHours)) {
+        $scope.invalidHours = invalidHours;
+      }
+      if (angular.isDefined(invalidMinutes)) {
+        $scope.invalidMinutes = invalidMinutes;
+      }
+    };
+
+    $scope.updateHours = function() {
+      var hours = getHoursFromTemplate(),
+        minutes = getMinutesFromTemplate();
+
+      if (angular.isDefined(hours) && angular.isDefined(minutes)) {
+        selected.setHours(hours);
+        if (selected < min || selected > max) {
+          invalidate(true);
+        } else {
+          refresh('h');
+        }
+      } else {
+        invalidate(true);
+      }
+    };
+
+    hoursInputEl.bind('blur', function(e) {
+      if (!$scope.invalidHours && $scope.hours < 10) {
+        $scope.$apply(function() {
+          $scope.hours = pad($scope.hours);
+        });
+      }
+    });
+
+    $scope.updateMinutes = function() {
+      var minutes = getMinutesFromTemplate(),
+        hours = getHoursFromTemplate();
+
+      if (angular.isDefined(minutes) && angular.isDefined(hours)) {
+        selected.setMinutes(minutes);
+        if (selected < min || selected > max) {
+          invalidate(undefined, true);
+        } else {
+          refresh('m');
+        }
+      } else {
+        invalidate(undefined, true);
+      }
+    };
+
+    minutesInputEl.bind('blur', function(e) {
+      if (!$scope.invalidMinutes && $scope.minutes < 10) {
+        $scope.$apply(function() {
+          $scope.minutes = pad($scope.minutes);
+        });
+      }
+    });
+
+  };
+
+  this.render = function() {
+    var date = ngModelCtrl.$viewValue;
+
+    if (isNaN(date)) {
+      ngModelCtrl.$setValidity('time', false);
+      $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
+    } else {
+      if (date) {
+        selected = date;
+      }
+
+      if (selected < min || selected > max) {
+        ngModelCtrl.$setValidity('time', false);
+        $scope.invalidHours = true;
+        $scope.invalidMinutes = true;
+      } else {
+        makeValid();
+      }
+      updateTemplate();
+    }
+  };
+
+  // Call internally when we know that model is valid.
+  function refresh(keyboardChange) {
+    makeValid();
+    ngModelCtrl.$setViewValue(new Date(selected));
+    updateTemplate(keyboardChange);
+  }
+
+  function makeValid() {
+    ngModelCtrl.$setValidity('time', true);
+    $scope.invalidHours = false;
+    $scope.invalidMinutes = false;
+  }
+
+  function updateTemplate(keyboardChange) {
+    var hours = selected.getHours(), minutes = selected.getMinutes();
+
+    if ($scope.showMeridian) {
+      hours = (hours === 0 || hours === 12) ? 12 : hours % 12; // Convert 24 to 12 hour system
+    }
+
+    $scope.hours = keyboardChange === 'h' ? hours : pad(hours);
+    if (keyboardChange !== 'm') {
+      $scope.minutes = pad(minutes);
+    }
+    $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
+  }
+
+  function addMinutes(date, minutes) {
+    var dt = new Date(date.getTime() + minutes * 60000);
+    var newDate = new Date(date);
+    newDate.setHours(dt.getHours(), dt.getMinutes());
+    return newDate;
+  }
+
+  function addMinutesToSelected(minutes) {
+    selected = addMinutes(selected, minutes);
+    refresh();
+  }
+
+  $scope.showSpinners = angular.isDefined($attrs.showSpinners) ?
+    $scope.$parent.$eval($attrs.showSpinners) : timepickerConfig.showSpinners;
+
+  $scope.incrementHours = function() {
+    if (!$scope.noIncrementHours()) {
+      addMinutesToSelected(hourStep * 60);
+    }
+  };
+
+  $scope.decrementHours = function() {
+    if (!$scope.noDecrementHours()) {
+      addMinutesToSelected(-hourStep * 60);
+    }
+  };
+
+  $scope.incrementMinutes = function() {
+    if (!$scope.noIncrementMinutes()) {
+      addMinutesToSelected(minuteStep);
+    }
+  };
+
+  $scope.decrementMinutes = function() {
+    if (!$scope.noDecrementMinutes()) {
+      addMinutesToSelected(-minuteStep);
+    }
+  };
+
+  $scope.toggleMeridian = function() {
+    if (!$scope.noToggleMeridian()) {
+      addMinutesToSelected(12 * 60 * (selected.getHours() < 12 ? 1 : -1));
+    }
+  };
+}])
+
+.directive('uibTimepicker', function() {
+  return {
+    restrict: 'EA',
+    require: ['uibTimepicker', '?^ngModel'],
+    controller: 'UibTimepickerController',
+    controllerAs: 'timepicker',
+    replace: true,
+    scope: {},
+    templateUrl: function(element, attrs) {
+      return attrs.templateUrl || 'template/timepicker/timepicker.html';
+    },
+    link: function(scope, element, attrs, ctrls) {
+      var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
+
+      if (ngModelCtrl) {
+        timepickerCtrl.init(ngModelCtrl, element.find('input'));
+      }
+    }
+  };
+});
+
+/* Deprecated timepicker below */
+
+angular.module('ui.bootstrap.timepicker')
+
+.value('$timepickerSuppressWarning', false)
+
+.controller('TimepickerController', ['$scope', '$element', '$attrs', '$controller', '$log', '$timepickerSuppressWarning', function($scope, $element, $attrs, $controller, $log, $timepickerSuppressWarning) {
+  if (!$timepickerSuppressWarning) {
+    $log.warn('TimepickerController is now deprecated. Use UibTimepickerController instead.');
+  }
+
+  angular.extend(this, $controller('UibTimepickerController', {
+    $scope: $scope,
+    $element: $element,
+    $attrs: $attrs
+  }));
+}])
+
+.directive('timepicker', ['$log', '$timepickerSuppressWarning', function($log, $timepickerSuppressWarning) {
+  return {
+    restrict: 'EA',
+    require: ['timepicker', '?^ngModel'],
+    controller: 'TimepickerController',
+    controllerAs: 'timepicker',
+    replace: true,
+    scope: {},
+    templateUrl: function(element, attrs) {
+      return attrs.templateUrl || 'template/timepicker/timepicker.html';
+    },
+    link: function(scope, element, attrs, ctrls) {
+      if (!$timepickerSuppressWarning) {
+        $log.warn('timepicker is now deprecated. Use uib-timepicker instead.');
+      }
+      var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
+
+      if (ngModelCtrl) {
+        timepickerCtrl.init(ngModelCtrl, element.find('input'));
+      }
+    }
+  };
+}]);
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tooltip/docs/demo.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tooltip/docs/demo.html
new file mode 100644
index 0000000..795f365
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tooltip/docs/demo.html
@@ -0,0 +1,69 @@
+<div ng-controller="TooltipDemoCtrl">
+    <div class="form-group">
+      <label>Dynamic Tooltip Text</label>
+      <input type="text" ng-model="dynamicTooltipText" class="form-control">
+    </div>
+    <div class="form-group">
+      <label>Dynamic Tooltip Popup Text</label>
+      <input type="text" ng-model="dynamicTooltip" class="form-control">
+    </div>
+    <p>
+      Pellentesque <a href="#" uib-tooltip="{{dynamicTooltip}}">{{dynamicTooltipText}}</a>,
+      sit amet venenatis urna cursus eget nunc scelerisque viverra mauris, in
+      aliquam. Tincidunt lobortis feugiat vivamus at
+      <a href="#" tooltip-placement="left" uib-tooltip="On the Left!">left</a> eget
+      arcu dictum varius duis at consectetur lorem. Vitae elementum curabitur
+      <a href="#" tooltip-placement="right" uib-tooltip="On the Right!">right</a>
+      nunc sed velit dignissim sodales ut eu sem integer vitae. Turpis egestas
+      <a href="#" tooltip-placement="bottom" uib-tooltip="On the Bottom!">bottom</a>
+      pharetra convallis posuere morbi leo urna,
+      <a href="#" tooltip-animation="false" uib-tooltip="I don't fade. :-(">fading</a>
+      at elementum eu, facilisis sed odio morbi quis commodo odio. In cursus
+      <a href="#" tooltip-popup-delay='1000' uib-tooltip='appears with delay'>show delay</a>
+      turpis massa tincidunt dui ut. In cursus
+      <a href="#" tooltip-popup-close-delay='1000' uib-tooltip='hides with delay'>hide delay</a>
+      <a href="#" uib-tooltip-template="'myTooltipTemplate.html'">Custom template</a>
+      nunc sed velit dignissim sodales ut eu sem integer vitae. Turpis egestas
+    </p>
+
+    <p>
+        I can even contain HTML. <a href="#" uib-tooltip-html="htmlTooltip">Check me out!</a>
+    </p>
+
+    <p>
+        <style>
+            /* Specify styling for tooltip contents */
+            .tooltip.customClass .tooltip-inner {
+                color: #880000;
+                background-color: #ffff66;
+                box-shadow: 0 6px 12px rgba(0,0,0,.175);
+            }
+            /* Hide arrow */
+            .tooltip.customClass .tooltip-arrow {
+                display: none;
+            }
+        </style>
+        I can have a custom class. <a href="#" uib-tooltip="I can have a custom class applied to me!" tooltip-class="customClass">Check me out!</a>
+    </p>
+
+    <form role="form">
+      <div class="form-group">
+        <label>Or use custom triggers, like focus: </label>
+        <input type="text" value="Click me!" uib-tooltip="See? Now click away..." tooltip-trigger="focus" tooltip-placement="right" class="form-control" />
+      </div>
+
+      <div class="form-group" ng-class="{'has-error' : !inputModel}">
+        <label>Disable tooltips conditionally:</label>
+        <input type="text" ng-model="inputModel" class="form-control"
+          placeholder="Hover over this for a tooltip until this is filled"
+          uib-tooltip="Enter something in this input field to disable this tooltip"
+          tooltip-placement="top"
+          tooltip-trigger="mouseenter"
+          tooltip-enable="!inputModel" />
+      </div>
+    </form>
+
+    <script type="text/ng-template" id="myTooltipTemplate.html">
+      <span>Special Tooltip with <strong>markup</strong> and {{ dynamicTooltipText }}</span>
+    </script>
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tooltip/docs/demo.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tooltip/docs/demo.js
new file mode 100644
index 0000000..18c2794
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tooltip/docs/demo.js
@@ -0,0 +1,5 @@
+angular.module('ui.bootstrap.demo').controller('TooltipDemoCtrl', function ($scope, $sce) {
+  $scope.dynamicTooltip = 'Hello, World!';
+  $scope.dynamicTooltipText = 'dynamic';
+  $scope.htmlTooltip = $sce.trustAsHtml('I\'ve been made <b>bold</b>!');
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tooltip/docs/readme.md b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tooltip/docs/readme.md
new file mode 100644
index 0000000..16d4cdb
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tooltip/docs/readme.md
@@ -0,0 +1,80 @@
+A lightweight, extensible directive for fancy tooltip creation. The tooltip
+directive supports multiple placements, optional transition animation, and more.
+
+There are three versions of the tooltip: `uib-tooltip`, `uib-tooltip-template`, and
+`uib-tooltip-html-unsafe`:
+
+- `uib-tooltip` takes text only and will escape any HTML provided.
+- `uib-tooltip-template` takes text that specifies the location of a template to
+  use for the tooltip. Note that this needs to be wrapped in a tag.
+- `uib-tooltip-html` takes
+  whatever HTML is provided and displays it in a tooltip; *The user is responsible for ensuring the
+  content is safe to put into the DOM!*
+- `uib-tooltip-html-unsafe` -- deprecated in favour of `tooltip-html`
+
+The tooltip directives provide several optional attributes to control how they
+will display:
+
+- `tooltip-placement`: Where to place it? Defaults to "top", but also accepts
+  "bottom", "left", "right".
+- `tooltip-animation`: Should it fade in and out? Defaults to "true".
+- `tooltip-popup-delay`: For how long should the user have to have the mouse
+  over the element before the tooltip shows (in milliseconds)? Defaults to 0.
+- `tooltip-close-popup-delay`: For how long should the tooltip remain open
+  after the close trigger event? Defaults to 0.
+- `tooltip-trigger`: What should trigger a show of the tooltip? Supports a space separated list of event names.
+  Note: this attribute is no longer observable. See `tooltip-enable`.
+- `tooltip-enable`: Is it enabled? It will enable or disable the configured
+  `tooltip-trigger`.
+- `tooltip-append-to-body`: Should the tooltip be appended to `$body` instead of
+  the parent element?
+- `tooltip-class`: Custom class to be applied to the tooltip.
+- `tooltip-is-open` <i class="glyphicon glyphicon-eye-open"></i>
+  _(Default: false)_:
+  Whether to show the tooltip.
+
+The tooltip directives require the `$position` service.
+
+**Triggers**
+
+The following show triggers are supported out of the box, along with their
+provided hide triggers:
+
+- `mouseenter`: `mouseleave`
+- `click`: `click`
+- `focus`: `blur`
+- `none`: ``
+
+For any non-supported value, the trigger will be used to both show and hide the
+tooltip. Using the 'none' trigger will disable the internal trigger(s), one can
+then use the `tooltip-is-open` attribute exclusively to show and hide the tooltip.
+
+**$uibTooltipProvider**
+
+Through the `$uibTooltipProvider`, you can change the way tooltips and popovers
+behave by default; the attributes above always take precedence. The following
+methods are available:
+
+- `setTriggers(obj)`: Extends the default trigger mappings mentioned above
+  with mappings of your own. E.g. `{ 'openTrigger': 'closeTrigger' }`.
+- `options(obj)`: Provide a set of defaults for certain tooltip and popover
+  attributes. Currently supports 'placement', 'animation', 'popupDelay', and
+  `appendToBody`. Here are the defaults:
+
+  <pre>
+  placement: 'top',
+  animation: true,
+  popupDelay: 0,
+  popupCloseDelay: 500,
+  appendToBody: false
+  </pre>
+
+**Known issues**
+
+For Safari 7+ support, if you want to use the **focus** `tooltip-trigger`, you need to use an anchor tag with a tab index. For example:
+
+```
+<a tabindex="0" uib-tooltip="Test" tooltip-trigger="focus" class="btn btn-default">
+  Click Me
+</a>
+```
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tooltip/test/tooltip-template.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tooltip/test/tooltip-template.spec.js
new file mode 100644
index 0000000..ad1c973
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tooltip/test/tooltip-template.spec.js
@@ -0,0 +1,146 @@
+describe('tooltip template', function() {
+  var elm,
+      elmBody,
+      scope,
+      elmScope,
+      tooltipScope;
+
+  // load the popover code
+  beforeEach(module('ui.bootstrap.tooltip'));
+
+  // load the template
+  beforeEach(module('template/tooltip/tooltip-template-popup.html'));
+
+  beforeEach(inject(function($templateCache) {
+    $templateCache.put('myUrl', [200, '<span>{{ myTemplateText }}</span>', {}]);
+  }));
+
+  beforeEach(inject(function($rootScope, $compile) {
+    elmBody = angular.element(
+      '<div><span uib-tooltip-template="templateUrl">Selector Text</span></div>'
+    );
+
+    scope = $rootScope;
+    $compile(elmBody)(scope);
+    scope.templateUrl = 'myUrl';
+
+    scope.$digest();
+    elm = elmBody.find('span');
+    elmScope = elm.scope();
+    tooltipScope = elmScope.$$childTail;
+  }));
+
+  function trigger(element, evt) {
+    evt = new Event(evt);
+
+    element[0].dispatchEvent(evt);
+    element.scope().$$childTail.$digest();
+  }
+
+  it('should open on mouseenter', inject(function() {
+    trigger(elm, 'mouseenter');
+    expect(tooltipScope.isOpen).toBe(true);
+
+    expect(elmBody.children().length).toBe(2);
+  }));
+
+  it('should not open on mouseenter if templateUrl is empty', inject(function() {
+    scope.templateUrl = null;
+    scope.$digest();
+
+    trigger(elm, 'mouseenter');
+    expect(tooltipScope.isOpen).toBe(false);
+
+    expect(elmBody.children().length).toBe(1);
+  }));
+
+  it('should show updated text', inject(function() {
+    scope.myTemplateText = 'some text';
+
+    trigger(elm, 'mouseenter');
+    expect(tooltipScope.isOpen).toBe(true);
+    scope.$digest();
+
+    expect(elmBody.children().eq(1).text().trim()).toBe('some text');
+
+    scope.myTemplateText = 'new text';
+    scope.$digest();
+
+    expect(elmBody.children().eq(1).text().trim()).toBe('new text');
+  }));
+
+  it('should hide tooltip when template becomes empty', inject(function($timeout) {
+    trigger(elm, 'mouseenter');
+    expect(tooltipScope.isOpen).toBe(true);
+
+    scope.templateUrl = '';
+    scope.$digest();
+
+    expect(tooltipScope.isOpen).toBe(false);
+
+    $timeout.flush();
+    expect(elmBody.children().length).toBe(1);
+  }));
+});
+
+/* Deprecation tests below */
+
+describe('tooltip template deprecation', function() {
+  beforeEach(module('ui.bootstrap.tooltip'));
+  beforeEach(module('template/tooltip/tooltip-template-popup.html'));
+
+  var elm, elmBody, elmScope, tooltipScope;
+
+  function trigger(element, evt) {
+    evt = new Event(evt);
+
+    element[0].dispatchEvent(evt);
+    element.scope().$$childTail.$digest();
+  }
+
+  it('should suppress warning', function() {
+    module(function($provide) {
+      $provide.value('$tooltipSuppressWarning', true);
+    });
+
+    inject(function($compile, $log, $rootScope, $templateCache) {
+      spyOn($log, 'warn');
+      $templateCache.put('myUrl', [200, '<span>{{ myTemplateText }}</span>', {}]);
+      $rootScope.templateUrl = 'myUrl';
+
+      elmBody = angular.element('<div><span tooltip-template="templateUrl">Selector Text</span></div>');
+      $compile(elmBody)($rootScope);
+      $rootScope.$digest();
+      elm = elmBody.find('span');
+      elmScope = elm.scope();
+      tooltipScope = elmScope.$$childTail;
+
+      trigger(elm, 'mouseenter');
+
+      expect($log.warn.calls.count()).toBe(0);
+    });
+  });
+
+  it('should give warning by default', inject(function($compile, $log, $rootScope, $templateCache) {
+    spyOn($log, 'warn');
+    $templateCache.put('myUrl', [200, '<span>{{ myTemplateText }}</span>', {}]);
+    $rootScope.templateUrl = 'myUrl';
+
+    var element = '<div><span tooltip-template="templateUrl">Selector Text</span></div>';
+    element = $compile(element)($rootScope);
+    $rootScope.$digest();
+
+    elmBody = angular.element('<div><span tooltip-template="templateUrl">Selector Text</span></div>');
+    $compile(elmBody)($rootScope);
+    $rootScope.$digest();
+    elm = elmBody.find('span');
+    elmScope = elm.scope();
+    tooltipScope = elmScope.$$childTail;
+
+    trigger(elm, 'mouseenter');
+
+    expect($log.warn.calls.count()).toBe(2);
+    expect($log.warn.calls.argsFor(0)).toEqual(['$tooltip is now deprecated. Use $uibTooltip instead.']);
+    expect($log.warn.calls.argsFor(1)).toEqual(['tooltip-template-popup is now deprecated. Use uib-tooltip-template-popup instead.']);
+  }));
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tooltip/test/tooltip.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tooltip/test/tooltip.spec.js
new file mode 100644
index 0000000..bbc8efb
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tooltip/test/tooltip.spec.js
@@ -0,0 +1,1070 @@
+describe('tooltip', function() {
+  var elm,
+      elmBody,
+      scope,
+      elmScope,
+      tooltipScope,
+      $document;
+
+  // load the tooltip code
+  beforeEach(module('ui.bootstrap.tooltip'));
+
+  // load the template
+  beforeEach(module('template/tooltip/tooltip-popup.html'));
+
+  beforeEach(inject(function($rootScope, $compile, _$document_) {
+    elmBody = angular.element(
+      '<div><span uib-tooltip="tooltip text" tooltip-animation="false">Selector Text</span></div>'
+    );
+
+    $document = _$document_;
+    scope = $rootScope;
+    $compile(elmBody)(scope);
+    scope.$digest();
+    elm = elmBody.find('span');
+    elmScope = elm.scope();
+    tooltipScope = elmScope.$$childTail;
+  }));
+
+  function trigger(element, evt) {
+    evt = new Event(evt);
+
+    element[0].dispatchEvent(evt);
+    element.scope().$$childTail.$digest();
+  }
+
+  it('should not be open initially', inject(function() {
+    expect(tooltipScope.isOpen).toBe(false);
+
+    // We can only test *that* the tooltip-popup element wasn't created as the
+    // implementation is templated and replaced.
+    expect(elmBody.children().length).toBe(1);
+  }));
+
+  it('should open on mouseenter', inject(function() {
+    trigger(elm, 'mouseenter');
+    expect(tooltipScope.isOpen).toBe(true);
+
+    // We can only test *that* the tooltip-popup element was created as the
+    // implementation is templated and replaced.
+    expect(elmBody.children().length).toBe(2);
+  }));
+
+  it('should close on mouseleave', inject(function() {
+    trigger(elm, 'mouseenter');
+    trigger(elm, 'mouseleave');
+    expect(tooltipScope.isOpen).toBe(false);
+  }));
+
+  it('should not animate on animation set to false', inject(function() {
+    expect(tooltipScope.animation).toBe(false);
+  }));
+
+  it('should have default placement of "top"', inject(function() {
+    trigger(elm, 'mouseenter');
+    expect(tooltipScope.placement).toBe('top');
+  }));
+
+  it('should allow specification of placement', inject(function($compile) {
+    elm = $compile(angular.element(
+      '<span uib-tooltip="tooltip text" tooltip-placement="bottom">Selector Text</span>'
+    ))(scope);
+    scope.$apply();
+    elmScope = elm.scope();
+    tooltipScope = elmScope.$$childTail;
+
+    trigger(elm, 'mouseenter');
+    expect(tooltipScope.placement).toBe('bottom');
+  }));
+
+  it('should update placement dynamically', inject(function($compile, $timeout) {
+    scope.place = 'bottom';
+    elm = $compile(angular.element(
+      '<span uib-tooltip="tooltip text" tooltip-placement="{{place}}">Selector Text</span>'
+    ))(scope);
+    scope.$apply();
+    elmScope = elm.scope();
+    tooltipScope = elmScope.$$childTail;
+
+    trigger(elm, 'mouseenter');
+    expect(tooltipScope.placement).toBe('bottom');
+
+    scope.place = 'right';
+    scope.$digest();
+    $timeout.flush();
+    expect(tooltipScope.placement).toBe('right');
+  }));
+
+  it('should work inside an ngRepeat', inject(function($compile) {
+    elm = $compile(angular.element(
+      '<ul>'+
+        '<li ng-repeat="item in items">'+
+          '<span uib-tooltip="{{item.tooltip}}">{{item.name}}</span>'+
+        '</li>'+
+      '</ul>'
+    ))(scope);
+
+    scope.items = [
+      { name: 'One', tooltip: 'First Tooltip' }
+    ];
+
+    scope.$digest();
+
+    var tt = angular.element(elm.find('li > span')[0]);
+    trigger(tt, 'mouseenter');
+
+    expect(tt.text()).toBe(scope.items[0].name);
+
+    tooltipScope = tt.scope().$$childTail;
+    expect(tooltipScope.content).toBe(scope.items[0].tooltip);
+
+    trigger(tt, 'mouseleave');
+    expect(tooltipScope.isOpen).toBeFalsy();
+  }));
+
+  it('should show correct text when in an ngRepeat', inject(function($compile, $timeout) {
+    elm = $compile(angular.element(
+      '<ul>'+
+        '<li ng-repeat="item in items">'+
+          '<span uib-tooltip="{{item.tooltip}}">{{item.name}}</span>'+
+        '</li>'+
+      '</ul>'
+    ))(scope);
+
+    scope.items = [
+      { name: 'One', tooltip: 'First Tooltip' },
+      { name: 'Second', tooltip: 'Second Tooltip' }
+    ];
+
+    scope.$digest();
+
+    var tt_1 = angular.element(elm.find('li > span')[0]);
+    var tt_2 = angular.element(elm.find('li > span')[1]);
+
+    trigger(tt_1, 'mouseenter');
+    trigger(tt_1, 'mouseleave');
+
+    $timeout.flush();
+
+    trigger(tt_2, 'mouseenter');
+
+    expect(tt_1.text()).toBe(scope.items[0].name);
+    expect(tt_2.text()).toBe(scope.items[1].name);
+
+    tooltipScope = tt_2.scope().$$childTail;
+    expect(tooltipScope.content).toBe(scope.items[1].tooltip);
+    expect(elm.find('.tooltip-inner').text()).toBe(scope.items[1].tooltip);
+
+    trigger(tt_2, 'mouseleave');
+  }));
+
+  it('should only have an isolate scope on the popup', inject(function($compile) {
+    var ttScope;
+
+    scope.tooltipMsg = 'Tooltip Text';
+    scope.alt = 'Alt Message';
+
+    elmBody = $compile(angular.element(
+      '<div><span alt={{alt}} uib-tooltip="{{tooltipMsg}}" tooltip-animation="false">Selector Text</span></div>'
+    ))(scope);
+
+    $compile(elmBody)(scope);
+    scope.$digest();
+    elm = elmBody.find('span');
+    elmScope = elm.scope();
+
+    trigger(elm, 'mouseenter');
+    expect(elm.attr('alt')).toBe(scope.alt);
+
+    ttScope = angular.element(elmBody.children()[1]).isolateScope();
+    expect(ttScope.placement).toBe('top');
+    expect(ttScope.content).toBe(scope.tooltipMsg);
+
+    trigger(elm, 'mouseleave');
+
+    //Isolate scope contents should be the same after hiding and showing again (issue 1191)
+    trigger(elm, 'mouseenter');
+
+    ttScope = angular.element(elmBody.children()[1]).isolateScope();
+    expect(ttScope.placement).toBe('top');
+    expect(ttScope.content).toBe(scope.tooltipMsg);
+  }));
+
+  it('should not show tooltips if there is nothing to show - issue #129', inject(function($compile) {
+    elmBody = $compile(angular.element(
+      '<div><span uib-tooltip="">Selector Text</span></div>'
+    ))(scope);
+    scope.$digest();
+    elmBody.find('span').trigger('mouseenter');
+
+    expect(elmBody.children().length).toBe(1);
+  }));
+
+  it('should close the tooltip when its trigger element is destroyed', inject(function() {
+    trigger(elm, 'mouseenter');
+    expect(tooltipScope.isOpen).toBe(true);
+
+    elm.remove();
+    elmScope.$destroy();
+    expect(elmBody.children().length).toBe(0);
+  }));
+
+  it('issue 1191 - scope on the popup should always be child of correct element scope', function() {
+    var ttScope;
+    trigger(elm, 'mouseenter');
+
+    ttScope = angular.element(elmBody.children()[1]).scope();
+    expect(ttScope.$parent).toBe(tooltipScope);
+
+    trigger(elm, 'mouseleave');
+
+    // After leaving and coming back, the scope's parent should be the same
+    trigger(elm, 'mouseenter');
+
+    ttScope = angular.element(elmBody.children()[1]).scope();
+    expect(ttScope.$parent).toBe(tooltipScope);
+
+    trigger(elm, 'mouseleave');
+  });
+
+  describe('with specified enable expression', function() {
+    beforeEach(inject(function($compile) {
+      scope.enable = false;
+      elmBody = $compile(angular.element(
+        '<div><span uib-tooltip="tooltip text" tooltip-enable="enable">Selector Text</span></div>'
+      ))(scope);
+      scope.$digest();
+      elm = elmBody.find('span');
+      elmScope = elm.scope();
+      tooltipScope = elmScope.$$childTail;
+    }));
+
+    it('should not open ', inject(function() {
+      trigger(elm, 'mouseenter');
+      expect(tooltipScope.isOpen).toBeFalsy();
+      expect(elmBody.children().length).toBe(1);
+    }));
+
+    it('should open', inject(function() {
+      scope.enable = true;
+      scope.$digest();
+      trigger(elm, 'mouseenter');
+      expect(tooltipScope.isOpen).toBeTruthy();
+      expect(elmBody.children().length).toBe(2);
+    }));
+  });
+
+  describe('with specified popup delay', function() {
+    var $timeout;
+    beforeEach(inject(function($compile, _$timeout_) {
+      $timeout = _$timeout_;
+      scope.delay = '1000';
+      elm = $compile(angular.element(
+        '<span uib-tooltip="tooltip text" tooltip-popup-delay="{{delay}}" ng-disabled="disabled">Selector Text</span>'
+      ))(scope);
+      elmScope = elm.scope();
+      tooltipScope = elmScope.$$childTail;
+      scope.$digest();
+    }));
+
+    it('should open after timeout', function() {
+      trigger(elm, 'mouseenter');
+      expect(tooltipScope.isOpen).toBe(false);
+
+      $timeout.flush();
+      expect(tooltipScope.isOpen).toBe(true);
+    });
+
+    it('should not open if mouseleave before timeout', function() {
+      trigger(elm, 'mouseenter');
+      expect(tooltipScope.isOpen).toBe(false);
+
+      trigger(elm, 'mouseleave');
+      $timeout.flush();
+      expect(tooltipScope.isOpen).toBe(false);
+    });
+
+    it('should use default popup delay if specified delay is not a number', function() {
+      scope.delay = 'text1000';
+      scope.$digest();
+      trigger(elm, 'mouseenter');
+      expect(tooltipScope.isOpen).toBe(true);
+    });
+
+    it('should not open if disabled is present', function() {
+      trigger(elm, 'mouseenter');
+      expect(tooltipScope.isOpen).toBe(false);
+
+      $timeout.flush(500);
+      expect(tooltipScope.isOpen).toBe(false);
+      elmScope.disabled = true;
+      elmScope.$digest();
+
+      expect(tooltipScope.isOpen).toBe(false);
+    });
+
+    it('should open when not disabled after being disabled - issue #4204', function() {
+      trigger(elm, 'mouseenter');
+      expect(tooltipScope.isOpen).toBe(false);
+
+      $timeout.flush(500);
+      elmScope.disabled = true;
+      elmScope.$digest();
+
+      $timeout.flush(500);
+      expect(tooltipScope.isOpen).toBe(false);
+
+      elmScope.disabled = false;
+      elmScope.$digest();
+
+      trigger(elm, 'mouseenter');
+      $timeout.flush();
+
+      expect(tooltipScope.isOpen).toBe(true);
+    });
+
+    it('should close the tooltips in order', inject(function($compile) {
+      var elm2 = $compile('<div><span uib-tooltip="tooltip #2" tooltip-is-open="isOpen2">Selector Text</span></div>')(scope);
+      scope.$digest();
+      elm2 = elm2.find('span');
+      var tooltipScope2 = elm2.scope().$$childTail;
+      tooltipScope2.isOpen = false;
+      scope.$digest();
+
+      trigger(elm, 'mouseenter');
+      tooltipScope2.$digest();
+      $timeout.flush();
+      expect(tooltipScope.isOpen).toBe(true);
+      expect(tooltipScope2.isOpen).toBe(false);
+
+      trigger(elm2, 'mouseenter');
+      tooltipScope2.$digest();
+      $timeout.flush();
+      expect(tooltipScope.isOpen).toBe(true);
+      expect(tooltipScope2.isOpen).toBe(true);
+
+      var evt = $.Event('keypress');
+      evt.which = 27;
+
+      $document.trigger(evt);
+      tooltipScope.$digest();
+      tooltipScope2.$digest();
+
+      expect(tooltipScope.isOpen).toBe(true);
+      expect(tooltipScope2.isOpen).toBe(false);
+
+      var evt2 = $.Event('keypress');
+      evt2.which = 27;
+
+      $document.trigger(evt2);
+      tooltipScope.$digest();
+      tooltipScope2.$digest();
+
+      expect(tooltipScope.isOpen).toBe(false);
+      expect(tooltipScope2.isOpen).toBe(false);
+    }));
+  });
+
+  describe('with specified popup close delay', function() {
+    var $timeout;
+    beforeEach(inject(function($compile, _$timeout_) {
+      $timeout = _$timeout_;
+      scope.delay = '1000';
+      elm = $compile(angular.element(
+        '<span uib-tooltip="tooltip text" tooltip-popup-close-delay="{{delay}}" ng-disabled="disabled">Selector Text</span>'
+      ))(scope);
+      elmScope = elm.scope();
+      tooltipScope = elmScope.$$childTail;
+      scope.$digest();
+    }));
+
+    it('should close after timeout', function() {
+      trigger(elm, 'mouseenter');
+      expect(tooltipScope.isOpen).toBe(true);
+      trigger(elm, 'mouseleave');
+      $timeout.flush();
+      expect(tooltipScope.isOpen).toBe(false);
+    });
+
+    it('should use default popup close delay if specified delay is not a number and close immediately', function() {
+      scope.delay = 'text1000';
+      scope.$digest();
+      trigger(elm, 'mouseenter');
+      expect(tooltipScope.popupCloseDelay).toBe(0);
+      expect(tooltipScope.isOpen).toBe(true);
+      trigger(elm, 'mouseleave');
+      $timeout.flush();
+      expect(tooltipScope.isOpen).toBe(false);
+    });
+
+    it('should open when not disabled after being disabled and close after delay - issue #4204', function() {
+      trigger(elm, 'mouseenter');
+      expect(tooltipScope.isOpen).toBe(true);
+
+      elmScope.disabled = true;
+      elmScope.$digest();
+
+      $timeout.flush(500);
+      expect(tooltipScope.isOpen).toBe(false);
+
+      elmScope.disabled = false;
+      elmScope.$digest();
+
+      trigger(elm, 'mouseenter');
+
+      expect(tooltipScope.isOpen).toBe(true);
+      trigger(elm, 'mouseleave');
+      $timeout.flush();
+      expect(tooltipScope.isOpen).toBe(false);
+    });
+  });
+
+  describe('with specified popup and popup close delay', function() {
+    var $timeout;
+    beforeEach(inject(function($compile, _$timeout_) {
+      $timeout = _$timeout_;
+      scope.delay = '1000';
+      elm = $compile(angular.element(
+        '<span uib-tooltip="tooltip text" tooltip-popup-close-delay="{{delay}}" tooltip-popup-close-delay="{{delay}}" ng-disabled="disabled">Selector Text</span>'
+      ))(scope);
+      elmScope = elm.scope();
+      tooltipScope = elmScope.$$childTail;
+      scope.$digest();
+    }));
+
+    it('should not open if mouseleave before timeout', function() {
+      trigger(elm, 'mouseenter');
+      $timeout.flush(500);
+      trigger(elm, 'mouseleave');
+      $timeout.flush();
+
+      expect(tooltipScope.isOpen).toBe(false);
+    });
+  });
+
+  describe('with an is-open attribute', function() {
+    beforeEach(inject(function ($compile) {
+      scope.isOpen = false;
+      elm = $compile(angular.element(
+        '<span uib-tooltip="tooltip text" tooltip-is-open="isOpen" >Selector Text</span>'
+      ))(scope);
+      elmScope = elm.scope();
+      tooltipScope = elmScope.$$childTail;
+      scope.$digest();
+    }));
+
+    it('should show and hide with the controller value', function() {
+      expect(tooltipScope.isOpen).toBe(false);
+      elmScope.isOpen = true;
+      elmScope.$digest();
+      expect(tooltipScope.isOpen).toBe(true);
+      elmScope.isOpen = false;
+      elmScope.$digest();
+      expect(tooltipScope.isOpen).toBe(false);
+    });
+
+    it('should update the controller value', function() {
+      trigger(elm, 'mouseenter');
+      expect(elmScope.isOpen).toBe(true);
+      trigger(elm, 'mouseleave');
+      expect(elmScope.isOpen).toBe(false);
+    });
+  });
+
+  describe('with an is-open attribute expression', function() {
+    beforeEach(inject(function($compile) {
+      scope.isOpen = false;
+      elm = $compile(angular.element(
+        '<span uib-tooltip="tooltip text" tooltip-is-open="isOpen === true" >Selector Text</span>'
+      ))(scope);
+      elmScope = elm.scope();
+      tooltipScope = elmScope.$$childTail;
+      scope.$digest();
+    }));
+
+    it('should show and hide with the expression', function() {
+      expect(tooltipScope.isOpen).toBe(false);
+      elmScope.isOpen = true;
+      elmScope.$digest();
+      expect(tooltipScope.isOpen).toBe(true);
+      elmScope.isOpen = false;
+      elmScope.$digest();
+      expect(tooltipScope.isOpen).toBe(false);
+    });
+  });
+
+  describe('with a trigger attribute', function() {
+    var scope, elmBody, elm, elmScope;
+
+    beforeEach(inject(function($rootScope) {
+      scope = $rootScope;
+    }));
+
+    it('should use it to show but set the hide trigger based on the map for mapped triggers', inject(function($compile) {
+      elmBody = angular.element(
+        '<div><input uib-tooltip="Hello!" tooltip-trigger="focus" /></div>'
+      );
+      $compile(elmBody)(scope);
+      scope.$apply();
+      elm = elmBody.find('input');
+      elmScope = elm.scope();
+      tooltipScope = elmScope.$$childTail;
+
+      expect(tooltipScope.isOpen).toBeFalsy();
+      trigger(elm, 'focus');
+      expect(tooltipScope.isOpen).toBeTruthy();
+      trigger(elm, 'blur');
+      expect(tooltipScope.isOpen).toBeFalsy();
+    }));
+
+    it('should use it as both the show and hide triggers for unmapped triggers', inject(function($compile) {
+      elmBody = angular.element(
+        '<div><input uib-tooltip="Hello!" tooltip-trigger="fakeTriggerAttr" /></div>'
+      );
+      $compile(elmBody)(scope);
+      scope.$apply();
+      elm = elmBody.find('input');
+      elmScope = elm.scope();
+      tooltipScope = elmScope.$$childTail;
+
+      expect(tooltipScope.isOpen).toBeFalsy();
+      trigger(elm, 'fakeTriggerAttr');
+      expect(tooltipScope.isOpen).toBeTruthy();
+      trigger(elm, 'fakeTriggerAttr');
+      expect(tooltipScope.isOpen).toBeFalsy();
+    }));
+
+    it('should only set up triggers once', inject(function($compile) {
+      scope.test = true;
+      elmBody = angular.element(
+        '<div>' +
+          '<input uib-tooltip="Hello!" tooltip-trigger="{{ (test && \'mouseenter\' || \'click\') }}" />' +
+          '<input uib-tooltip="Hello!" tooltip-trigger="{{ (test && \'mouseenter\' || \'click\') }}" />' +
+        '</div>'
+      );
+
+      $compile(elmBody)(scope);
+      scope.$apply();
+      var elm1 = elmBody.find('input').eq(0);
+      var elm2 = elmBody.find('input').eq(1);
+      var elmScope1 = elm1.scope();
+      var elmScope2 = elm2.scope();
+      var tooltipScope2 = elmScope2.$$childTail;
+
+      scope.$apply('test = false');
+
+      // click trigger isn't set
+      elm2.click();
+      expect(tooltipScope2.isOpen).toBeFalsy();
+
+      // mouseenter trigger is still set
+      trigger(elm2, 'mouseenter');
+      expect(tooltipScope2.isOpen).toBeTruthy();
+    }));
+
+    it('should accept multiple triggers based on the map for mapped triggers', inject(function($compile) {
+      elmBody = angular.element(
+        '<div><input uib-tooltip="Hello!" tooltip-trigger="focus fakeTriggerAttr" /></div>'
+      );
+      $compile(elmBody)(scope);
+      scope.$apply();
+      elm = elmBody.find('input');
+      elmScope = elm.scope();
+      tooltipScope = elmScope.$$childTail;
+
+      expect(tooltipScope.isOpen).toBeFalsy();
+      trigger(elm, 'focus');
+      expect(tooltipScope.isOpen).toBeTruthy();
+      trigger(elm, 'blur');
+      expect(tooltipScope.isOpen).toBeFalsy();
+      trigger(elm, 'fakeTriggerAttr');
+      expect(tooltipScope.isOpen).toBeTruthy();
+      trigger(elm, 'fakeTriggerAttr');
+      expect(tooltipScope.isOpen).toBeFalsy();
+    }));
+
+    it('should not show when trigger is set to "none"', inject(function($compile) {
+      elmBody = angular.element(
+        '<div><input uib-tooltip="Hello!" tooltip-trigger="none" /></div>'
+      );
+      $compile(elmBody)(scope);
+      scope.$apply();
+      elm = elmBody.find('input');
+      elmScope = elm.scope();
+      tooltipScope = elmScope.$$childTail;
+      expect(tooltipScope.isOpen).toBeFalsy();
+      elm.trigger('mouseenter');
+      expect(tooltipScope.isOpen).toBeFalsy();
+    }));
+  });
+
+  describe('with an append-to-body attribute', function() {
+    var scope, elmBody, elm, elmScope, $body;
+
+    beforeEach(inject(function($rootScope) {
+      scope = $rootScope;
+    }));
+
+    afterEach(function() {
+      $body.find('.tooltip').remove();
+    });
+
+    it('should append to the body', inject(function($compile, $document) {
+      $body = $document.find('body');
+      elmBody = angular.element(
+        '<div><span uib-tooltip="tooltip text" tooltip-append-to-body="true">Selector Text</span></div>'
+      );
+
+      $compile(elmBody)(scope);
+      scope.$digest();
+      elm = elmBody.find('span');
+      elmScope = elm.scope();
+      tooltipScope = elmScope.$$childTail;
+
+      var bodyLength = $body.children().length;
+      trigger(elm, 'mouseenter');
+
+      expect(tooltipScope.isOpen).toBe(true);
+      expect(elmBody.children().length).toBe(1);
+      expect($body.children().length).toEqual(bodyLength + 1);
+    }));
+  });
+
+  describe('cleanup', function() {
+    var elmBody, elm, elmScope, tooltipScope;
+
+    function inCache() {
+      var match = false;
+
+      angular.forEach(angular.element.cache, function(item) {
+        if (item.data && item.data.$scope === tooltipScope) {
+          match = true;
+        }
+      });
+
+      return match;
+    }
+
+    beforeEach(inject(function($compile, $rootScope) {
+      elmBody = angular.element('<div><input uib-tooltip="Hello!" tooltip-trigger="fooTrigger" /></div>');
+
+      $compile(elmBody)($rootScope);
+      $rootScope.$apply();
+
+      elm = elmBody.find('input');
+      elmScope = elm.scope();
+      trigger(elm, 'fooTrigger');
+      tooltipScope = elmScope.$$childTail.$$childTail;
+    }));
+
+    it('should not contain a cached reference when not visible', inject(function($timeout) {
+      expect(inCache()).toBeTruthy();
+      elmScope.$destroy();
+      expect(inCache()).toBeFalsy();
+    }));
+  });
+
+  describe('observers', function() {
+    var elmBody, elm, elmScope, scope, tooltipScope;
+
+    beforeEach(inject(function($compile, $rootScope) {
+      scope = $rootScope;
+      scope.content = 'tooltip content';
+      scope.placement = 'top';
+      elmBody = angular.element('<div><input uib-tooltip="{{content}}" tooltip-placement={{placement}} /></div>');
+      $compile(elmBody)(scope);
+      scope.$apply();
+
+      elm = elmBody.find('input');
+      elmScope = elm.scope();
+      tooltipScope = elmScope.$$childTail;
+    }));
+
+    it('should be removed when tooltip hides', inject(function($timeout) {
+      expect(tooltipScope.content).toBe(undefined);
+      expect(tooltipScope.placement).toBe(undefined);
+
+      trigger(elm, 'mouseenter');
+      expect(tooltipScope.content).toBe('tooltip content');
+      expect(tooltipScope.placement).toBe('top');
+      scope.content = 'tooltip content updated';
+
+      scope.placement = 'bottom';
+      scope.$apply();
+      expect(tooltipScope.content).toBe('tooltip content updated');
+      expect(tooltipScope.placement).toBe('bottom');
+
+      trigger(elm, 'mouseleave');
+      $timeout.flush();
+      scope.content = 'tooltip content updated after close';
+      scope.placement = 'left';
+      scope.$apply();
+      expect(tooltipScope.content).toBe('tooltip content updated');
+      expect(tooltipScope.placement).toBe('bottom');
+    }));
+  });
+});
+
+describe('tooltipWithDifferentSymbols', function() {
+    var elmBody;
+
+    // load the tooltip code
+    beforeEach(module('ui.bootstrap.tooltip'));
+
+    // load the template
+    beforeEach(module('template/tooltip/tooltip-popup.html'));
+
+    // configure interpolate provider to use [[ ]] instead of {{ }}
+    beforeEach(module(function($interpolateProvider) {
+      $interpolateProvider.startSymbol('[[');
+      $interpolateProvider.startSymbol(']]');
+    }));
+
+    function trigger(element, evt) {
+      evt = new Event(evt);
+
+      element[0].dispatchEvent(evt);
+      element.scope().$$childTail.$digest();
+    }
+
+    it('should show the correct tooltip text', inject(function($compile, $rootScope) {
+      elmBody = angular.element(
+        '<div><input type="text" uib-tooltip="My tooltip" tooltip-trigger="focus" tooltip-placement="right" /></div>'
+      );
+      $compile(elmBody)($rootScope);
+      $rootScope.$apply();
+      var elmInput = elmBody.find('input');
+      trigger(elmInput, 'focus');
+
+      expect(elmInput.next().find('div').next().html()).toBe('My tooltip');
+    }));
+});
+
+describe('tooltip positioning', function() {
+  var elm, elmBody, elmScope, tooltipScope, scope;
+  var $position;
+
+  // load the tooltip code
+  beforeEach(module('ui.bootstrap.tooltip', function($tooltipProvider) {
+    $tooltipProvider.options({ animation: false });
+  }));
+
+  // load the template
+  beforeEach(module('template/tooltip/tooltip-popup.html'));
+
+  beforeEach(inject(function($rootScope, $compile, $uibPosition) {
+    $position = $uibPosition;
+    spyOn($position, 'positionElements').and.callThrough();
+
+    scope = $rootScope;
+    scope.text = 'Some Text';
+
+    elmBody = $compile(angular.element(
+      '<div><span uib-tooltip="{{ text }}">Selector Text</span></div>'
+    ))(scope);
+    scope.$digest();
+    elm = elmBody.find('span');
+    elmScope = elm.scope();
+    tooltipScope = elmScope.$$childTail;
+  }));
+
+  function trigger(element, evt) {
+    evt = new Event(evt);
+
+    element[0].dispatchEvent(evt);
+    element.scope().$$childTail.$digest();
+  }
+
+  it('should re-position when value changes', inject(function($timeout) {
+    trigger(elm, 'mouseenter');
+
+    scope.$digest();
+    $timeout.flush();
+    var startingPositionCalls = $position.positionElements.calls.count();
+
+    scope.text = 'New Text';
+    scope.$digest();
+    $timeout.flush();
+    expect(elm.attr('uib-tooltip')).toBe('New Text');
+    expect($position.positionElements.calls.count()).toEqual(startingPositionCalls + 1);
+    // Check that positionElements was called with elm
+    expect($position.positionElements.calls.argsFor(startingPositionCalls)[0][0])
+      .toBe(elm[0]);
+
+    scope.$digest();
+    $timeout.verifyNoPendingTasks();
+    expect($position.positionElements.calls.count()).toEqual(startingPositionCalls + 1);
+    expect($position.positionElements.calls.argsFor(startingPositionCalls)[0][0])
+      .toBe(elm[0]);
+    scope.$digest();
+  }));
+
+});
+
+describe('tooltipHtml', function() {
+  var elm, elmBody, elmScope, tooltipScope, scope;
+
+  // load the tooltip code
+  beforeEach(module('ui.bootstrap.tooltip', function($tooltipProvider) {
+    $tooltipProvider.options({ animation: false });
+  }));
+
+  // load the template
+  beforeEach(module('template/tooltip/tooltip-html-popup.html'));
+
+  beforeEach(inject(function($rootScope, $compile, $sce) {
+    scope = $rootScope;
+    scope.html = 'I say: <strong class="hello">Hello!</strong>';
+    scope.safeHtml = $sce.trustAsHtml(scope.html);
+
+    elmBody = $compile(angular.element(
+      '<div><span uib-tooltip-html="safeHtml">Selector Text</span></div>'
+    ))(scope);
+    scope.$digest();
+    elm = elmBody.find('span');
+    elmScope = elm.scope();
+    tooltipScope = elmScope.$$childTail;
+  }));
+
+  function trigger(element, evt) {
+    evt = new Event(evt);
+
+    element[0].dispatchEvent(evt);
+    element.scope().$$childTail.$digest();
+  }
+
+  it('should render html properly', inject(function() {
+    trigger(elm, 'mouseenter');
+    expect(elmBody.find('.tooltip-inner').html()).toBe(scope.html);
+  }));
+
+  it('should not open if html is empty', function() {
+    scope.safeHtml = null;
+    scope.$digest();
+    trigger(elm, 'mouseenter');
+    expect(tooltipScope.isOpen).toBe(false);
+  });
+
+  it('should show on mouseenter and hide on mouseleave', inject(function($sce) {
+    expect(tooltipScope.isOpen).toBe(false);
+
+    trigger(elm, 'mouseenter');
+    expect(tooltipScope.isOpen).toBe(true);
+    expect(elmBody.children().length).toBe(2);
+
+    expect($sce.getTrustedHtml(tooltipScope.contentExp())).toEqual(scope.html);
+
+    trigger(elm, 'mouseleave');
+    expect(tooltipScope.isOpen).toBe(false);
+    expect(elmBody.children().length).toBe(1);
+  }));
+});
+
+describe('$tooltipProvider', function() {
+  var elm,
+      elmBody,
+      scope,
+      elmScope,
+      tooltipScope;
+
+  function trigger(element, evt) {
+    evt = new Event(evt);
+
+    element[0].dispatchEvent(evt);
+    element.scope().$$childTail.$digest();
+  }
+
+  describe('popupDelay', function() {
+    beforeEach(module('ui.bootstrap.tooltip', function($tooltipProvider) {
+      $tooltipProvider.options({popupDelay: 1000});
+    }));
+
+    // load the template
+    beforeEach(module('template/tooltip/tooltip-popup.html'));
+
+    beforeEach(inject(function($rootScope, $compile) {
+      elmBody = angular.element(
+        '<div><span uib-tooltip="tooltip text">Selector Text</span></div>'
+      );
+
+      scope = $rootScope;
+      $compile(elmBody)(scope);
+      scope.$digest();
+      elm = elmBody.find('span');
+      elmScope = elm.scope();
+      tooltipScope = elmScope.$$childTail;
+    }));
+
+    it('should open after timeout', inject(function($timeout) {
+      trigger(elm, 'mouseenter');
+      expect(tooltipScope.isOpen).toBe(false);
+
+      $timeout.flush();
+      expect(tooltipScope.isOpen).toBe(true);
+    }));
+  });
+
+  describe('appendToBody', function() {
+    var $body;
+
+    beforeEach(module('template/tooltip/tooltip-popup.html'));
+    beforeEach(module('ui.bootstrap.tooltip', function($tooltipProvider) {
+      $tooltipProvider.options({ appendToBody: true });
+    }));
+
+    afterEach(function() {
+      $body.find('.tooltip').remove();
+    });
+
+    it('should append to the body', inject(function($rootScope, $compile, $document) {
+      $body = $document.find('body');
+      elmBody = angular.element(
+        '<div><span uib-tooltip="tooltip text">Selector Text</span></div>'
+      );
+
+      scope = $rootScope;
+      $compile(elmBody)(scope);
+      scope.$digest();
+      elm = elmBody.find('span');
+      elmScope = elm.scope();
+      tooltipScope = elmScope.$$childTail;
+
+      var bodyLength = $body.children().length;
+      trigger(elm, 'mouseenter');
+
+      expect(tooltipScope.isOpen).toBe(true);
+      expect(elmBody.children().length).toBe(1);
+      expect($body.children().length).toEqual(bodyLength + 1);
+    }));
+
+    it('should close on location change', inject(function($rootScope, $compile) {
+      elmBody = angular.element(
+        '<div><span uib-tooltip="tooltip text">Selector Text</span></div>'
+      );
+
+      scope = $rootScope;
+      $compile(elmBody)(scope);
+      scope.$digest();
+      elm = elmBody.find('span');
+      elmScope = elm.scope();
+      tooltipScope = elmScope.$$childTail;
+
+      trigger(elm, 'mouseenter');
+      expect(tooltipScope.isOpen).toBe(true);
+
+      scope.$broadcast('$locationChangeSuccess');
+      scope.$digest();
+      expect(tooltipScope.isOpen).toBe(false);
+    }));
+  });
+
+  describe('triggers', function() {
+    describe('triggers with a mapped value', function() {
+      beforeEach(module('ui.bootstrap.tooltip', function($uibTooltipProvider) {
+        $uibTooltipProvider.options({trigger: 'focus'});
+      }));
+
+      // load the template
+      beforeEach(module('template/tooltip/tooltip-popup.html'));
+
+      it('should use the show trigger and the mapped value for the hide trigger', inject(function($rootScope, $compile) {
+        elmBody = angular.element(
+          '<div><input uib-tooltip="tooltip text" /></div>'
+        );
+
+        scope = $rootScope;
+        $compile(elmBody)(scope);
+        scope.$digest();
+        elm = elmBody.find('input');
+        elmScope = elm.scope();
+        tooltipScope = elmScope.$$childTail;
+
+        expect(tooltipScope.isOpen).toBeFalsy();
+        trigger(elm, 'focus');
+        expect(tooltipScope.isOpen).toBeTruthy();
+        trigger(elm, 'blur');
+        expect(tooltipScope.isOpen).toBeFalsy();
+      }));
+
+      it('should override the show and hide triggers if there is an attribute', inject(function($rootScope, $compile) {
+        elmBody = angular.element(
+          '<div><input uib-tooltip="tooltip text" tooltip-trigger="mouseenter"/></div>'
+        );
+
+        scope = $rootScope;
+        $compile(elmBody)(scope);
+        scope.$digest();
+        elm = elmBody.find('input');
+        elmScope = elm.scope();
+        tooltipScope = elmScope.$$childTail;
+
+        expect(tooltipScope.isOpen).toBeFalsy();
+        trigger(elm, 'mouseenter');
+        expect(tooltipScope.isOpen).toBeTruthy();
+        trigger(elm, 'mouseleave');
+        expect(tooltipScope.isOpen).toBeFalsy();
+      }));
+    });
+
+    describe('triggers with a custom mapped value', function() {
+      beforeEach(module('ui.bootstrap.tooltip', function($uibTooltipProvider) {
+        $uibTooltipProvider.setTriggers({ customOpenTrigger: 'foo bar' });
+        $uibTooltipProvider.options({trigger: 'customOpenTrigger'});
+      }));
+
+      // load the template
+      beforeEach(module('template/tooltip/tooltip-popup.html'));
+
+      it('should use the show trigger and the mapped value for the hide trigger', inject(function($rootScope, $compile) {
+        elmBody = angular.element(
+          '<div><input uib-tooltip="tooltip text" /></div>'
+        );
+
+        scope = $rootScope;
+        $compile(elmBody)(scope);
+        scope.$digest();
+        elm = elmBody.find('input');
+        elmScope = elm.scope();
+        tooltipScope = elmScope.$$childTail;
+
+        expect(tooltipScope.isOpen).toBeFalsy();
+        trigger(elm, 'customOpenTrigger');
+        expect(tooltipScope.isOpen).toBeTruthy();
+        trigger(elm, 'foo');
+        expect(tooltipScope.isOpen).toBeFalsy();
+        trigger(elm, 'customOpenTrigger');
+        expect(tooltipScope.isOpen).toBeTruthy();
+        trigger(elm, 'bar');
+        expect(tooltipScope.isOpen).toBeFalsy();
+      }));
+    });
+
+    describe('triggers without a mapped value', function() {
+      beforeEach(module('ui.bootstrap.tooltip', function($uibTooltipProvider) {
+        $uibTooltipProvider.options({trigger: 'fakeTrigger'});
+      }));
+
+      // load the template
+      beforeEach(module('template/tooltip/tooltip-popup.html'));
+
+      it('should use the show trigger to hide', inject(function($rootScope, $compile) {
+        elmBody = angular.element(
+          '<div><span uib-tooltip="tooltip text">Selector Text</span></div>'
+        );
+
+        scope = $rootScope;
+        $compile(elmBody)(scope);
+        scope.$digest();
+        elm = elmBody.find('span');
+        elmScope = elm.scope();
+        tooltipScope = elmScope.$$childTail;
+
+        expect(tooltipScope.isOpen).toBeFalsy();
+        trigger(elm, 'fakeTrigger');
+        expect(tooltipScope.isOpen).toBeTruthy();
+        trigger(elm, 'fakeTrigger');
+        expect(tooltipScope.isOpen).toBeFalsy();
+      }));
+    });
+  });
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tooltip/test/tooltip2.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tooltip/test/tooltip2.spec.js
new file mode 100644
index 0000000..2ed8e2b
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tooltip/test/tooltip2.spec.js
@@ -0,0 +1,282 @@
+describe('tooltip directive', function() {
+  var $rootScope, $compile, $document, $timeout;
+
+  beforeEach(module('ui.bootstrap.tooltip'));
+  beforeEach(module('template/tooltip/tooltip-popup.html'));
+  beforeEach(module('template/tooltip/tooltip-template-popup.html'));
+  beforeEach(module('template/tooltip/tooltip-html-popup.html'));
+  beforeEach(inject(function(_$rootScope_, _$compile_, _$document_, _$timeout_) {
+    $rootScope = _$rootScope_;
+    $compile = _$compile_;
+    $document = _$document_;
+    $timeout = _$timeout_;
+  }));
+
+  beforeEach(function() {
+    jasmine.addMatchers({
+      toHaveOpenTooltips: function(util, customEqualityTesters) {
+        return {
+          compare: function(actual, noOfOpened) {
+            var ttipElements = actual.find('div.tooltip');
+            noOfOpened = noOfOpened || 1;
+
+            var result = {
+              pass: util.equals(ttipElements.length, noOfOpened, customEqualityTesters)
+            };
+
+            if (result.message) {
+              result.message = 'Expected "' + angular.mock.dump(ttipElements) + '" not to have "' + ttipElements.length + '" opened tooltips.';
+            } else {
+              result.message = 'Expected "' + angular.mock.dump(ttipElements) + '" to have "' + ttipElements.length + '" opened tooltips.';
+            }
+
+            return result;
+          }
+        };
+      }
+    });
+  });
+
+  function compileTooltip(ttipMarkup) {
+    var fragment = $compile('<div>' + ttipMarkup + '</div>')($rootScope);
+    $rootScope.$digest();
+    return fragment;
+  }
+
+  function closeTooltip(hostEl, triggerEvt, shouldNotFlush) {
+    trigger(hostEl, triggerEvt || 'mouseleave');
+    hostEl.scope().$$childTail.$digest();
+    if (!shouldNotFlush) {
+      $timeout.flush();
+    }
+  }
+
+  function trigger(element, evt) {
+    evt = new Event(evt);
+
+    element[0].dispatchEvent(evt);
+    element.scope().$$childTail.$digest();
+  }
+
+  describe('basic scenarios with default options', function() {
+    it('shows default tooltip on mouse enter and closes on mouse leave', function() {
+      var fragment = compileTooltip('<span uib-tooltip="tooltip text">Trigger here</span>');
+
+      trigger(fragment.find('span'), 'mouseenter');
+      expect(fragment).toHaveOpenTooltips();
+
+      closeTooltip(fragment.find('span'));
+      expect(fragment).not.toHaveOpenTooltips();
+    });
+
+    it('should not show a tooltip when its content is empty', function() {
+      var fragment = compileTooltip('<span uib-tooltip=""></span>');
+      trigger(fragment.find('span'), 'mouseenter');
+      expect(fragment).not.toHaveOpenTooltips();
+    });
+
+    it('should not show a tooltip when its content becomes empty', function() {
+      $rootScope.content = 'some text';
+      var fragment = compileTooltip('<span uib-tooltip="{{ content }}"></span>');
+
+      trigger(fragment.find('span'), 'mouseenter');
+      expect(fragment).toHaveOpenTooltips();
+
+      $rootScope.content = '';
+      $rootScope.$digest();
+      $timeout.flush();
+      expect(fragment).not.toHaveOpenTooltips();
+    });
+
+    it('should update tooltip when its content becomes empty', function() {
+      $rootScope.content = 'some text';
+      var fragment = compileTooltip('<span uib-tooltip="{{ content }}"></span>');
+
+      $rootScope.content = '';
+      $rootScope.$digest();
+
+      trigger(fragment.find('span'), 'mouseenter');
+      expect(fragment).not.toHaveOpenTooltips();
+    });
+  });
+
+  describe('option by option', function() {
+    var tooltipTypes = {
+      'tooltip': 'uib-tooltip="tooltip text"',
+      'tooltip-html': 'uib-tooltip-html="tooltipSafeHtml"',
+      'tooltip-template': 'uib-tooltip-template="\'tooltipTextUrl\'"'
+    };
+
+    beforeEach(inject(function($sce, $templateCache) {
+      $rootScope.tooltipText = 'tooltip text';
+      $rootScope.tooltipSafeHtml = $sce.trustAsHtml('tooltip text');
+      $templateCache.put('tooltipTextUrl', [200, '<span>tooltip text</span>', {}]);
+    }));
+
+    angular.forEach(tooltipTypes, function(html, key) {
+      describe(key, function() {
+        describe('placement', function() {
+          it('can specify an alternative, valid placement', function() {
+            var fragment = compileTooltip('<span ' + html + ' tooltip-placement="left">Trigger here</span>');
+            trigger(fragment.find('span'), 'mouseenter');
+
+            var ttipElement = fragment.find('div.tooltip');
+            expect(fragment).toHaveOpenTooltips();
+            expect(ttipElement).toHaveClass('left');
+
+            closeTooltip(fragment.find('span'));
+            expect(fragment).not.toHaveOpenTooltips();
+          });
+        });
+
+        describe('class', function() {
+          it('can specify a custom class', function() {
+            var fragment = compileTooltip('<span ' + html + ' tooltip-class="custom">Trigger here</span>');
+            trigger(fragment.find('span'), 'mouseenter');
+
+            var ttipElement = fragment.find('div.tooltip');
+            expect(fragment).toHaveOpenTooltips();
+            expect(ttipElement).toHaveClass('custom');
+
+            closeTooltip(fragment.find('span'));
+            expect(fragment).not.toHaveOpenTooltips();
+          });
+        });
+      });
+    });
+  });
+
+  it('should show even after close trigger is called multiple times - issue #1847', function() {
+    var fragment = compileTooltip('<span uib-tooltip="tooltip text">Trigger here</span>');
+
+    trigger(fragment.find('span'), 'mouseenter');
+    expect(fragment).toHaveOpenTooltips();
+
+    closeTooltip(fragment.find('span'), null, true);
+    // Close trigger is called again before timer completes
+    // The close trigger can be called any number of times (even after close has already been called)
+    // since users can trigger the hide triggers manually.
+    closeTooltip(fragment.find('span'), null, true);
+    expect(fragment).toHaveOpenTooltips();
+
+    trigger(fragment.find('span'), 'mouseenter');
+    expect(fragment).toHaveOpenTooltips();
+
+    $timeout.flush();
+    expect(fragment).toHaveOpenTooltips();
+  });
+
+  it('should hide even after show trigger is called multiple times', function() {
+    var fragment = compileTooltip('<span uib-tooltip="tooltip text" tooltip-popup-delay="1000">Trigger here</span>');
+
+    trigger(fragment.find('span'), 'mouseenter');
+    trigger(fragment.find('span'), 'mouseenter');
+
+    closeTooltip(fragment.find('span'));
+    expect(fragment).not.toHaveOpenTooltips();
+  });
+
+  it('should not show tooltips element is disabled (button) - issue #3167', function() {
+    var fragment = compileTooltip('<button uib-tooltip="cancel!" ng-disabled="disabled" ng-click="disabled = true">Cancel</button>');
+
+    trigger(fragment.find('button'), 'mouseenter');
+    expect(fragment).toHaveOpenTooltips();
+
+    trigger(fragment.find('button'), 'click');
+    $timeout.flush();
+    // One needs to flush deferred functions before checking there is no tooltip.
+    expect(fragment).not.toHaveOpenTooltips();
+  });
+});
+
+/* Deprecation tests below */
+
+describe('tooltip deprecation', function() {
+  beforeEach(module('ui.bootstrap.tooltip'));
+  beforeEach(module('template/tooltip/tooltip-popup.html'));
+  beforeEach(module('template/tooltip/tooltip-template-popup.html'));
+  beforeEach(module('template/tooltip/tooltip-html-popup.html'));
+
+  describe('tooltip', function() {
+    it('should suppress warning', function() {
+      module(function($provide) {
+        $provide.value('$tooltipSuppressWarning', true);
+      });
+
+      inject(function($compile, $log, $rootScope) {
+        spyOn($log, 'warn');
+
+        var element = '<div><span tooltip="tooltip text">Trigger here</span></div>';
+        element = $compile(element)($rootScope);
+        $rootScope.$digest();
+        expect($log.warn.calls.count()).toBe(0);
+      });
+    });
+
+    it('should give warning by default', inject(function($compile, $log, $rootScope) {
+      spyOn($log, 'warn');
+
+      var element = '<div><span tooltip="tooltip text">Trigger here</span></div>';
+      element = $compile(element)($rootScope);
+      $rootScope.$digest();
+
+      expect($log.warn.calls.count()).toBe(1);
+      expect($log.warn.calls.argsFor(0)).toEqual(['$tooltip is now deprecated. Use $uibTooltip instead.']);
+    }));
+  });
+
+  describe('tooltip html', function() {
+    var elm, elmBody, elmScope, tooltipScope;
+
+    function trigger(element, evt) {
+      evt = new Event(evt);
+
+      element[0].dispatchEvent(evt);
+      element.scope().$$childTail.$digest();
+    }
+
+    it('should suppress warning', function() {
+      module(function($provide) {
+        $provide.value('$tooltipSuppressWarning', true);
+      });
+
+      inject(function($compile, $log, $rootScope, $sce) {
+        spyOn($log, 'warn');
+
+        $rootScope.html = 'I say: <strong class="hello">Hello!</strong>';
+        $rootScope.safeHtml = $sce.trustAsHtml($rootScope.html);
+        elmBody = angular.element('<div><span tooltip-html="safeHtml">Selector Text</span></div>');
+        $compile(elmBody)($rootScope);
+        $rootScope.$digest();
+        elm = elmBody.find('span');
+        elmScope = elm.scope();
+        tooltipScope = elmScope.$$childTail;
+
+        trigger(elm, 'mouseenter');
+        tooltipScope.$digest();
+
+        expect($log.warn.calls.count()).toBe(0);
+      });
+    });
+
+    it('should give warning by default', inject(function($compile, $log, $rootScope, $sce) {
+      spyOn($log, 'warn');
+
+      $rootScope.html = 'I say: <strong class="hello">Hello!</strong>';
+      $rootScope.safeHtml = $sce.trustAsHtml($rootScope.html);
+      elmBody = angular.element('<div><span tooltip-html="safeHtml">Selector Text</span></div>');
+      $compile(elmBody)($rootScope);
+      $rootScope.$digest();
+      elm = elmBody.find('span');
+      elmScope = elm.scope();
+      tooltipScope = elmScope.$$childTail;
+
+      trigger(elm, 'mouseenter');
+      tooltipScope.$digest();
+
+      expect($log.warn.calls.count()).toBe(2);
+      expect($log.warn.calls.argsFor(0)).toEqual(['$tooltip is now deprecated. Use $uibTooltip instead.']);
+      expect($log.warn.calls.argsFor(1)).toEqual(['tooltip-html-popup is now deprecated. Use uib-tooltip-html-popup instead.']);
+    }));
+  });
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tooltip/tooltip.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tooltip/tooltip.js
new file mode 100644
index 0000000..cbc7e97
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/tooltip/tooltip.js
@@ -0,0 +1,833 @@
+/**
+ * The following features are still outstanding: animation as a
+ * function, placement as a function, inside, support for more triggers than
+ * just mouse enter/leave, html tooltips, and selector delegation.
+ */
+angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.stackedMap'])
+
+/**
+ * The $tooltip service creates tooltip- and popover-like directives as well as
+ * houses global options for them.
+ */
+.provider('$uibTooltip', function() {
+  // The default options tooltip and popover.
+  var defaultOptions = {
+    placement: 'top',
+    animation: true,
+    popupDelay: 0,
+    popupCloseDelay: 0,
+    useContentExp: false
+  };
+
+  // Default hide triggers for each show trigger
+  var triggerMap = {
+    'mouseenter': 'mouseleave',
+    'click': 'click',
+    'focus': 'blur',
+    'none': ''
+  };
+
+  // The options specified to the provider globally.
+  var globalOptions = {};
+
+  /**
+   * `options({})` allows global configuration of all tooltips in the
+   * application.
+   *
+   *   var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
+   *     // place tooltips left instead of top by default
+   *     $tooltipProvider.options( { placement: 'left' } );
+   *   });
+   */
+	this.options = function(value) {
+		angular.extend(globalOptions, value);
+	};
+
+  /**
+   * This allows you to extend the set of trigger mappings available. E.g.:
+   *
+   *   $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
+   */
+  this.setTriggers = function setTriggers(triggers) {
+    angular.extend(triggerMap, triggers);
+  };
+
+  /**
+   * This is a helper function for translating camel-case to snake-case.
+   */
+  function snake_case(name) {
+    var regexp = /[A-Z]/g;
+    var separator = '-';
+    return name.replace(regexp, function(letter, pos) {
+      return (pos ? separator : '') + letter.toLowerCase();
+    });
+  }
+
+  /**
+   * Returns the actual instance of the $tooltip service.
+   * TODO support multiple triggers
+   */
+  this.$get = ['$window', '$compile', '$timeout', '$document', '$uibPosition', '$interpolate', '$rootScope', '$parse', '$$stackedMap', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse, $$stackedMap) {
+    var openedTooltips = $$stackedMap.createNew();
+    $document.on('keypress', function(e) {
+      if (e.which === 27) {
+        var last = openedTooltips.top();
+        if (last) {
+          last.value.close();
+          openedTooltips.removeTop();
+          last = null;
+        }
+      }
+    });
+
+    return function $tooltip(ttType, prefix, defaultTriggerShow, options) {
+      options = angular.extend({}, defaultOptions, globalOptions, options);
+
+      /**
+       * Returns an object of show and hide triggers.
+       *
+       * If a trigger is supplied,
+       * it is used to show the tooltip; otherwise, it will use the `trigger`
+       * option passed to the `$tooltipProvider.options` method; else it will
+       * default to the trigger supplied to this directive factory.
+       *
+       * The hide trigger is based on the show trigger. If the `trigger` option
+       * was passed to the `$tooltipProvider.options` method, it will use the
+       * mapped trigger from `triggerMap` or the passed trigger if the map is
+       * undefined; otherwise, it uses the `triggerMap` value of the show
+       * trigger; else it will just use the show trigger.
+       */
+      function getTriggers(trigger) {
+        var show = (trigger || options.trigger || defaultTriggerShow).split(' ');
+        var hide = show.map(function(trigger) {
+          return triggerMap[trigger] || trigger;
+        });
+        return {
+          show: show,
+          hide: hide
+        };
+      }
+
+      var directiveName = snake_case(ttType);
+
+      var startSym = $interpolate.startSymbol();
+      var endSym = $interpolate.endSymbol();
+      var template =
+        '<div '+ directiveName + '-popup '+
+          'title="' + startSym + 'title' + endSym + '" '+
+          (options.useContentExp ?
+            'content-exp="contentExp()" ' :
+            'content="' + startSym + 'content' + endSym + '" ') +
+          'placement="' + startSym + 'placement' + endSym + '" '+
+          'popup-class="' + startSym + 'popupClass' + endSym + '" '+
+          'animation="animation" ' +
+          'is-open="isOpen"' +
+          'origin-scope="origScope" ' +
+          'style="visibility: hidden; display: block; top: -9999px; left: -9999px;"' +
+          '>' +
+        '</div>';
+
+      return {
+        compile: function(tElem, tAttrs) {
+          var tooltipLinker = $compile(template);
+
+          return function link(scope, element, attrs, tooltipCtrl) {
+            var tooltip;
+            var tooltipLinkedScope;
+            var transitionTimeout;
+            var showTimeout;
+            var hideTimeout;
+            var positionTimeout;
+            var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false;
+            var triggers = getTriggers(undefined);
+            var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']);
+            var ttScope = scope.$new(true);
+            var repositionScheduled = false;
+            var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false;
+            var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false;
+            var observers = [];
+
+            var positionTooltip = function() {
+              // check if tooltip exists and is not empty
+              if (!tooltip || !tooltip.html()) { return; }
+
+              if (!positionTimeout) {
+                positionTimeout = $timeout(function() {
+                  // Reset the positioning.
+                  tooltip.css({ top: 0, left: 0 });
+
+                  // Now set the calculated positioning.
+                  var ttCss = $position.positionElements(element, tooltip, ttScope.placement, appendToBody);
+                  ttCss.top += 'px';
+                  ttCss.left += 'px';
+                  ttCss.visibility = 'visible';
+                  tooltip.css(ttCss);
+
+                  positionTimeout = null;
+                }, 0, false);
+              }
+            };
+
+            // Set up the correct scope to allow transclusion later
+            ttScope.origScope = scope;
+
+            // By default, the tooltip is not open.
+            // TODO add ability to start tooltip opened
+            ttScope.isOpen = false;
+            openedTooltips.add(ttScope, {
+              close: hide
+            });
+
+            function toggleTooltipBind() {
+              if (!ttScope.isOpen) {
+                showTooltipBind();
+              } else {
+                hideTooltipBind();
+              }
+            }
+
+            // Show the tooltip with delay if specified, otherwise show it immediately
+            function showTooltipBind() {
+              if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) {
+                return;
+              }
+
+              cancelHide();
+              prepareTooltip();
+
+              if (ttScope.popupDelay) {
+                // Do nothing if the tooltip was already scheduled to pop-up.
+                // This happens if show is triggered multiple times before any hide is triggered.
+                if (!showTimeout) {
+                  showTimeout = $timeout(show, ttScope.popupDelay, false);
+                }
+              } else {
+                show();
+              }
+            }
+
+            function hideTooltipBind() {
+              cancelShow();
+
+              if (ttScope.popupCloseDelay) {
+                if (!hideTimeout) {
+                  hideTimeout = $timeout(hide, ttScope.popupCloseDelay, false);
+                }
+              } else {
+                hide();
+              }
+            }
+
+            // Show the tooltip popup element.
+            function show() {
+              cancelShow();
+              cancelHide();
+
+              // Don't show empty tooltips.
+              if (!ttScope.content) {
+                return angular.noop;
+              }
+
+              createTooltip();
+
+              // And show the tooltip.
+              ttScope.$evalAsync(function() {
+                ttScope.isOpen = true;
+                assignIsOpen(true);
+                positionTooltip();
+              });
+            }
+
+            function cancelShow() {
+              if (showTimeout) {
+                $timeout.cancel(showTimeout);
+                showTimeout = null;
+              }
+
+              if (positionTimeout) {
+                $timeout.cancel(positionTimeout);
+                positionTimeout = null;
+              }
+            }
+
+            // Hide the tooltip popup element.
+            function hide() {
+              cancelShow();
+              cancelHide();
+
+              if (!ttScope) {
+                return;
+              }
+
+              // First things first: we don't show it anymore.
+              ttScope.$evalAsync(function() {
+                ttScope.isOpen = false;
+                assignIsOpen(false);
+                // And now we remove it from the DOM. However, if we have animation, we
+                // need to wait for it to expire beforehand.
+                // FIXME: this is a placeholder for a port of the transitions library.
+                // The fade transition in TWBS is 150ms.
+                if (ttScope.animation) {
+                  if (!transitionTimeout) {
+                    transitionTimeout = $timeout(removeTooltip, 150, false);
+                  }
+                } else {
+                  removeTooltip();
+                }
+              });
+            }
+
+            function cancelHide() {
+              if (hideTimeout) {
+                $timeout.cancel(hideTimeout);
+                hideTimeout = null;
+              }
+              if (transitionTimeout) {
+                $timeout.cancel(transitionTimeout);
+                transitionTimeout = null;
+              }
+            }
+
+            function createTooltip() {
+              // There can only be one tooltip element per directive shown at once.
+              if (tooltip) {
+                return;
+              }
+
+              tooltipLinkedScope = ttScope.$new();
+              tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) {
+                if (appendToBody) {
+                  $document.find('body').append(tooltip);
+                } else {
+                  element.after(tooltip);
+                }
+              });
+
+              prepObservers();
+            }
+
+            function removeTooltip() {
+              unregisterObservers();
+
+              transitionTimeout = null;
+              if (tooltip) {
+                tooltip.remove();
+                tooltip = null;
+              }
+              if (tooltipLinkedScope) {
+                tooltipLinkedScope.$destroy();
+                tooltipLinkedScope = null;
+              }
+            }
+
+            /**
+             * Set the inital scope values. Once
+             * the tooltip is created, the observers
+             * will be added to keep things in synch.
+             */
+            function prepareTooltip() {
+              ttScope.title = attrs[prefix + 'Title'];
+              if (contentParse) {
+                ttScope.content = contentParse(scope);
+              } else {
+                ttScope.content = attrs[ttType];
+              }
+
+              ttScope.popupClass = attrs[prefix + 'Class'];
+              ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement;
+
+              var delay = parseInt(attrs[prefix + 'PopupDelay'], 10);
+              var closeDelay = parseInt(attrs[prefix + 'PopupCloseDelay'], 10);
+              ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay;
+              ttScope.popupCloseDelay = !isNaN(closeDelay) ? closeDelay : options.popupCloseDelay;
+            }
+
+            function assignIsOpen(isOpen) {
+              if (isOpenParse && angular.isFunction(isOpenParse.assign)) {
+                isOpenParse.assign(scope, isOpen);
+              }
+            }
+
+            ttScope.contentExp = function() {
+              return ttScope.content;
+            };
+
+            /**
+             * Observe the relevant attributes.
+             */
+            attrs.$observe('disabled', function(val) {
+              if (val) {
+                cancelShow();
+              }
+
+              if (val && ttScope.isOpen) {
+                hide();
+              }
+            });
+
+            if (isOpenParse) {
+              scope.$watch(isOpenParse, function(val) {
+                /*jshint -W018 */
+                if (ttScope && !val === ttScope.isOpen) {
+                  toggleTooltipBind();
+                }
+                /*jshint +W018 */
+              });
+            }
+
+            function prepObservers() {
+              observers.length = 0;
+
+              if (contentParse) {
+                observers.push(
+                  scope.$watch(contentParse, function(val) {
+                    ttScope.content = val;
+                    if (!val && ttScope.isOpen) {
+                      hide();
+                    }
+                  })
+                );
+
+                observers.push(
+                  tooltipLinkedScope.$watch(function() {
+                    if (!repositionScheduled) {
+                      repositionScheduled = true;
+                      tooltipLinkedScope.$$postDigest(function() {
+                        repositionScheduled = false;
+                        if (ttScope && ttScope.isOpen) {
+                          positionTooltip();
+                        }
+                      });
+                    }
+                  })
+                );
+              } else {
+                observers.push(
+                  attrs.$observe(ttType, function(val) {
+                    ttScope.content = val;
+                    if (!val && ttScope.isOpen) {
+                      hide();
+                    } else {
+                      positionTooltip();
+                    }
+                  })
+                );
+              }
+
+              observers.push(
+                attrs.$observe(prefix + 'Title', function(val) {
+                  ttScope.title = val;
+                  if (ttScope.isOpen) {
+                    positionTooltip();
+                  }
+                })
+              );
+
+              observers.push(
+                attrs.$observe(prefix + 'Placement', function(val) {
+                  ttScope.placement = val ? val : options.placement;
+                  if (ttScope.isOpen) {
+                    positionTooltip();
+                  }
+                })
+              );
+            }
+
+            function unregisterObservers() {
+              if (observers.length) {
+                angular.forEach(observers, function(observer) {
+                  observer();
+                });
+                observers.length = 0;
+              }
+            }
+
+            var unregisterTriggers = function() {
+              triggers.show.forEach(function(trigger) {
+                element.unbind(trigger, showTooltipBind);
+              });
+              triggers.hide.forEach(function(trigger) {
+                trigger.split(' ').forEach(function(hideTrigger) {
+                  element[0].removeEventListener(hideTrigger, hideTooltipBind);
+                });
+              });
+            };
+
+            function prepTriggers() {
+              var val = attrs[prefix + 'Trigger'];
+              unregisterTriggers();
+
+              triggers = getTriggers(val);
+
+              if (triggers.show !== 'none') {
+                triggers.show.forEach(function(trigger, idx) {
+                  // Using raw addEventListener due to jqLite/jQuery bug - #4060
+                  if (trigger === triggers.hide[idx]) {
+                    element[0].addEventListener(trigger, toggleTooltipBind);
+                  } else if (trigger) {
+                    element[0].addEventListener(trigger, showTooltipBind);
+                    triggers.hide[idx].split(' ').forEach(function(trigger) {
+                      element[0].addEventListener(trigger, hideTooltipBind);
+                    });
+                  }
+
+                  element.on('keypress', function(e) {
+                    if (e.which === 27) {
+                      hideTooltipBind();
+                    }
+                  });
+                });
+              }
+            }
+
+            prepTriggers();
+
+            var animation = scope.$eval(attrs[prefix + 'Animation']);
+            ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation;
+
+            var appendToBodyVal = scope.$eval(attrs[prefix + 'AppendToBody']);
+            appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody;
+
+            // if a tooltip is attached to <body> we need to remove it on
+            // location change as its parent scope will probably not be destroyed
+            // by the change.
+            if (appendToBody) {
+              scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess() {
+                if (ttScope.isOpen) {
+                  hide();
+                }
+              });
+            }
+
+            // Make sure tooltip is destroyed and removed.
+            scope.$on('$destroy', function onDestroyTooltip() {
+              cancelShow();
+              cancelHide();
+              unregisterTriggers();
+              removeTooltip();
+              openedTooltips.remove(ttScope);
+              ttScope = null;
+            });
+          };
+        }
+      };
+    };
+  }];
+})
+
+// This is mostly ngInclude code but with a custom scope
+.directive('uibTooltipTemplateTransclude', [
+         '$animate', '$sce', '$compile', '$templateRequest',
+function ($animate ,  $sce ,  $compile ,  $templateRequest) {
+  return {
+    link: function(scope, elem, attrs) {
+      var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope);
+
+      var changeCounter = 0,
+        currentScope,
+        previousElement,
+        currentElement;
+
+      var cleanupLastIncludeContent = function() {
+        if (previousElement) {
+          previousElement.remove();
+          previousElement = null;
+        }
+
+        if (currentScope) {
+          currentScope.$destroy();
+          currentScope = null;
+        }
+
+        if (currentElement) {
+          $animate.leave(currentElement).then(function() {
+            previousElement = null;
+          });
+          previousElement = currentElement;
+          currentElement = null;
+        }
+      };
+
+      scope.$watch($sce.parseAsResourceUrl(attrs.uibTooltipTemplateTransclude), function(src) {
+        var thisChangeId = ++changeCounter;
+
+        if (src) {
+          //set the 2nd param to true to ignore the template request error so that the inner
+          //contents and scope can be cleaned up.
+          $templateRequest(src, true).then(function(response) {
+            if (thisChangeId !== changeCounter) { return; }
+            var newScope = origScope.$new();
+            var template = response;
+
+            var clone = $compile(template)(newScope, function(clone) {
+              cleanupLastIncludeContent();
+              $animate.enter(clone, elem);
+            });
+
+            currentScope = newScope;
+            currentElement = clone;
+
+            currentScope.$emit('$includeContentLoaded', src);
+          }, function() {
+            if (thisChangeId === changeCounter) {
+              cleanupLastIncludeContent();
+              scope.$emit('$includeContentError', src);
+            }
+          });
+          scope.$emit('$includeContentRequested', src);
+        } else {
+          cleanupLastIncludeContent();
+        }
+      });
+
+      scope.$on('$destroy', cleanupLastIncludeContent);
+    }
+  };
+}])
+
+/**
+ * Note that it's intentional that these classes are *not* applied through $animate.
+ * They must not be animated as they're expected to be present on the tooltip on
+ * initialization.
+ */
+.directive('uibTooltipClasses', function() {
+  return {
+    restrict: 'A',
+    link: function(scope, element, attrs) {
+      if (scope.placement) {
+        element.addClass(scope.placement);
+      }
+
+      if (scope.popupClass) {
+        element.addClass(scope.popupClass);
+      }
+
+      if (scope.animation()) {
+        element.addClass(attrs.tooltipAnimationClass);
+      }
+    }
+  };
+})
+
+.directive('uibTooltipPopup', function() {
+  return {
+    replace: true,
+    scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
+    templateUrl: 'template/tooltip/tooltip-popup.html',
+    link: function(scope, element) {
+      element.addClass('tooltip');
+    }
+  };
+})
+
+.directive('uibTooltip', [ '$uibTooltip', function($uibTooltip) {
+  return $uibTooltip('uibTooltip', 'tooltip', 'mouseenter');
+}])
+
+.directive('uibTooltipTemplatePopup', function() {
+  return {
+    replace: true,
+    scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
+      originScope: '&' },
+    templateUrl: 'template/tooltip/tooltip-template-popup.html',
+    link: function(scope, element) {
+      element.addClass('tooltip');
+    }
+  };
+})
+
+.directive('uibTooltipTemplate', ['$uibTooltip', function($uibTooltip) {
+  return $uibTooltip('uibTooltipTemplate', 'tooltip', 'mouseenter', {
+    useContentExp: true
+  });
+}])
+
+.directive('uibTooltipHtmlPopup', function() {
+  return {
+    replace: true,
+    scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
+    templateUrl: 'template/tooltip/tooltip-html-popup.html',
+    link: function(scope, element) {
+      element.addClass('tooltip');
+    }
+  };
+})
+
+.directive('uibTooltipHtml', ['$uibTooltip', function($uibTooltip) {
+  return $uibTooltip('uibTooltipHtml', 'tooltip', 'mouseenter', {
+    useContentExp: true
+  });
+}]);
+
+/* Deprecated tooltip below */
+
+angular.module('ui.bootstrap.tooltip')
+
+.value('$tooltipSuppressWarning', false)
+
+.provider('$tooltip', ['$uibTooltipProvider', function($uibTooltipProvider) {
+  angular.extend(this, $uibTooltipProvider);
+
+  this.$get = ['$log', '$tooltipSuppressWarning', '$injector', function($log, $tooltipSuppressWarning, $injector) {
+    if (!$tooltipSuppressWarning) {
+      $log.warn('$tooltip is now deprecated. Use $uibTooltip instead.');
+    }
+
+    return $injector.invoke($uibTooltipProvider.$get);
+  }];
+}])
+
+// This is mostly ngInclude code but with a custom scope
+.directive('tooltipTemplateTransclude', [
+         '$animate', '$sce', '$compile', '$templateRequest', '$log', '$tooltipSuppressWarning',
+function ($animate ,  $sce ,  $compile ,  $templateRequest,   $log,   $tooltipSuppressWarning) {
+  return {
+    link: function(scope, elem, attrs) {
+      if (!$tooltipSuppressWarning) {
+        $log.warn('tooltip-template-transclude is now deprecated. Use uib-tooltip-template-transclude instead.');
+      }
+
+      var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope);
+
+      var changeCounter = 0,
+        currentScope,
+        previousElement,
+        currentElement;
+
+      var cleanupLastIncludeContent = function() {
+        if (previousElement) {
+          previousElement.remove();
+          previousElement = null;
+        }
+        if (currentScope) {
+          currentScope.$destroy();
+          currentScope = null;
+        }
+        if (currentElement) {
+          $animate.leave(currentElement).then(function() {
+            previousElement = null;
+          });
+          previousElement = currentElement;
+          currentElement = null;
+        }
+      };
+
+      scope.$watch($sce.parseAsResourceUrl(attrs.tooltipTemplateTransclude), function(src) {
+        var thisChangeId = ++changeCounter;
+
+        if (src) {
+          //set the 2nd param to true to ignore the template request error so that the inner
+          //contents and scope can be cleaned up.
+          $templateRequest(src, true).then(function(response) {
+            if (thisChangeId !== changeCounter) { return; }
+            var newScope = origScope.$new();
+            var template = response;
+
+            var clone = $compile(template)(newScope, function(clone) {
+              cleanupLastIncludeContent();
+              $animate.enter(clone, elem);
+            });
+
+            currentScope = newScope;
+            currentElement = clone;
+
+            currentScope.$emit('$includeContentLoaded', src);
+          }, function() {
+            if (thisChangeId === changeCounter) {
+              cleanupLastIncludeContent();
+              scope.$emit('$includeContentError', src);
+            }
+          });
+          scope.$emit('$includeContentRequested', src);
+        } else {
+          cleanupLastIncludeContent();
+        }
+      });
+
+      scope.$on('$destroy', cleanupLastIncludeContent);
+    }
+  };
+}])
+
+.directive('tooltipClasses', ['$log', '$tooltipSuppressWarning', function($log, $tooltipSuppressWarning) {
+  return {
+    restrict: 'A',
+    link: function(scope, element, attrs) {
+      if (!$tooltipSuppressWarning) {
+        $log.warn('tooltip-classes is now deprecated. Use uib-tooltip-classes instead.');
+      }
+
+      if (scope.placement) {
+        element.addClass(scope.placement);
+      }
+      if (scope.popupClass) {
+        element.addClass(scope.popupClass);
+      }
+      if (scope.animation()) {
+        element.addClass(attrs.tooltipAnimationClass);
+      }
+    }
+  };
+}])
+
+.directive('tooltipPopup', ['$log', '$tooltipSuppressWarning', function($log, $tooltipSuppressWarning) {
+  return {
+    replace: true,
+    scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
+    templateUrl: 'template/tooltip/tooltip-popup.html',
+    link: function(scope, element) {
+      if (!$tooltipSuppressWarning) {
+        $log.warn('tooltip-popup is now deprecated. Use uib-tooltip-popup instead.');
+      }
+
+      element.addClass('tooltip');
+    }
+  };
+}])
+
+.directive('tooltip', ['$tooltip', function($tooltip) {
+  return $tooltip('tooltip', 'tooltip', 'mouseenter');
+}])
+
+.directive('tooltipTemplatePopup', ['$log', '$tooltipSuppressWarning', function($log, $tooltipSuppressWarning) {
+  return {
+    replace: true,
+    scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
+      originScope: '&' },
+    templateUrl: 'template/tooltip/tooltip-template-popup.html',
+    link: function(scope, element) {
+      if (!$tooltipSuppressWarning) {
+        $log.warn('tooltip-template-popup is now deprecated. Use uib-tooltip-template-popup instead.');
+      }
+
+      element.addClass('tooltip');
+    }
+  };
+}])
+
+.directive('tooltipTemplate', ['$tooltip', function($tooltip) {
+  return $tooltip('tooltipTemplate', 'tooltip', 'mouseenter', {
+    useContentExp: true
+  });
+}])
+
+.directive('tooltipHtmlPopup', ['$log', '$tooltipSuppressWarning', function($log, $tooltipSuppressWarning) {
+  return {
+    replace: true,
+    scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
+    templateUrl: 'template/tooltip/tooltip-html-popup.html',
+    link: function(scope, element) {
+      if (!$tooltipSuppressWarning) {
+        $log.warn('tooltip-html-popup is now deprecated. Use uib-tooltip-html-popup instead.');
+      }
+
+      element.addClass('tooltip');
+    }
+  };
+}])
+
+.directive('tooltipHtml', ['$tooltip', function($tooltip) {
+  return $tooltip('tooltipHtml', 'tooltip', 'mouseenter', {
+    useContentExp: true
+  });
+}]);
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/docs/demo.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/docs/demo.html
new file mode 100644
index 0000000..ff4dea9
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/docs/demo.html
@@ -0,0 +1,74 @@
+<style>
+  .typeahead-demo .custom-popup-wrapper {
+    position: absolute;
+    top: 100%;
+    left: 0;
+    z-index: 1000;
+    display: none;
+    background-color: #f9f9f9;
+  }
+
+  .typeahead-demo .custom-popup-wrapper > .message {
+    padding: 10px 20px;
+    border-bottom: 1px solid #ddd;
+    color: #868686;
+  }
+
+  .typeahead-demo .custom-popup-wrapper > .dropdown-menu {
+    position: static;
+    float: none;
+    display: block;
+    min-width: 160px;
+    background-color: transparent;
+    border: none;
+    border-radius: 0;
+    box-shadow: none;
+  }
+</style>
+
+<script type="text/ng-template" id="customTemplate.html">
+  <a>
+      <img ng-src="http://upload.wikimedia.org/wikipedia/commons/thumb/{{match.model.flag}}" width="16">
+      <span ng-bind-html="match.label | uibTypeaheadHighlight:query"></span>
+  </a>
+</script>
+
+<script type="text/ng-template" id="customPopupTemplate.html">
+  <div class="custom-popup-wrapper"
+     ng-style="{top: position().top+'px', left: position().left+'px'}"
+     style="display: block;"
+     ng-show="isOpen() && !moveInProgress"
+     aria-hidden="{{!isOpen()}}">
+    <p class="message">select location from drop down.</p>
+
+    <ul class="dropdown-menu" role="listbox">
+        <li ng-repeat="match in matches track by $index" ng-class="{active: isActive($index) }"
+            ng-mouseenter="selectActive($index)" ng-click="selectMatch($index)" role="option" id="{{::match.id}}">
+            <div uib-typeahead-match index="$index" match="match" query="query" template-url="templateUrl"></div>
+        </li>
+    </ul>
+  </div>
+</script>
+
+<div class='container-fluid typeahead-demo' ng-controller="TypeaheadCtrl">
+
+    <h4>Static arrays</h4>
+    <pre>Model: {{selected | json}}</pre>
+    <input type="text" ng-model="selected" uib-typeahead="state for state in states | filter:$viewValue | limitTo:8" class="form-control">
+
+    <h4>Asynchronous results</h4>
+    <pre>Model: {{asyncSelected | json}}</pre>
+    <input type="text" ng-model="asyncSelected" placeholder="Locations loaded via $http" uib-typeahead="address for address in getLocation($viewValue)" typeahead-loading="loadingLocations" typeahead-no-results="noResults" class="form-control">
+    <i ng-show="loadingLocations" class="glyphicon glyphicon-refresh"></i>
+    <div ng-show="noResults">
+      <i class="glyphicon glyphicon-remove"></i> No Results Found
+    </div>
+
+    <h4>Custom templates for results</h4>
+    <pre>Model: {{customSelected | json}}</pre>
+    <input type="text" ng-model="customSelected" placeholder="Custom template" uib-typeahead="state as state.name for state in statesWithFlags | filter:{name:$viewValue}" typeahead-template-url="customTemplate.html" class="form-control">
+
+    <h4>Custom popup templates for typeahead's dropdown</h4>
+    <pre>Model: {{customPopupSelected | json}}</pre>
+    <input type="text" ng-model="customPopupSelected" placeholder="Custom popup template" uib-typeahead="state as state.name for state in statesWithFlags | filter:{name:$viewValue}" typeahead-popup-template-url="customPopupTemplate.html" class="form-control">
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/docs/demo.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/docs/demo.js
new file mode 100644
index 0000000..0148ec4
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/docs/demo.js
@@ -0,0 +1,20 @@
+angular.module('ui.bootstrap.demo').controller('TypeaheadCtrl', function($scope, $http) {
+
+  $scope.selected = undefined;
+  $scope.states = ['Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut', 'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico', 'New York', 'North Dakota', 'North Carolina', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania', 'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming'];
+  // Any function returning a promise object can be used to load values asynchronously
+  $scope.getLocation = function(val) {
+    return $http.get('//maps.googleapis.com/maps/api/geocode/json', {
+      params: {
+        address: val,
+        sensor: false
+      }
+    }).then(function(response){
+      return response.data.results.map(function(item){
+        return item.formatted_address;
+      });
+    });
+  };
+
+  $scope.statesWithFlags = [{'name':'Alabama','flag':'5/5c/Flag_of_Alabama.svg/45px-Flag_of_Alabama.svg.png'},{'name':'Alaska','flag':'e/e6/Flag_of_Alaska.svg/43px-Flag_of_Alaska.svg.png'},{'name':'Arizona','flag':'9/9d/Flag_of_Arizona.svg/45px-Flag_of_Arizona.svg.png'},{'name':'Arkansas','flag':'9/9d/Flag_of_Arkansas.svg/45px-Flag_of_Arkansas.svg.png'},{'name':'California','flag':'0/01/Flag_of_California.svg/45px-Flag_of_California.svg.png'},{'name':'Colorado','flag':'4/46/Flag_of_Colorado.svg/45px-Flag_of_Colorado.svg.png'},{'name':'Connecticut','flag':'9/96/Flag_of_Connecticut.svg/39px-Flag_of_Connecticut.svg.png'},{'name':'Delaware','flag':'c/c6/Flag_of_Delaware.svg/45px-Flag_of_Delaware.svg.png'},{'name':'Florida','flag':'f/f7/Flag_of_Florida.svg/45px-Flag_of_Florida.svg.png'},{'name':'Georgia','flag':'5/54/Flag_of_Georgia_%28U.S._state%29.svg/46px-Flag_of_Georgia_%28U.S._state%29.svg.png'},{'name':'Hawaii','flag':'e/ef/Flag_of_Hawaii.svg/46px-Flag_of_Hawaii.svg.png'},{'name':'Idaho','flag':'a/a4/Flag_of_Idaho.svg/38px-Flag_of_Idaho.svg.png'},{'name':'Illinois','flag':'0/01/Flag_of_Illinois.svg/46px-Flag_of_Illinois.svg.png'},{'name':'Indiana','flag':'a/ac/Flag_of_Indiana.svg/45px-Flag_of_Indiana.svg.png'},{'name':'Iowa','flag':'a/aa/Flag_of_Iowa.svg/44px-Flag_of_Iowa.svg.png'},{'name':'Kansas','flag':'d/da/Flag_of_Kansas.svg/46px-Flag_of_Kansas.svg.png'},{'name':'Kentucky','flag':'8/8d/Flag_of_Kentucky.svg/46px-Flag_of_Kentucky.svg.png'},{'name':'Louisiana','flag':'e/e0/Flag_of_Louisiana.svg/46px-Flag_of_Louisiana.svg.png'},{'name':'Maine','flag':'3/35/Flag_of_Maine.svg/45px-Flag_of_Maine.svg.png'},{'name':'Maryland','flag':'a/a0/Flag_of_Maryland.svg/45px-Flag_of_Maryland.svg.png'},{'name':'Massachusetts','flag':'f/f2/Flag_of_Massachusetts.svg/46px-Flag_of_Massachusetts.svg.png'},{'name':'Michigan','flag':'b/b5/Flag_of_Michigan.svg/45px-Flag_of_Michigan.svg.png'},{'name':'Minnesota','flag':'b/b9/Flag_of_Minnesota.svg/46px-Flag_of_Minnesota.svg.png'},{'name':'Mississippi','flag':'4/42/Flag_of_Mississippi.svg/45px-Flag_of_Mississippi.svg.png'},{'name':'Missouri','flag':'5/5a/Flag_of_Missouri.svg/46px-Flag_of_Missouri.svg.png'},{'name':'Montana','flag':'c/cb/Flag_of_Montana.svg/45px-Flag_of_Montana.svg.png'},{'name':'Nebraska','flag':'4/4d/Flag_of_Nebraska.svg/46px-Flag_of_Nebraska.svg.png'},{'name':'Nevada','flag':'f/f1/Flag_of_Nevada.svg/45px-Flag_of_Nevada.svg.png'},{'name':'New Hampshire','flag':'2/28/Flag_of_New_Hampshire.svg/45px-Flag_of_New_Hampshire.svg.png'},{'name':'New Jersey','flag':'9/92/Flag_of_New_Jersey.svg/45px-Flag_of_New_Jersey.svg.png'},{'name':'New Mexico','flag':'c/c3/Flag_of_New_Mexico.svg/45px-Flag_of_New_Mexico.svg.png'},{'name':'New York','flag':'1/1a/Flag_of_New_York.svg/46px-Flag_of_New_York.svg.png'},{'name':'North Carolina','flag':'b/bb/Flag_of_North_Carolina.svg/45px-Flag_of_North_Carolina.svg.png'},{'name':'North Dakota','flag':'e/ee/Flag_of_North_Dakota.svg/38px-Flag_of_North_Dakota.svg.png'},{'name':'Ohio','flag':'4/4c/Flag_of_Ohio.svg/46px-Flag_of_Ohio.svg.png'},{'name':'Oklahoma','flag':'6/6e/Flag_of_Oklahoma.svg/45px-Flag_of_Oklahoma.svg.png'},{'name':'Oregon','flag':'b/b9/Flag_of_Oregon.svg/46px-Flag_of_Oregon.svg.png'},{'name':'Pennsylvania','flag':'f/f7/Flag_of_Pennsylvania.svg/45px-Flag_of_Pennsylvania.svg.png'},{'name':'Rhode Island','flag':'f/f3/Flag_of_Rhode_Island.svg/32px-Flag_of_Rhode_Island.svg.png'},{'name':'South Carolina','flag':'6/69/Flag_of_South_Carolina.svg/45px-Flag_of_South_Carolina.svg.png'},{'name':'South Dakota','flag':'1/1a/Flag_of_South_Dakota.svg/46px-Flag_of_South_Dakota.svg.png'},{'name':'Tennessee','flag':'9/9e/Flag_of_Tennessee.svg/46px-Flag_of_Tennessee.svg.png'},{'name':'Texas','flag':'f/f7/Flag_of_Texas.svg/45px-Flag_of_Texas.svg.png'},{'name':'Utah','flag':'f/f6/Flag_of_Utah.svg/45px-Flag_of_Utah.svg.png'},{'name':'Vermont','flag':'4/49/Flag_of_Vermont.svg/46px-Flag_of_Vermont.svg.png'},{'name':'Virginia','flag':'4/47/Flag_of_Virginia.svg/44px-Flag_of_Virginia.svg.png'},{'name':'Washington','flag':'5/54/Flag_of_Washington.svg/46px-Flag_of_Washington.svg.png'},{'name':'West Virginia','flag':'2/22/Flag_of_West_Virginia.svg/46px-Flag_of_West_Virginia.svg.png'},{'name':'Wisconsin','flag':'2/22/Flag_of_Wisconsin.svg/45px-Flag_of_Wisconsin.svg.png'},{'name':'Wyoming','flag':'b/bc/Flag_of_Wyoming.svg/43px-Flag_of_Wyoming.svg.png'}];
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/docs/readme.md b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/docs/readme.md
new file mode 100644
index 0000000..e5539fd
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/docs/readme.md
@@ -0,0 +1,80 @@
+Typeahead is a AngularJS version of [Bootstrap v2's typeahead plugin](http://getbootstrap.com/2.3.2/javascript.html#typeahead).
+This directive can be used to quickly create elegant typeaheads with any form text input.
+
+It is very well integrated into AngularJS as it uses a subset of the
+[select directive](http://docs.angularjs.org/api/ng.directive:select) syntax, which is very flexible. Supported expressions are:
+
+* _label_ for _value_ in _sourceArray_
+* _select_ as _label_ for _value_ in _sourceArray_
+
+The `sourceArray` expression can use a special `$viewValue` variable that corresponds to the value entered inside the input.
+
+This directive works with promises, meaning you can retrieve matches using the `$http` service with minimal effort.
+
+The typeahead directives provide several attributes:
+
+* `ng-model` <i class="glyphicon glyphicon-eye-open"></i>
+   :
+   Assignable angular expression to data-bind to
+
+* `typeahead` <i class="glyphicon glyphicon-eye-open"></i>
+   :
+   Comprehension Angular expression (see [select directive](http://docs.angularjs.org/api/ng.directive:select))
+
+* `typeahead-append-to-body` <i class="glyphicon glyphicon-eye-open"></i>
+   _(Defaults: false)_ : Should the typeahead popup be appended to $body instead of the parent element?
+
+* `typeahead-append-to-element-id`
+   _(Defaults: false)_ : Should the typeahead popup be appended to an element id instead of the parent element?
+
+* `typeahead-editable`
+   _(Defaults: true)_ :
+   Should it restrict model values to the ones selected from the popup only ?
+   
+* `typeahead-focus-first`
+   _(Defaults: true)_ :
+   Should the first match automatically be focused as you type?
+
+* `typeahead-input-formatter` <i class="glyphicon glyphicon-eye-open"></i>
+   _(Defaults: undefined)_ :
+   Format the ng-model result after selection
+
+* `typeahead-loading` <i class="glyphicon glyphicon-eye-open"></i>
+   _(Defaults: angular.noop)_ :
+   Binding to a variable that indicates if matches are being retrieved asynchronously
+
+* `typeahead-min-length` <i class="glyphicon glyphicon-eye-open"></i>
+   _(Defaults: 1)_ :
+   Minimal no of characters that needs to be entered before typeahead kicks-in. Must be greater than or equal to 1.
+
+* `typeahead-no-results` <i class="glyphicon glyphicon-eye-open"></i>
+   _(Defaults: angular.noop)_ :
+   Binding to a variable that indicates if no matching results were found
+
+* `typeahead-on-select($item, $model, $label)`
+   _(Defaults: null)_ :
+   A callback executed when a match is selected
+
+* `typeahead-select-on-exact`
+   _(Defaults: false)_ :
+   Should it automatically select an item when there is one option that exactly matches the user input?
+
+* `typeahead-template-url` <i class="glyphicon glyphicon-eye-open"></i>
+   :
+   Set custom item template
+
+* `typeahead-popup-template-url`
+   _(Defaults: `template/typeahead/typeahead-popup.html`)_ :
+   Set custom popup template
+
+* `typeahead-wait-ms` <i class="glyphicon glyphicon-eye-open"></i>
+   _(Defaults: 0)_ :
+   Minimal wait time after last character typed before typeahead kicks-in
+
+* `typeahead-select-on-blur`
+   _(Defaults: false)_ :
+   On blur, select the currently highlighted match
+
+* `typeahead-focus-on-select`
+   _(Defaults: true) :
+   On selection, focus the input element the typeahead directive is associated with
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/test/typeahead-highlight-ngsanitize.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/test/typeahead-highlight-ngsanitize.spec.js
new file mode 100644
index 0000000..de24319
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/test/typeahead-highlight-ngsanitize.spec.js
@@ -0,0 +1,16 @@
+describe('Security concerns', function() {
+  var highlightFilter, $sanitize, logSpy;
+
+  beforeEach(module('ui.bootstrap.typeahead', 'ngSanitize'));
+
+  beforeEach(inject(function (uibTypeaheadHighlightFilter, _$sanitize_, $log) {
+    highlightFilter = uibTypeaheadHighlightFilter;
+    $sanitize = _$sanitize_;
+    logSpy = spyOn($log, 'warn');
+  }));
+
+  it('should not call the $log service when ngSanitize is present', function() {
+    highlightFilter('before <script src="">match</script> after', 'match');
+    expect(logSpy).not.toHaveBeenCalled();
+  });
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/test/typeahead-highlight.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/test/typeahead-highlight.spec.js
new file mode 100644
index 0000000..c696587
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/test/typeahead-highlight.spec.js
@@ -0,0 +1,80 @@
+describe('typeaheadHighlight', function () {
+
+  var highlightFilter, $log, $sce, logSpy;
+
+  beforeEach(module('ui.bootstrap.typeahead'));
+
+  beforeEach(inject(function(_$log_, _$sce_) {
+    $log = _$log_;
+    $sce = _$sce_;
+    logSpy = spyOn($log, 'warn');
+  }));
+
+  beforeEach(inject(function(uibTypeaheadHighlightFilter) {
+    highlightFilter = uibTypeaheadHighlightFilter;
+  }));
+
+  it('should higlight a match', function() {
+    expect($sce.getTrustedHtml(highlightFilter('before match after', 'match'))).toEqual('before <strong>match</strong> after');
+  });
+
+  it('should higlight a match with mixed case', function() {
+    expect($sce.getTrustedHtml(highlightFilter('before MaTch after', 'match'))).toEqual('before <strong>MaTch</strong> after');
+  });
+
+  it('should higlight all matches', function() {
+    expect($sce.getTrustedHtml(highlightFilter('before MaTch after match', 'match'))).toEqual('before <strong>MaTch</strong> after <strong>match</strong>');
+  });
+
+  it('should do nothing if no match', function() {
+    expect($sce.getTrustedHtml(highlightFilter('before match after', 'nomatch'))).toEqual('before match after');
+  });
+
+  it('should do nothing if no or empty query', function() {
+    expect($sce.getTrustedHtml(highlightFilter('before match after', ''))).toEqual('before match after');
+    expect($sce.getTrustedHtml(highlightFilter('before match after', null))).toEqual('before match after');
+    expect($sce.getTrustedHtml(highlightFilter('before match after', undefined))).toEqual('before match after');
+  });
+
+  it('issue 316 - should work correctly for regexp reserved words', function() {
+    expect($sce.getTrustedHtml(highlightFilter('before (match after', '(match'))).toEqual('before <strong>(match</strong> after');
+  });
+
+  it('issue 1777 - should work correctly with numeric values', function() {
+    expect($sce.getTrustedHtml(highlightFilter(123, '2'))).toEqual('1<strong>2</strong>3');
+  });
+
+  it('should show a warning when this component is being used unsafely', function() {
+    highlightFilter('<i>before</i> match after', 'match');
+    expect(logSpy).toHaveBeenCalled();
+  });
+});
+
+/* Deprecation tests below */
+
+describe('typeahead highlightFilter deprecated', function(){
+  var highlightFilter, $log, $sce, logSpy;
+
+  beforeEach(module('ui.bootstrap.typeahead'));
+
+  it('should supress the warning by default', function(){
+    module(function($provide) {
+      $provide.value('$typeaheadSuppressWarning', true);
+    });
+    
+    inject(function($compile, $log, $rootScope, typeaheadHighlightFilter, $sce){
+      spyOn($log, 'warn');
+      var highlightFilter = typeaheadHighlightFilter;
+      $sce.getTrustedHtml(highlightFilter('before match after', 'match'));
+      expect($log.warn.calls.count()).toBe(0);
+    });
+  });
+  
+  it('should decrecate typeaheadHighlightFilter', inject(function($compile, $log, $rootScope, typeaheadHighlightFilter, $sce){
+    spyOn($log, 'warn');
+    var highlightFilter = typeaheadHighlightFilter;
+    $sce.getTrustedHtml(highlightFilter('before match after', 'match'));
+    expect($log.warn.calls.count()).toBe(1);
+    expect($log.warn.calls.argsFor(0)).toEqual(['typeaheadHighlight is now deprecated. Use uibTypeaheadHighlight instead.']);
+  }));
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/test/typeahead-parser.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/test/typeahead-parser.spec.js
new file mode 100644
index 0000000..f19aec5
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/test/typeahead-parser.spec.js
@@ -0,0 +1,79 @@
+describe('syntax parser', function() {
+  var typeaheadParser, scope, filterFilter;
+
+  beforeEach(module('ui.bootstrap.typeahead'));
+  beforeEach(inject(function(_$rootScope_, _filterFilter_, _typeaheadParser_) {
+    typeaheadParser = _typeaheadParser_;
+    scope = _$rootScope_;
+    filterFilter = _filterFilter_;
+  }));
+
+  it('should parse the simplest array-based syntax', function() {
+    scope.states = ['Alabama', 'California', 'Delaware'];
+    var result = typeaheadParser.parse('state for state in states | filter:$viewValue');
+
+    var itemName = result.itemName;
+    var locals = {$viewValue:'al'};
+    expect(result.source(scope, locals)).toEqual(['Alabama', 'California']);
+
+    locals[itemName] = 'Alabama';
+    expect(result.viewMapper(scope, locals)).toEqual('Alabama');
+    expect(result.modelMapper(scope, locals)).toEqual('Alabama');
+  });
+
+  it('should parse the simplest function-based syntax', function() {
+    scope.getStates = function($viewValue) {
+      return filterFilter(['Alabama', 'California', 'Delaware'], $viewValue);
+    };
+    var result = typeaheadParser.parse('state for state in getStates($viewValue)');
+
+    var itemName = result.itemName;
+    var locals = {$viewValue:'al'};
+    expect(result.source(scope, locals)).toEqual(['Alabama', 'California']);
+
+    locals[itemName] = 'Alabama';
+    expect(result.viewMapper(scope, locals)).toEqual('Alabama');
+    expect(result.modelMapper(scope, locals)).toEqual('Alabama');
+  });
+
+  it('should allow to specify custom model mapping that is used as a label as well', function () {
+    scope.states = [
+      {code:'AL', name:'Alabama'},
+      {code:'CA', name:'California'},
+      {code:'DE', name:'Delaware'}
+    ];
+    var result = typeaheadParser.parse('state.name for state in states | filter:$viewValue | orderBy:"name":true');
+
+    var itemName = result.itemName;
+    expect(itemName).toEqual('state');
+    expect(result.source(scope, {$viewValue:'al'})).toEqual([
+      {code:'CA', name:'California'},
+      {code:'AL', name:'Alabama'}
+    ]);
+
+    var locals = {$viewValue:'al'};
+    locals[itemName] = {code:'AL', name:'Alabama'};
+    expect(result.viewMapper(scope, locals)).toEqual('Alabama');
+    expect(result.modelMapper(scope, locals)).toEqual('Alabama');
+  });
+
+  it('should allow to specify custom view and model mappers', function() {
+    scope.states = [
+      {code:'AL', name:'Alabama'},
+      {code:'CA', name:'California'},
+      {code:'DE', name:'Delaware'}
+    ];
+    var result = typeaheadParser.parse('state.code as state.name + " ("+state.code+")" for state in states | filter:$viewValue | orderBy:"name":true');
+
+    var itemName = result.itemName;
+    expect(result.source(scope, {$viewValue:'al'})).toEqual([
+      {code:'CA', name:'California'},
+      {code:'AL', name:'Alabama'}
+    ]);
+
+    var locals = {$viewValue:'al'};
+    locals[itemName] = {code:'AL', name:'Alabama'};
+    expect(result.viewMapper(scope, locals)).toEqual('Alabama (AL)');
+    expect(result.modelMapper(scope, locals)).toEqual('AL');
+  });
+});
\ No newline at end of file
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/test/typeahead-popup.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/test/typeahead-popup.spec.js
new file mode 100644
index 0000000..6393660
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/test/typeahead-popup.spec.js
@@ -0,0 +1,101 @@
+describe('typeaheadPopup - result rendering', function() {
+  var scope, $rootScope, $compile;
+
+  beforeEach(module('ui.bootstrap.typeahead'));
+  beforeEach(module('template/typeahead/typeahead-popup.html'));
+  beforeEach(module('template/typeahead/typeahead-match.html'));
+  beforeEach(inject(function(_$rootScope_, _$compile_) {
+    $rootScope = _$rootScope_;
+    scope = $rootScope.$new();
+    $compile = _$compile_;
+  }));
+
+  it('should render initial results', function() {
+    scope.matches = ['foo', 'bar', 'baz'];
+    scope.active = 1;
+
+    var el = $compile('<div><uib-typeahead-popup matches="matches" active="active" select="select(activeIdx)"></uib-typeahead-popup></div>')(scope);
+    $rootScope.$digest();
+
+    var liElems = el.find('li');
+    expect(liElems.length).toEqual(3);
+    expect(liElems.eq(0)).not.toHaveClass('active');
+    expect(liElems.eq(1)).toHaveClass('active');
+    expect(liElems.eq(2)).not.toHaveClass('active');
+  });
+
+  it('should change active item on mouseenter', function() {
+    scope.matches = ['foo', 'bar', 'baz'];
+    scope.active = 1;
+
+    var el = $compile('<div><uib-typeahead-popup matches="matches" active="active" select="select(activeIdx)"></uib-typeahead-popup></div>')(scope);
+    $rootScope.$digest();
+
+    var liElems = el.find('li');
+    expect(liElems.eq(1)).toHaveClass('active');
+    expect(liElems.eq(2)).not.toHaveClass('active');
+
+    liElems.eq(2).trigger('mouseenter');
+
+    expect(liElems.eq(1)).not.toHaveClass('active');
+    expect(liElems.eq(2)).toHaveClass('active');
+  });
+
+  it('should select an item on mouse click', function() {
+    scope.matches = ['foo', 'bar', 'baz'];
+    scope.active = 1;
+    $rootScope.select = angular.noop;
+    spyOn($rootScope, 'select');
+
+    var el = $compile('<div><uib-typeahead-popup matches="matches" active="active" select="select(activeIdx)"></uib-typeahead-popup></div>')(scope);
+    $rootScope.$digest();
+
+    var liElems = el.find('li');
+    liElems.eq(2).find('a').trigger('click');
+    expect($rootScope.select).toHaveBeenCalledWith(2);
+  });
+});
+
+/* Deprecation tests below */
+
+describe('typeaheadPopup deprecation', function() {
+  beforeEach(module('ui.bootstrap.typeahead'));
+  beforeEach(module('ngSanitize'));
+  beforeEach(module('template/typeahead/typeahead-popup.html'));
+  beforeEach(module('template/typeahead/typeahead-match.html'));
+
+  it('should suppress warning', function() {
+    module(function($provide) {
+      $provide.value('$typeaheadSuppressWarning', true);
+    });
+
+    inject(function($compile, $log, $rootScope) {
+      var scope = $rootScope.$new();
+      scope.matches = ['foo', 'bar', 'baz'];
+      scope.active = 1;
+      $rootScope.select = angular.noop;
+      spyOn($log, 'warn');
+
+      var element = '<div><typeahead-popup matches="matches" active="active" select="select(activeIdx)"></typeahead-popup></div>';
+      element = $compile(element)(scope);
+      $rootScope.$digest();
+      expect($log.warn.calls.count()).toBe(0);
+    });
+  });
+
+  it('should give warning by default', inject(function($compile, $log, $rootScope) {
+    var scope = $rootScope.$new();
+    scope.matches = ['foo', 'bar', 'baz'];
+    scope.active = 1;
+    $rootScope.select = angular.noop;
+    spyOn($log, 'warn');
+
+    var element = '<div><typeahead-popup matches="matches" active="active" select="select(activeIdx)"></typeahead-popup></div>';
+    element = $compile(element)(scope);
+
+    $rootScope.$digest();
+
+    expect($log.warn.calls.count()).toBe(1);
+    expect($log.warn.calls.argsFor(0)).toEqual(['typeahead-popup is now deprecated. Use uib-typeahead-popup instead.']);
+  }));
+});
\ No newline at end of file
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/test/typeahead.spec.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/test/typeahead.spec.js
new file mode 100644
index 0000000..040a228
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/test/typeahead.spec.js
@@ -0,0 +1,1097 @@
+describe('typeahead tests', function() {
+  var $scope, $compile, $document, $templateCache, $timeout, $window;
+  var changeInputValueTo;
+
+  beforeEach(module('ui.bootstrap.typeahead'));
+  beforeEach(module('ngSanitize'));
+  beforeEach(module('template/typeahead/typeahead-popup.html'));
+  beforeEach(module('template/typeahead/typeahead-match.html'));
+  beforeEach(module(function($compileProvider) {
+    $compileProvider.directive('formatter', function() {
+      return {
+        require: 'ngModel',
+        link: function (scope, elm, attrs, ngModelCtrl) {
+          ngModelCtrl.$formatters.unshift(function(viewVal) {
+            return 'formatted' + viewVal;
+          });
+        }
+      };
+    });
+    $compileProvider.directive('childDirective', function() {
+      return {
+          restrict: 'A',
+          require: '^parentDirective',
+          link: function(scope, element, attrs, ctrl) {}
+      };
+    });
+  }));
+  beforeEach(inject(function(_$rootScope_, _$compile_, _$document_, _$templateCache_, _$timeout_, _$window_, $sniffer) {
+    $scope = _$rootScope_;
+    $scope.source = ['foo', 'bar', 'baz'];
+    $scope.states = [
+      {code: 'AL', name: 'Alaska'},
+      {code: 'CL', name: 'California'}
+    ];
+    $compile = _$compile_;
+    $document = _$document_;
+    $templateCache = _$templateCache_;
+    $timeout = _$timeout_;
+    $window = _$window_;
+    changeInputValueTo = function(element, value) {
+      var inputEl = findInput(element);
+      inputEl.val(value);
+      inputEl.trigger($sniffer.hasEvent('input') ? 'input' : 'change');
+      $scope.$digest();
+    };
+  }));
+
+  //utility functions
+  var prepareInputEl = function(inputTpl) {
+    var el = $compile(angular.element(inputTpl))($scope);
+    $scope.$digest();
+    return el;
+  };
+
+  var findInput = function(element) {
+    return element.find('input');
+  };
+
+  var findDropDown = function(element) {
+    return element.find('ul.dropdown-menu');
+  };
+
+  var findMatches = function(element) {
+    return findDropDown(element).find('li');
+  };
+
+  var triggerKeyDown = function(element, keyCode) {
+    var inputEl = findInput(element);
+    var e = $.Event('keydown');
+    e.which = keyCode;
+    inputEl.trigger(e);
+  };
+
+  //custom matchers
+  beforeEach(function () {
+    jasmine.addMatchers({
+      toBeClosed: function(util, customEqualityTesters) {
+        return {
+          compare: function(actual, expected) {
+            var typeaheadEl = findDropDown(actual);
+
+            var result = {
+              pass: util.equals(typeaheadEl.hasClass('ng-hide'), true, customEqualityTesters)
+            };
+
+            if (result.pass) {
+              result.message = 'Expected "' + angular.mock.dump(typeaheadEl) + '" not to be closed.';
+            } else {
+              result.message = 'Expected "' + angular.mock.dump(typeaheadEl) + '" to be closed.';
+            }
+
+            return result;
+          }
+        };
+      },
+      toBeOpenWithActive: function(util, customEqualityTesters) {
+        return {
+          compare: function(actual, noOfMatches, activeIdx) {
+            var typeaheadEl = findDropDown(actual);
+            var liEls = findMatches(actual);
+
+            var result = {
+              pass: util.equals(typeaheadEl.length, 1, customEqualityTesters) &&
+                    util.equals(typeaheadEl.hasClass('ng-hide'), false, customEqualityTesters) &&
+                    util.equals(liEls.length, noOfMatches, customEqualityTesters) &&
+                    activeIdx === -1 ? !$(liEls).hasClass('active') : $(liEls[activeIdx]).hasClass('active')
+            };
+
+            if (result.pass) {
+              result.message = 'Expected "' + actual + '" not to be opened.';
+            } else {
+              result.message = 'Expected "' + actual + '" to be opened.';
+            }
+
+            return result;
+          }
+        };
+      }
+    });
+  });
+
+  afterEach(function() {
+    findDropDown($document.find('body')).remove();
+  });
+
+  //coarse grained, "integration" tests
+  describe('initial state and model changes', function() {
+    it('should be closed by default', function() {
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source"></div>');
+      expect(element).toBeClosed();
+    });
+
+    it('should correctly render initial state if the "as" keyword is used', function() {
+      $scope.result = $scope.states[0];
+
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="state as state.name for state in states"></div>');
+      var inputEl = findInput(element);
+
+      expect(inputEl.val()).toEqual('Alaska');
+    });
+
+    it('should default to bound model for initial rendering if there is not enough info to render label', function() {
+      $scope.result = $scope.states[0].code;
+
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="state.code as state.name + state.code for state in states"></div>');
+      var inputEl = findInput(element);
+
+      expect(inputEl.val()).toEqual('AL');
+    });
+
+    it('should not get open on model change', function() {
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source"></div>');
+      $scope.$apply(function () {
+        $scope.result = 'foo';
+      });
+      expect(element).toBeClosed();
+    });
+  });
+
+  describe('basic functionality', function() {
+    it('should open and close typeahead based on matches', function() {
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue"></div>');
+      var inputEl = findInput(element);
+      var ownsId = inputEl.attr('aria-owns');
+
+      expect(inputEl.attr('aria-expanded')).toBe('false');
+      expect(inputEl.attr('aria-activedescendant')).toBeUndefined();
+
+      changeInputValueTo(element, 'ba');
+      expect(element).toBeOpenWithActive(2, 0);
+      expect(findDropDown(element).attr('id')).toBe(ownsId);
+      expect(inputEl.attr('aria-expanded')).toBe('true');
+      var activeOptionId = ownsId + '-option-0';
+      expect(inputEl.attr('aria-activedescendant')).toBe(activeOptionId);
+      expect(findDropDown(element).find('li.active').attr('id')).toBe(activeOptionId);
+
+      changeInputValueTo(element, '');
+      expect(element).toBeClosed();
+      expect(inputEl.attr('aria-expanded')).toBe('false');
+      expect(inputEl.attr('aria-activedescendant')).toBeUndefined();
+    });
+
+    it('should allow expressions over multiple lines', function() {
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source \n' +
+        '| filter:$viewValue"></div>');
+      changeInputValueTo(element, 'ba');
+      expect(element).toBeOpenWithActive(2, 0);
+
+      changeInputValueTo(element, '');
+      expect(element).toBeClosed();
+    });
+
+    it('should not open typeahead if input value smaller than a defined threshold', function() {
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue" typeahead-min-length="2"></div>');
+      changeInputValueTo(element, 'b');
+      expect(element).toBeClosed();
+    });
+
+    it('should support custom model selecting function', function() {
+      $scope.updaterFn = function(selectedItem) {
+        return 'prefix' + selectedItem;
+      };
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="updaterFn(item) as item for item in source | filter:$viewValue"></div>');
+      changeInputValueTo(element, 'f');
+      triggerKeyDown(element, 13);
+      expect($scope.result).toEqual('prefixfoo');
+    });
+
+    it('should support custom label rendering function', function() {
+      $scope.formatterFn = function(sourceItem) {
+        return 'prefix' + sourceItem;
+      };
+
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item as formatterFn(item) for item in source | filter:$viewValue"></div>');
+      changeInputValueTo(element, 'fo');
+      var matchHighlight = findMatches(element).find('a').html();
+      expect(matchHighlight).toEqual('prefix<strong>fo</strong>o');
+    });
+
+    it('should by default bind view value to model even if not part of matches', function() {
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue"></div>');
+      changeInputValueTo(element, 'not in matches');
+      expect($scope.result).toEqual('not in matches');
+    });
+
+    it('should support the editable property to limit model bindings to matches only', function() {
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue" typeahead-editable="false"></div>');
+      changeInputValueTo(element, 'not in matches');
+      expect($scope.result).toEqual(undefined);
+    });
+
+    it('should set validation errors for non-editable inputs', function() {
+      var element = prepareInputEl(
+        '<div><form name="form">' +
+          '<input name="input" ng-model="result" uib-typeahead="item for item in source | filter:$viewValue" typeahead-editable="false">' +
+        '</form></div>');
+
+      changeInputValueTo(element, 'not in matches');
+      expect($scope.result).toEqual(undefined);
+      expect($scope.form.input.$error.editable).toBeTruthy();
+
+      changeInputValueTo(element, 'foo');
+      triggerKeyDown(element, 13);
+      expect($scope.result).toEqual('foo');
+      expect($scope.form.input.$error.editable).toBeFalsy();
+    });
+
+    it('should not set editable validation error for empty input', function() {
+      var element = prepareInputEl(
+        '<div><form name="form">' +
+          '<input name="input" ng-model="result" uib-typeahead="item for item in source | filter:$viewValue" typeahead-editable="false">' +
+        '</form></div>');
+
+      changeInputValueTo(element, 'not in matches');
+      expect($scope.result).toEqual(undefined);
+      expect($scope.form.input.$error.editable).toBeTruthy();
+      changeInputValueTo(element, '');
+      expect($scope.result).toEqual(null);
+      expect($scope.form.input.$error.editable).toBeFalsy();
+    });
+
+    it('should bind loading indicator expression', inject(function($timeout) {
+      $scope.isLoading = false;
+      $scope.loadMatches = function(viewValue) {
+        return $timeout(function() {
+          return [];
+        }, 1000);
+      };
+
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in loadMatches()" typeahead-loading="isLoading"></div>');
+      changeInputValueTo(element, 'foo');
+
+      expect($scope.isLoading).toBeTruthy();
+      $timeout.flush();
+      expect($scope.isLoading).toBeFalsy();
+    }));
+
+    it('should support timeout before trying to match $viewValue', inject(function($timeout) {
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue" typeahead-wait-ms="200"></div>');
+      changeInputValueTo(element, 'foo');
+      expect(element).toBeClosed();
+
+      $timeout.flush();
+      expect(element).toBeOpenWithActive(1, 0);
+    }));
+
+    it('should cancel old timeouts when something is typed within waitTime', inject(function($timeout) {
+      var values = [];
+      $scope.loadMatches = function(viewValue) {
+        values.push(viewValue);
+        return $scope.source;
+      };
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in loadMatches($viewValue) | filter:$viewValue" typeahead-wait-ms="200"></div>');
+      changeInputValueTo(element, 'first');
+      changeInputValueTo(element, 'second');
+
+      $timeout.flush();
+
+      expect(values).not.toContain('first');
+    }));
+
+    it('should allow timeouts when something is typed after waitTime has passed', inject(function($timeout) {
+      var values = [];
+
+      $scope.loadMatches = function(viewValue) {
+        values.push(viewValue);
+        return $scope.source;
+      };
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in loadMatches($viewValue) | filter:$viewValue" typeahead-wait-ms="200"></div>');
+
+      changeInputValueTo(element, 'first');
+      $timeout.flush();
+
+      expect(values).toContain('first');
+
+      changeInputValueTo(element, 'second');
+      $timeout.flush();
+
+      expect(values).toContain('second');
+    }));
+
+    it('should support custom popup templates', function() {
+      $templateCache.put('custom.html', '<div class="custom">foo</div>');
+
+      var element = prepareInputEl('<div><input ng-model="result" typeahead-popup-template-url="custom.html" uib-typeahead="state as state.name for state in states | filter:$viewValue"></div>');
+
+      changeInputValueTo(element, 'Al');
+
+      expect(element.find('.custom').text()).toBe('foo');
+    });
+
+    it('should support custom templates for matched items', function() {
+      $templateCache.put('custom.html', '<p>{{ index }} {{ match.label }}</p>');
+
+      var element = prepareInputEl('<div><input ng-model="result" typeahead-template-url="custom.html" uib-typeahead="state as state.name for state in states | filter:$viewValue"></div>');
+
+      changeInputValueTo(element, 'Al');
+
+      expect(findMatches(element).eq(0).find('p').text()).toEqual('0 Alaska');
+    });
+
+    it('should support directives which require controllers in custom templates for matched items', function() {
+      $templateCache.put('custom.html', '<p child-directive>{{ index }} {{ match.label }}</p>');
+
+      var element = prepareInputEl('<div><input ng-model="result" typeahead-template-url="custom.html" uib-typeahead="state as state.name for state in states | filter:$viewValue"></div>');
+
+      element.data('$parentDirectiveController', {});
+
+      changeInputValueTo(element, 'Al');
+
+      expect(findMatches(element).eq(0).find('p').text()).toEqual('0 Alaska');
+    });
+
+    it('should throw error on invalid expression', function() {
+      var prepareInvalidDir = function() {
+        prepareInputEl('<div><input ng-model="result" uib-typeahead="an invalid expression"></div>');
+      };
+      expect(prepareInvalidDir).toThrow();
+    });
+  });
+
+  describe('selecting a match', function() {
+    it('should select a match on enter', function() {
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue"></div>');
+      var inputEl = findInput(element);
+
+      changeInputValueTo(element, 'b');
+      triggerKeyDown(element, 13);
+
+      expect($scope.result).toEqual('bar');
+      expect(inputEl.val()).toEqual('bar');
+      expect(element).toBeClosed();
+    });
+
+    it('should select a match on tab', function() {
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue"></div>');
+      var inputEl = findInput(element);
+
+      changeInputValueTo(element, 'b');
+      triggerKeyDown(element, 9);
+
+      expect($scope.result).toEqual('bar');
+      expect(inputEl.val()).toEqual('bar');
+      expect(element).toBeClosed();
+    });
+
+    it('should not select any match on blur without \'select-on-blur=true\' option', function() {
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue"></div>');
+      var inputEl = findInput(element);
+
+      changeInputValueTo(element, 'b');
+      inputEl.blur(); // input loses focus
+
+      // no change
+      expect($scope.result).toEqual('b');
+      expect(inputEl.val()).toEqual('b');
+    });
+
+    it('should select a match on blur with \'select-on-blur=true\' option', function() {
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue" typeahead-select-on-blur="true"></div>');
+      var inputEl = findInput(element);
+
+      changeInputValueTo(element, 'b');
+      inputEl.blur(); // input loses focus
+
+      // first element should be selected
+      expect($scope.result).toEqual('bar');
+      expect(inputEl.val()).toEqual('bar');
+    });
+
+    it('should select match on click', function() {
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue"></div>');
+      var inputEl = findInput(element);
+
+      changeInputValueTo(element, 'b');
+      var match = $(findMatches(element)[1]).find('a')[0];
+
+      $(match).click();
+      $scope.$digest();
+
+      expect($scope.result).toEqual('baz');
+      expect(inputEl.val()).toEqual('baz');
+      expect(element).toBeClosed();
+    });
+
+    it('should invoke select callback on select', function() {
+      $scope.onSelect = function($item, $model, $label) {
+        $scope.$item = $item;
+        $scope.$model = $model;
+        $scope.$label = $label;
+      };
+      var element = prepareInputEl('<div><input ng-model="result" typeahead-on-select="onSelect($item, $model, $label)" uib-typeahead="state.code as state.name for state in states | filter:$viewValue"></div>');
+
+      changeInputValueTo(element, 'Alas');
+      triggerKeyDown(element, 13);
+
+      expect($scope.result).toEqual('AL');
+      expect($scope.$item).toEqual($scope.states[0]);
+      expect($scope.$model).toEqual('AL');
+      expect($scope.$label).toEqual('Alaska');
+    });
+
+    it('should correctly update inputs value on mapping where label is not derived from the model', function() {
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="state.code as state.name for state in states | filter:$viewValue"></div>');
+      var inputEl = findInput(element);
+
+      changeInputValueTo(element, 'Alas');
+      triggerKeyDown(element, 13);
+
+      expect($scope.result).toEqual('AL');
+      expect(inputEl.val()).toEqual('AL');
+    });
+
+    it('should bind no results indicator as true when no matches returned', inject(function($timeout) {
+      $scope.isNoResults = false;
+      $scope.loadMatches = function(viewValue) {
+        return $timeout(function() {
+          return [];
+        }, 1000);
+      };
+
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in loadMatches()" typeahead-no-results="isNoResults"></div>');
+      changeInputValueTo(element, 'foo');
+
+      expect($scope.isNoResults).toBeFalsy();
+      $timeout.flush();
+      expect($scope.isNoResults).toBeTruthy();
+    }));
+
+    it('should bind no results indicator as false when matches are returned', inject(function($timeout) {
+      $scope.isNoResults = false;
+      $scope.loadMatches = function(viewValue) {
+        return $timeout(function() {
+          return [viewValue];
+        }, 1000);
+      };
+
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in loadMatches()" typeahead-no-results="isNoResults"></div>');
+      changeInputValueTo(element, 'foo');
+
+      expect($scope.isNoResults).toBeFalsy();
+      $timeout.flush();
+      expect($scope.isNoResults).toBeFalsy();
+    }));
+
+    it('should not focus the input if `typeahead-focus-on-select` is false', function() {
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue" typeahead-focus-on-select="false"></div>');
+      $document.find('body').append(element);
+      var inputEl = findInput(element);
+
+      changeInputValueTo(element, 'b');
+      var match = $(findMatches(element)[1]).find('a')[0];
+
+      $(match).click();
+      $scope.$digest();
+      $timeout.flush();
+
+      expect(document.activeElement).not.toBe(inputEl[0]);
+      expect($scope.result).toEqual('baz');
+    });
+  });
+
+  describe('select on exact match', function() {
+    it('should select on an exact match when set', function() {
+      $scope.onSelect = jasmine.createSpy('onSelect');
+      var element = prepareInputEl('<div><input ng-model="result" typeahead-editable="false" typeahead-on-select="onSelect()" uib-typeahead="item for item in source | filter:$viewValue" typeahead-select-on-exact="true"></div>');
+      var inputEl = findInput(element);
+
+      changeInputValueTo(element, 'bar');
+
+      expect($scope.result).toEqual('bar');
+      expect(inputEl.val()).toEqual('bar');
+      expect(element).toBeClosed();
+      expect($scope.onSelect).toHaveBeenCalled();
+    });
+
+    it('should not select on an exact match by default', function() {
+      $scope.onSelect = jasmine.createSpy('onSelect');
+      var element = prepareInputEl('<div><input ng-model="result" typeahead-editable="false" typeahead-on-select="onSelect()" uib-typeahead="item for item in source | filter:$viewValue"></div>');
+      var inputEl = findInput(element);
+
+      changeInputValueTo(element, 'bar');
+
+      expect($scope.result).toBeUndefined();
+      expect(inputEl.val()).toEqual('bar');
+      expect($scope.onSelect.calls.any()).toBe(false);
+    });
+
+    it('should not be case sensitive when select on an exact match', function() {
+      $scope.onSelect = jasmine.createSpy('onSelect');
+      var element = prepareInputEl('<div><input ng-model="result" typeahead-editable="false" typeahead-on-select="onSelect()" uib-typeahead="item for item in source | filter:$viewValue" typeahead-select-on-exact="true"></div>');
+      var inputEl = findInput(element);
+
+      changeInputValueTo(element, 'BaR');
+
+      expect($scope.result).toEqual('bar');
+      expect(inputEl.val()).toEqual('bar');
+      expect(element).toBeClosed();
+      expect($scope.onSelect).toHaveBeenCalled();
+    });
+
+    it('should not auto select when not a match with one potential result left', function() {
+      $scope.onSelect = jasmine.createSpy('onSelect');
+      var element = prepareInputEl('<div><input ng-model="result" typeahead-editable="false" typeahead-on-select="onSelect()" uib-typeahead="item for item in source | filter:$viewValue" typeahead-select-on-exact="true"></div>');
+      var inputEl = findInput(element);
+
+      changeInputValueTo(element, 'fo');
+
+      expect($scope.result).toBeUndefined();
+      expect(inputEl.val()).toEqual('fo');
+      expect($scope.onSelect.calls.any()).toBe(false);
+    });
+  });
+
+  describe('pop-up interaction', function() {
+    var element;
+
+    beforeEach(function() {
+      element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue"></div>');
+    });
+
+    it('should activate prev/next matches on up/down keys', function() {
+      changeInputValueTo(element, 'b');
+      expect(element).toBeOpenWithActive(2, 0);
+
+      // Down arrow key
+      triggerKeyDown(element, 40);
+      expect(element).toBeOpenWithActive(2, 1);
+
+      // Down arrow key goes back to first element
+      triggerKeyDown(element, 40);
+      expect(element).toBeOpenWithActive(2, 0);
+
+      // Up arrow key goes back to last element
+      triggerKeyDown(element, 38);
+      expect(element).toBeOpenWithActive(2, 1);
+
+      // Up arrow key goes back to first element
+      triggerKeyDown(element, 38);
+      expect(element).toBeOpenWithActive(2, 0);
+    });
+
+    it('should close popup on escape key', function() {
+      changeInputValueTo(element, 'b');
+      expect(element).toBeOpenWithActive(2, 0);
+
+      // Escape key
+      triggerKeyDown(element, 27);
+      expect(element).toBeClosed();
+    });
+
+    it('should highlight match on mouseenter', function() {
+      changeInputValueTo(element, 'b');
+      expect(element).toBeOpenWithActive(2, 0);
+
+      findMatches(element).eq(1).trigger('mouseenter');
+      expect(element).toBeOpenWithActive(2, 1);
+    });
+  });
+
+  describe('promises', function() {
+    var element, deferred;
+
+    beforeEach(inject(function($q) {
+      deferred = $q.defer();
+      $scope.source = function() {
+        return deferred.promise;
+      };
+      element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source()"></div>');
+    }));
+
+    it('should display matches from promise', function() {
+      changeInputValueTo(element, 'c');
+      expect(element).toBeClosed();
+
+      deferred.resolve(['good', 'stuff']);
+      $scope.$digest();
+      expect(element).toBeOpenWithActive(2, 0);
+    });
+
+    it('should not display anything when promise is rejected', function() {
+      changeInputValueTo(element, 'c');
+      expect(element).toBeClosed();
+
+      deferred.reject('fail');
+      $scope.$digest();
+      expect(element).toBeClosed();
+    });
+
+    it('PR #3178, resolves #2999 - should not return property "length" of undefined for undefined matches', function() {
+      changeInputValueTo(element, 'c');
+      expect(element).toBeClosed();
+
+      deferred.resolve();
+      $scope.$digest();
+      expect(element).toBeClosed();
+    });
+  });
+
+  describe('non-regressions tests', function() {
+
+    it('issue 231 - closes matches popup on click outside typeahead', function() {
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue"></div>');
+
+      changeInputValueTo(element, 'b');
+
+      $document.find('body').click();
+      $scope.$digest();
+
+      expect(element).toBeClosed();
+    });
+
+    it('issue 591 - initial formatting for un-selected match and complex label expression', function() {
+      var inputEl = findInput(prepareInputEl('<div><input ng-model="result" uib-typeahead="state as state.name + \' \' + state.code for state in states | filter:$viewValue"></div>'));
+      expect(inputEl.val()).toEqual('');
+    });
+
+    it('issue 786 - name of internal model should not conflict with scope model name', function() {
+      $scope.state = $scope.states[0];
+      var element = prepareInputEl('<div><input ng-model="state" uib-typeahead="state as state.name for state in states | filter:$viewValue"></div>');
+      var inputEl = findInput(element);
+
+      expect(inputEl.val()).toEqual('Alaska');
+    });
+
+    it('issue 863 - it should work correctly with input type="email"', function() {
+      $scope.emails = ['foo@host.com', 'bar@host.com'];
+      var element = prepareInputEl('<div><input type="email" ng-model="email" uib-typeahead="email for email in emails | filter:$viewValue"></div>');
+      var inputEl = findInput(element);
+
+      changeInputValueTo(element, 'bar');
+      expect(element).toBeOpenWithActive(1, 0);
+
+      triggerKeyDown(element, 13);
+
+      expect($scope.email).toEqual('bar@host.com');
+      expect(inputEl.val()).toEqual('bar@host.com');
+    });
+
+    it('issue 964 - should not show popup with matches if an element is not focused', function() {
+      $scope.items = function(viewValue) {
+        return $timeout(function() {
+          return [viewValue];
+        });
+      };
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in items($viewValue)"></div>');
+      var inputEl = findInput(element);
+
+      changeInputValueTo(element, 'match');
+      $scope.$digest();
+
+      inputEl.blur();
+      $timeout.flush();
+
+      expect(element).toBeClosed();
+    });
+
+    it('should properly update loading callback if an element is not focused', function() {
+      $scope.items = function(viewValue) {
+        return $timeout(function(){
+          return [viewValue];
+        });
+      };
+      var element = prepareInputEl('<div><input ng-model="result" typeahead-loading="isLoading" uib-typeahead="item for item in items($viewValue)"></div>');
+      var inputEl = findInput(element);
+
+      changeInputValueTo(element, 'match');
+      $scope.$digest();
+
+      inputEl.blur();
+      $timeout.flush();
+
+      expect($scope.isLoading).toBeFalsy();
+    });
+
+    it('issue 1140 - should properly update loading callback when deleting characters', function() {
+      $scope.items = function(viewValue) {
+        return $timeout(function() {
+          return [viewValue];
+        });
+      };
+      var element = prepareInputEl('<div><input ng-model="result" typeahead-min-length="2" typeahead-loading="isLoading" uib-typeahead="item for item in items($viewValue)"></div>');
+
+      changeInputValueTo(element, 'match');
+      $scope.$digest();
+
+      expect($scope.isLoading).toBeTruthy();
+
+      changeInputValueTo(element, 'm');
+      $timeout.flush();
+      $scope.$digest();
+
+      expect($scope.isLoading).toBeFalsy();
+    });
+
+    it('should cancel old timeout when deleting characters', inject(function($timeout) {
+      var values = [];
+      $scope.loadMatches = function(viewValue) {
+        values.push(viewValue);
+        return $scope.source;
+      };
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in loadMatches($viewValue) | filter:$viewValue" typeahead-min-length="2" typeahead-wait-ms="200"></div>');
+      changeInputValueTo(element, 'match');
+      changeInputValueTo(element, 'm');
+
+      $timeout.flush();
+
+      expect(values).not.toContain('match');
+    }));
+
+    describe('', function() {
+      // Dummy describe to be able to create an after hook for this tests
+      var element;
+
+      it('does not close matches popup on click in input', function() {
+        element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue"></div>');
+        var inputEl = findInput(element);
+
+        // Note that this bug can only be found when element is in the document
+        $document.find('body').append(element);
+
+        changeInputValueTo(element, 'b');
+
+        inputEl.click();
+        $scope.$digest();
+
+        expect(element).toBeOpenWithActive(2, 0);
+      });
+
+      it('issue #1773 - should not trigger an error when used with ng-focus', function() {
+        element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue" ng-focus="foo()"></div>');
+        var inputEl = findInput(element);
+
+        // Note that this bug can only be found when element is in the document
+        $document.find('body').append(element);
+
+        changeInputValueTo(element, 'b');
+        var match = $(findMatches(element)[1]).find('a')[0];
+
+        $(match).click();
+        $scope.$digest();
+      });
+
+      afterEach(function() {
+        element.remove();
+      });
+    });
+
+    it('issue #1238 - allow names like "query" to be used inside "in" expressions ', function() {
+      $scope.query = function() {
+        return ['foo', 'bar'];
+      };
+
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in query($viewValue)"></div>');
+      changeInputValueTo(element, 'bar');
+
+      expect(element).toBeOpenWithActive(2, 0);
+    });
+
+    it('issue #3318 - should set model validity to true when set manually', function() {
+      var element = prepareInputEl(
+        '<div><form name="form">' +
+          '<input name="input" ng-model="result" uib-typeahead="item for item in source | filter:$viewValue" typeahead-editable="false">' +
+        '</form></div>');
+
+      changeInputValueTo(element, 'not in matches');
+      $scope.$apply(function() {
+        $scope.result = 'manually set';
+      });
+
+      expect($scope.result).toEqual('manually set');
+      expect($scope.form.input.$valid).toBeTruthy();
+    });
+
+    it('issue #3166 - should set \'parse\' key as valid when selecting a perfect match and not editable', function() {
+      var element = prepareInputEl('<div ng-form="test"><input name="typeahead" ng-model="result" uib-typeahead="state as state.name for state in states | filter:$viewValue" typeahead-editable="false"></div>');
+      var inputEl = findInput(element);
+
+      changeInputValueTo(element, 'Alaska');
+      triggerKeyDown(element, 13);
+
+      expect($scope.test.typeahead.$error.parse).toBeUndefined();
+    });
+
+    it('issue #3823 - should support ng-model-options getterSetter', function() {
+      function resultSetter(state) {
+        return state;
+      }
+      $scope.result = resultSetter;
+      var element = prepareInputEl('<div><input name="typeahead" ng-model="result" ng-model-options="{getterSetter: true}" uib-typeahead="state as state.name for state in states | filter:$viewValue" typeahead-editable="false"></div>');
+
+      changeInputValueTo(element, 'Alaska');
+      triggerKeyDown(element, 13);
+
+      expect($scope.result).toBe(resultSetter);
+    });
+  });
+
+  describe('input formatting', function() {
+    it('should co-operate with existing formatters', function() {
+      $scope.result = $scope.states[0];
+
+      var element = prepareInputEl('<div><input ng-model="result.name" formatter uib-typeahead="state.name for state in states | filter:$viewValue"></div>'),
+      inputEl = findInput(element);
+
+      expect(inputEl.val()).toEqual('formatted' + $scope.result.name);
+    });
+
+    it('should support a custom input formatting function', function() {
+      $scope.result = $scope.states[0];
+      $scope.formatInput = function($model) {
+        return $model.code;
+      };
+
+      var element = prepareInputEl('<div><input ng-model="result" typeahead-input-formatter="formatInput($model)" uib-typeahead="state as state.name for state in states | filter:$viewValue"></div>'),
+      inputEl = findInput(element);
+
+      expect(inputEl.val()).toEqual('AL');
+      expect($scope.result).toEqual($scope.states[0]);
+    });
+  });
+
+  describe('append to element id', function() {
+    it('append typeahead results to element', function() {
+      $document.find('body').append('<div id="myElement"></div>');
+      var element = prepareInputEl('<div><input name="input" ng-model="result" uib-typeahead="item for item in states | filter:$viewValue" typeahead-append-to-element-id="myElement"></div>');
+      changeInputValueTo(element, 'al');
+      expect($document.find('#myElement')).toBeOpenWithActive(2, 0);
+      $document.find('#myElement').remove();
+    });
+  });
+
+  describe('append to body', function() {
+    afterEach(function() {
+      angular.element($window).off('resize');
+      $document.find('body').off('scroll');
+    });
+
+    it('append typeahead results to body', function() {
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue" typeahead-append-to-body="true"></div>');
+      changeInputValueTo(element, 'ba');
+      expect($document.find('body')).toBeOpenWithActive(2, 0);
+    });
+
+    it('should not append to body when value of the attribute is false', function() {
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue" typeahead-append-to-body="false"></div>');
+      changeInputValueTo(element, 'ba');
+      expect(findDropDown($document.find('body')).length).toEqual(0);
+    });
+
+    it('should have right position after scroll', function() {
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue" typeahead-append-to-body="true"></div>');
+      var dropdown = findDropDown($document.find('body'));
+      var body = angular.element(document.body);
+
+      // Set body height to allow scrolling
+      body.css({height:'10000px'});
+
+      // Scroll top
+      window.scroll(0, 1000);
+
+      // Set input value to show dropdown
+      changeInputValueTo(element, 'ba');
+
+      // Init position of dropdown must be 1000px
+      expect(dropdown.css('top') ).toEqual('1000px');
+
+      // After scroll, must have new position
+      window.scroll(0, 500);
+      body.triggerHandler('scroll');
+      $timeout.flush();
+      expect(dropdown.css('top')).toEqual('500px');
+    });
+  });
+
+  describe('focus first', function() {
+    it('should focus the first element by default', function() {
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue"></div>');
+      changeInputValueTo(element, 'b');
+      expect(element).toBeOpenWithActive(2, 0);
+
+      // Down arrow key
+      triggerKeyDown(element, 40);
+      expect(element).toBeOpenWithActive(2, 1);
+
+      // Down arrow key goes back to first element
+      triggerKeyDown(element, 40);
+      expect(element).toBeOpenWithActive(2, 0);
+
+      // Up arrow key goes back to last element
+      triggerKeyDown(element, 38);
+      expect(element).toBeOpenWithActive(2, 1);
+
+      // Up arrow key goes back to first element
+      triggerKeyDown(element, 38);
+      expect(element).toBeOpenWithActive(2, 0);
+    });
+
+    it('should not focus the first element until keys are pressed', function() {
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue" typeahead-focus-first="false"></div>');
+      changeInputValueTo(element, 'b');
+      expect(element).toBeOpenWithActive(2, -1);
+
+      // Down arrow key goes to first element
+      triggerKeyDown(element, 40);
+      expect(element).toBeOpenWithActive(2, 0);
+
+      // Down arrow key goes to second element
+      triggerKeyDown(element, 40);
+      expect(element).toBeOpenWithActive(2, 1);
+
+      // Down arrow key goes back to first element
+      triggerKeyDown(element, 40);
+      expect(element).toBeOpenWithActive(2, 0);
+
+      // Up arrow key goes back to last element
+      triggerKeyDown(element, 38);
+      expect(element).toBeOpenWithActive(2, 1);
+
+      // Up arrow key goes back to first element
+      triggerKeyDown(element, 38);
+      expect(element).toBeOpenWithActive(2, 0);
+
+      // New input goes back to no focus
+      changeInputValueTo(element, 'a');
+      changeInputValueTo(element, 'b');
+      expect(element).toBeOpenWithActive(2, -1);
+
+      // Up arrow key goes to last element
+      triggerKeyDown(element, 38);
+      expect(element).toBeOpenWithActive(2, 1);
+    });
+  });
+
+  it('should not capture enter or tab when an item is not focused', function() {
+    $scope.select_count = 0;
+    $scope.onSelect = function($item, $model, $label) {
+      $scope.select_count = $scope.select_count + 1;
+    };
+    var element = prepareInputEl('<div><input ng-model="result" ng-keydown="keyDownEvent = $event" uib-typeahead="item for item in source | filter:$viewValue" typeahead-on-select="onSelect($item, $model, $label)" typeahead-focus-first="false"></div>');
+    changeInputValueTo(element, 'b');
+
+    // enter key should not be captured when nothing is focused
+    triggerKeyDown(element, 13);
+    expect($scope.keyDownEvent.isDefaultPrevented()).toBeFalsy();
+    expect($scope.select_count).toEqual(0);
+
+    // tab key should close the dropdown when nothing is focused
+    triggerKeyDown(element, 9);
+    expect($scope.keyDownEvent.isDefaultPrevented()).toBeFalsy();
+    expect($scope.select_count).toEqual(0);
+    expect(element).toBeClosed();
+  });
+
+  it('should capture enter or tab when an item is focused', function() {
+    $scope.select_count = 0;
+    $scope.onSelect = function($item, $model, $label) {
+      $scope.select_count = $scope.select_count + 1;
+    };
+    var element = prepareInputEl('<div><input ng-model="result" ng-keydown="keyDownEvent = $event" uib-typeahead="item for item in source | filter:$viewValue" typeahead-on-select="onSelect($item, $model, $label)" typeahead-focus-first="false"></div>');
+    changeInputValueTo(element, 'b');
+
+    // down key should be captured and focus first element
+    triggerKeyDown(element, 40);
+    expect($scope.keyDownEvent.isDefaultPrevented()).toBeTruthy();
+    expect(element).toBeOpenWithActive(2, 0);
+
+    // enter key should be captured now that something is focused
+    triggerKeyDown(element, 13);
+    expect($scope.keyDownEvent.isDefaultPrevented()).toBeTruthy();
+    expect($scope.select_count).toEqual(1);
+  });
+
+  describe('minLength set to 0', function() {
+    it('should open typeahead if input is changed to empty string if defined threshold is 0', function() {
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue" typeahead-min-length="0"></div>');
+      changeInputValueTo(element, '');
+      expect(element).toBeOpenWithActive(3, 0);
+    });
+  });
+
+  describe('event listeners', function() {
+    afterEach(function() {
+      angular.element($window).off('resize');
+      $document.find('body').off('scroll');
+    });
+
+    it('should register event listeners when attached to body', function() {
+      spyOn(window, 'addEventListener');
+      spyOn(document.body, 'addEventListener');
+
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue" typeahead-append-to-body="true"></div>');
+
+      expect(window.addEventListener).toHaveBeenCalledWith('resize', jasmine.any(Function), false);
+      expect(document.body.addEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function), false);
+    });
+
+    it('should remove event listeners when attached to body', function() {
+      spyOn(window, 'removeEventListener');
+      spyOn(document.body, 'removeEventListener');
+
+      var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue" typeahead-append-to-body="true"></div>');
+      $scope.$destroy();
+
+      expect(window.removeEventListener).toHaveBeenCalledWith('resize', jasmine.any(Function), false);
+      expect(document.body.removeEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function), false);
+    });
+  });
+});
+
+/* Deprecation tests below */
+
+describe('typeahead deprecation', function() {
+  beforeEach(module('ui.bootstrap.typeahead'));
+  beforeEach(module('ngSanitize'));
+  beforeEach(module('template/typeahead/typeahead-popup.html'));
+  beforeEach(module('template/typeahead/typeahead-match.html'));
+
+  it('should suppress warning', function() {
+    module(function($provide) {
+      $provide.value('$typeaheadSuppressWarning', true);
+    });
+
+    inject(function($compile, $log, $rootScope) {
+      spyOn($log, 'warn');
+
+      var element = '<div><input ng-model="result" typeahead="item for item in source | filter:$viewValue" typeahead-min-length="0"></div>';
+      element = $compile(element)($rootScope);
+      $rootScope.$digest();
+      expect($log.warn.calls.count()).toBe(0);
+    });
+  });
+
+  it('should give warning by default', inject(function($compile, $log, $rootScope) {
+    spyOn($log, 'warn');
+
+    var element = '<div><input ng-model="result" typeahead="item for item in source | filter:$viewValue" typeahead-min-length="0"></div>';
+    element = $compile(element)($rootScope);
+    $rootScope.$digest();
+
+    expect($log.warn.calls.count()).toBe(3);
+    expect($log.warn.calls.argsFor(0)).toEqual(['typeaheadParser is now deprecated. Use uibTypeaheadParser instead.']);
+    expect($log.warn.calls.argsFor(1)).toEqual(['typeahead is now deprecated. Use uib-typeahead instead.']);
+    expect($log.warn.calls.argsFor(2)).toEqual(['typeahead-popup is now deprecated. Use uib-typeahead-popup instead.']);
+  }));
+
+  it('should deprecate typeaheadMatch', inject(function($compile, $log, $rootScope, $templateCache, $sniffer){
+    spyOn($log, 'warn');
+
+    var element = '<div typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></div>';
+    element = $compile(element)($rootScope);
+    $rootScope.$digest();
+
+    expect($log.warn.calls.count()).toBe(1);
+    expect($log.warn.calls.argsFor(0)).toEqual(['typeahead-match is now deprecated. Use uib-typeahead-match instead.']);
+  }));
+});
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/typeahead.js b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/typeahead.js
new file mode 100644
index 0000000..aa88842
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/src/typeahead/typeahead.js
@@ -0,0 +1,1060 @@
+angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
+
+/**
+ * A helper service that can parse typeahead's syntax (string provided by users)
+ * Extracted to a separate service for ease of unit testing
+ */
+  .factory('uibTypeaheadParser', ['$parse', function($parse) {
+    //                      00000111000000000000022200000000000000003333333333333330000000000044000
+    var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;
+    return {
+      parse: function(input) {
+        var match = input.match(TYPEAHEAD_REGEXP);
+        if (!match) {
+          throw new Error(
+            'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
+              ' but got "' + input + '".');
+        }
+
+        return {
+          itemName: match[3],
+          source: $parse(match[4]),
+          viewMapper: $parse(match[2] || match[1]),
+          modelMapper: $parse(match[1])
+        };
+      }
+    };
+  }])
+
+  .controller('UibTypeaheadController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$uibPosition', 'uibTypeaheadParser',
+    function(originalScope, element, attrs, $compile, $parse, $q, $timeout, $document, $window, $rootScope, $position, typeaheadParser) {
+    var HOT_KEYS = [9, 13, 27, 38, 40];
+    var eventDebounceTime = 200;
+    var modelCtrl, ngModelOptions;
+    //SUPPORTED ATTRIBUTES (OPTIONS)
+
+    //minimal no of characters that needs to be entered before typeahead kicks-in
+    var minLength = originalScope.$eval(attrs.typeaheadMinLength);
+    if (!minLength && minLength !== 0) {
+      minLength = 1;
+    }
+
+    //minimal wait time after last character typed before typeahead kicks-in
+    var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
+
+    //should it restrict model values to the ones selected from the popup only?
+    var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
+
+    //binding to a variable that indicates if matches are being retrieved asynchronously
+    var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
+
+    //a callback executed when a match is selected
+    var onSelectCallback = $parse(attrs.typeaheadOnSelect);
+
+    //should it select highlighted popup value when losing focus?
+    var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false;
+
+    //binding to a variable that indicates if there were no results after the query is completed
+    var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop;
+
+    var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
+
+    var appendToBody =  attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;
+
+    var appendToElementId =  attrs.typeaheadAppendToElementId || false;
+
+    var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;
+
+    //If input matches an item of the list exactly, select it automatically
+    var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false;
+
+    //INTERNAL VARIABLES
+
+    //model setter executed upon match selection
+    var parsedModel = $parse(attrs.ngModel);
+    var invokeModelSetter = $parse(attrs.ngModel + '($$$p)');
+    var $setModelValue = function(scope, newValue) {
+      if (angular.isFunction(parsedModel(originalScope)) &&
+        ngModelOptions && ngModelOptions.$options && ngModelOptions.$options.getterSetter) {
+        return invokeModelSetter(scope, {$$$p: newValue});
+      } else {
+        return parsedModel.assign(scope, newValue);
+      }
+    };
+
+    //expressions used by typeahead
+    var parserResult = typeaheadParser.parse(attrs.uibTypeahead);
+
+    var hasFocus;
+
+    //Used to avoid bug in iOS webview where iOS keyboard does not fire
+    //mousedown & mouseup events
+    //Issue #3699
+    var selected;
+
+    //create a child scope for the typeahead directive so we are not polluting original scope
+    //with typeahead-specific data (matches, query etc.)
+    var scope = originalScope.$new();
+    var offDestroy = originalScope.$on('$destroy', function() {
+      scope.$destroy();
+    });
+    scope.$on('$destroy', offDestroy);
+
+    // WAI-ARIA
+    var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
+    element.attr({
+      'aria-autocomplete': 'list',
+      'aria-expanded': false,
+      'aria-owns': popupId
+    });
+
+    //pop-up element used to display matches
+    var popUpEl = angular.element('<div uib-typeahead-popup></div>');
+    popUpEl.attr({
+      id: popupId,
+      matches: 'matches',
+      active: 'activeIdx',
+      select: 'select(activeIdx)',
+      'move-in-progress': 'moveInProgress',
+      query: 'query',
+      position: 'position'
+    });
+    //custom item template
+    if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
+      popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
+    }
+
+    if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) {
+      popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl);
+    }
+
+    var resetMatches = function() {
+      scope.matches = [];
+      scope.activeIdx = -1;
+      element.attr('aria-expanded', false);
+    };
+
+    var getMatchId = function(index) {
+      return popupId + '-option-' + index;
+    };
+
+    // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.
+    // This attribute is added or removed automatically when the `activeIdx` changes.
+    scope.$watch('activeIdx', function(index) {
+      if (index < 0) {
+        element.removeAttr('aria-activedescendant');
+      } else {
+        element.attr('aria-activedescendant', getMatchId(index));
+      }
+    });
+
+    var inputIsExactMatch = function(inputValue, index) {
+      if (scope.matches.length > index && inputValue) {
+        return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase();
+      }
+
+      return false;
+    };
+
+    var getMatchesAsync = function(inputValue) {
+      var locals = {$viewValue: inputValue};
+      isLoadingSetter(originalScope, true);
+      isNoResultsSetter(originalScope, false);
+      $q.when(parserResult.source(originalScope, locals)).then(function(matches) {
+        //it might happen that several async queries were in progress if a user were typing fast
+        //but we are interested only in responses that correspond to the current view value
+        var onCurrentRequest = (inputValue === modelCtrl.$viewValue);
+        if (onCurrentRequest && hasFocus) {
+          if (matches && matches.length > 0) {
+            scope.activeIdx = focusFirst ? 0 : -1;
+            isNoResultsSetter(originalScope, false);
+            scope.matches.length = 0;
+
+            //transform labels
+            for (var i = 0; i < matches.length; i++) {
+              locals[parserResult.itemName] = matches[i];
+              scope.matches.push({
+                id: getMatchId(i),
+                label: parserResult.viewMapper(scope, locals),
+                model: matches[i]
+              });
+            }
+
+            scope.query = inputValue;
+            //position pop-up with matches - we need to re-calculate its position each time we are opening a window
+            //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
+            //due to other elements being rendered
+            recalculatePosition();
+
+            element.attr('aria-expanded', true);
+
+            //Select the single remaining option if user input matches
+            if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) {
+              scope.select(0);
+            }
+          } else {
+            resetMatches();
+            isNoResultsSetter(originalScope, true);
+          }
+        }
+        if (onCurrentRequest) {
+          isLoadingSetter(originalScope, false);
+        }
+      }, function() {
+        resetMatches();
+        isLoadingSetter(originalScope, false);
+        isNoResultsSetter(originalScope, true);
+      });
+    };
+
+    // bind events only if appendToBody params exist - performance feature
+    if (appendToBody) {
+      angular.element($window).bind('resize', fireRecalculating);
+      $document.find('body').bind('scroll', fireRecalculating);
+    }
+
+    // Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
+    var timeoutEventPromise;
+
+    // Default progress type
+    scope.moveInProgress = false;
+
+    function fireRecalculating() {
+      if (!scope.moveInProgress) {
+        scope.moveInProgress = true;
+        scope.$digest();
+      }
+
+      // Cancel previous timeout
+      if (timeoutEventPromise) {
+        $timeout.cancel(timeoutEventPromise);
+      }
+
+      // Debounced executing recalculate after events fired
+      timeoutEventPromise = $timeout(function() {
+        // if popup is visible
+        if (scope.matches.length) {
+          recalculatePosition();
+        }
+
+        scope.moveInProgress = false;
+      }, eventDebounceTime);
+    }
+
+    // recalculate actual position and set new values to scope
+    // after digest loop is popup in right position
+    function recalculatePosition() {
+      scope.position = appendToBody ? $position.offset(element) : $position.position(element);
+      scope.position.top += element.prop('offsetHeight');
+    }
+
+    //we need to propagate user's query so we can higlight matches
+    scope.query = undefined;
+
+    //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
+    var timeoutPromise;
+
+    var scheduleSearchWithTimeout = function(inputValue) {
+      timeoutPromise = $timeout(function() {
+        getMatchesAsync(inputValue);
+      }, waitTime);
+    };
+
+    var cancelPreviousTimeout = function() {
+      if (timeoutPromise) {
+        $timeout.cancel(timeoutPromise);
+      }
+    };
+
+    resetMatches();
+
+    scope.select = function(activeIdx) {
+      //called from within the $digest() cycle
+      var locals = {};
+      var model, item;
+
+      selected = true;
+      locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
+      model = parserResult.modelMapper(originalScope, locals);
+      $setModelValue(originalScope, model);
+      modelCtrl.$setValidity('editable', true);
+      modelCtrl.$setValidity('parse', true);
+
+      onSelectCallback(originalScope, {
+        $item: item,
+        $model: model,
+        $label: parserResult.viewMapper(originalScope, locals)
+      });
+
+      resetMatches();
+
+      //return focus to the input element if a match was selected via a mouse click event
+      // use timeout to avoid $rootScope:inprog error
+      if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) {
+        $timeout(function() { element[0].focus(); }, 0, false);
+      }
+    };
+
+    //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
+    element.bind('keydown', function(evt) {
+      //typeahead is open and an "interesting" key was pressed
+      if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
+        return;
+      }
+
+      // if there's nothing selected (i.e. focusFirst) and enter or tab is hit, clear the results
+      if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13)) {
+        resetMatches();
+        scope.$digest();
+        return;
+      }
+
+      evt.preventDefault();
+
+      if (evt.which === 40) {
+        scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
+        scope.$digest();
+      } else if (evt.which === 38) {
+        scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
+        scope.$digest();
+      } else if (evt.which === 13 || evt.which === 9) {
+        scope.$apply(function () {
+          scope.select(scope.activeIdx);
+        });
+      } else if (evt.which === 27) {
+        evt.stopPropagation();
+
+        resetMatches();
+        scope.$digest();
+      }
+    });
+
+    element.bind('blur', function() {
+      if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) {
+        selected = true;
+        scope.$apply(function() {
+          scope.select(scope.activeIdx);
+        });
+      }
+      hasFocus = false;
+      selected = false;
+    });
+
+    // Keep reference to click handler to unbind it.
+    var dismissClickHandler = function(evt) {
+      // Issue #3973
+      // Firefox treats right click as a click on document
+      if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) {
+        resetMatches();
+        if (!$rootScope.$$phase) {
+          scope.$digest();
+        }
+      }
+    };
+
+    $document.bind('click', dismissClickHandler);
+
+    originalScope.$on('$destroy', function() {
+      $document.unbind('click', dismissClickHandler);
+      if (appendToBody || appendToElementId) {
+        $popup.remove();
+      }
+
+      if (appendToBody) {
+        angular.element($window).unbind('resize', fireRecalculating);
+        $document.find('body').unbind('scroll', fireRecalculating);
+      }
+      // Prevent jQuery cache memory leak
+      popUpEl.remove();
+    });
+
+    var $popup = $compile(popUpEl)(scope);
+
+    if (appendToBody) {
+      $document.find('body').append($popup);
+    } else if (appendToElementId !== false) {
+      angular.element($document[0].getElementById(appendToElementId)).append($popup);
+    } else {
+      element.after($popup);
+    }
+
+    this.init = function(_modelCtrl, _ngModelOptions) {
+      modelCtrl = _modelCtrl;
+      ngModelOptions = _ngModelOptions;
+
+      //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
+      //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
+      modelCtrl.$parsers.unshift(function(inputValue) {
+        hasFocus = true;
+
+        if (minLength === 0 || inputValue && inputValue.length >= minLength) {
+          if (waitTime > 0) {
+            cancelPreviousTimeout();
+            scheduleSearchWithTimeout(inputValue);
+          } else {
+            getMatchesAsync(inputValue);
+          }
+        } else {
+          isLoadingSetter(originalScope, false);
+          cancelPreviousTimeout();
+          resetMatches();
+        }
+
+        if (isEditable) {
+          return inputValue;
+        } else {
+          if (!inputValue) {
+            // Reset in case user had typed something previously.
+            modelCtrl.$setValidity('editable', true);
+            return null;
+          } else {
+            modelCtrl.$setValidity('editable', false);
+            return undefined;
+          }
+        }
+      });
+
+      modelCtrl.$formatters.push(function(modelValue) {
+        var candidateViewValue, emptyViewValue;
+        var locals = {};
+
+        // The validity may be set to false via $parsers (see above) if
+        // the model is restricted to selected values. If the model
+        // is set manually it is considered to be valid.
+        if (!isEditable) {
+          modelCtrl.$setValidity('editable', true);
+        }
+
+        if (inputFormatter) {
+          locals.$model = modelValue;
+          return inputFormatter(originalScope, locals);
+        } else {
+          //it might happen that we don't have enough info to properly render input value
+          //we need to check for this situation and simply return model value if we can't apply custom formatting
+          locals[parserResult.itemName] = modelValue;
+          candidateViewValue = parserResult.viewMapper(originalScope, locals);
+          locals[parserResult.itemName] = undefined;
+          emptyViewValue = parserResult.viewMapper(originalScope, locals);
+
+          return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue;
+        }
+      });
+    };
+  }])
+
+  .directive('uibTypeahead', function() {
+    return {
+      controller: 'UibTypeaheadController',
+      require: ['ngModel', '^?ngModelOptions', 'uibTypeahead'],
+      link: function(originalScope, element, attrs, ctrls) {
+        ctrls[2].init(ctrls[0], ctrls[1]);
+      }
+    };
+  })
+
+  .directive('uibTypeaheadPopup', function() {
+    return {
+      scope: {
+        matches: '=',
+        query: '=',
+        active: '=',
+        position: '&',
+        moveInProgress: '=',
+        select: '&'
+      },
+      replace: true,
+      templateUrl: function(element, attrs) {
+        return attrs.popupTemplateUrl || 'template/typeahead/typeahead-popup.html';
+      },
+      link: function(scope, element, attrs) {
+        scope.templateUrl = attrs.templateUrl;
+
+        scope.isOpen = function() {
+          return scope.matches.length > 0;
+        };
+
+        scope.isActive = function(matchIdx) {
+          return scope.active == matchIdx;
+        };
+
+        scope.selectActive = function(matchIdx) {
+          scope.active = matchIdx;
+        };
+
+        scope.selectMatch = function(activeIdx) {
+          scope.select({activeIdx:activeIdx});
+        };
+      }
+    };
+  })
+
+  .directive('uibTypeaheadMatch', ['$templateRequest', '$compile', '$parse', function($templateRequest, $compile, $parse) {
+    return {
+      scope: {
+        index: '=',
+        match: '=',
+        query: '='
+      },
+      link:function(scope, element, attrs) {
+        var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html';
+        $templateRequest(tplUrl).then(function(tplContent) {
+          $compile(tplContent.trim())(scope, function(clonedElement) {
+            element.replaceWith(clonedElement);
+          });
+        });
+      }
+    };
+  }])
+
+  .filter('uibTypeaheadHighlight', ['$sce', '$injector', '$log', function($sce, $injector, $log) {
+    var isSanitizePresent;
+    isSanitizePresent = $injector.has('$sanitize');
+
+    function escapeRegexp(queryToEscape) {
+      // Regex: capture the whole query string and replace it with the string that will be used to match
+      // the results, for example if the capture is "a" the result will be \a
+      return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
+    }
+
+    function containsHtml(matchItem) {
+      return /<.*>/g.test(matchItem);
+    }
+
+    return function(matchItem, query) {
+      if (!isSanitizePresent && containsHtml(matchItem)) {
+        $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger
+      }
+      matchItem = query? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem; // Replaces the capture string with a the same string inside of a "strong" tag
+      if (!isSanitizePresent) {
+        matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive
+      }
+      return matchItem;
+    };
+  }]);
+
+/* Deprecated typeahead below */
+  
+angular.module('ui.bootstrap.typeahead')
+  .value('$typeaheadSuppressWarning', false)
+  .service('typeaheadParser', ['$parse', 'uibTypeaheadParser', '$log', '$typeaheadSuppressWarning', function($parse, uibTypeaheadParser, $log, $typeaheadSuppressWarning) {
+    if (!$typeaheadSuppressWarning) {
+      $log.warn('typeaheadParser is now deprecated. Use uibTypeaheadParser instead.');
+    }
+
+    return uibTypeaheadParser;
+  }])
+
+  .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$uibPosition', 'typeaheadParser', '$log', '$typeaheadSuppressWarning',
+    function($compile, $parse, $q, $timeout, $document, $window, $rootScope, $position, typeaheadParser, $log, $typeaheadSuppressWarning) {
+    var HOT_KEYS = [9, 13, 27, 38, 40];
+    var eventDebounceTime = 200;
+    return {
+      require: ['ngModel', '^?ngModelOptions'],
+      link: function(originalScope, element, attrs, ctrls) {
+        if (!$typeaheadSuppressWarning) {
+          $log.warn('typeahead is now deprecated. Use uib-typeahead instead.');
+        }
+        var modelCtrl = ctrls[0];
+        var ngModelOptions = ctrls[1];
+        //SUPPORTED ATTRIBUTES (OPTIONS)
+
+        //minimal no of characters that needs to be entered before typeahead kicks-in
+        var minLength = originalScope.$eval(attrs.typeaheadMinLength);
+        if (!minLength && minLength !== 0) {
+          minLength = 1;
+        }
+
+        //minimal wait time after last character typed before typeahead kicks-in
+        var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
+
+        //should it restrict model values to the ones selected from the popup only?
+        var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
+
+        //binding to a variable that indicates if matches are being retrieved asynchronously
+        var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
+
+        //a callback executed when a match is selected
+        var onSelectCallback = $parse(attrs.typeaheadOnSelect);
+
+        //should it select highlighted popup value when losing focus?
+        var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false;
+
+        //binding to a variable that indicates if there were no results after the query is completed
+        var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop;
+
+        var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
+
+        var appendToBody =  attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;
+
+        var appendToElementId =  attrs.typeaheadAppendToElementId || false;
+
+        var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;
+
+        //If input matches an item of the list exactly, select it automatically
+        var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false;
+
+        //INTERNAL VARIABLES
+
+        //model setter executed upon match selection
+        var parsedModel = $parse(attrs.ngModel);
+        var invokeModelSetter = $parse(attrs.ngModel + '($$$p)');
+        var $setModelValue = function(scope, newValue) {
+          if (angular.isFunction(parsedModel(originalScope)) &&
+            ngModelOptions && ngModelOptions.$options && ngModelOptions.$options.getterSetter) {
+            return invokeModelSetter(scope, {$$$p: newValue});
+          } else {
+            return parsedModel.assign(scope, newValue);
+          }
+        };
+
+        //expressions used by typeahead
+        var parserResult = typeaheadParser.parse(attrs.typeahead);
+
+        var hasFocus;
+
+        //Used to avoid bug in iOS webview where iOS keyboard does not fire
+        //mousedown & mouseup events
+        //Issue #3699
+        var selected;
+
+        //create a child scope for the typeahead directive so we are not polluting original scope
+        //with typeahead-specific data (matches, query etc.)
+        var scope = originalScope.$new();
+        var offDestroy = originalScope.$on('$destroy', function() {
+			    scope.$destroy();
+        });
+        scope.$on('$destroy', offDestroy);
+
+        // WAI-ARIA
+        var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
+        element.attr({
+          'aria-autocomplete': 'list',
+          'aria-expanded': false,
+          'aria-owns': popupId
+        });
+
+        //pop-up element used to display matches
+        var popUpEl = angular.element('<div typeahead-popup></div>');
+        popUpEl.attr({
+          id: popupId,
+          matches: 'matches',
+          active: 'activeIdx',
+          select: 'select(activeIdx)',
+          'move-in-progress': 'moveInProgress',
+          query: 'query',
+          position: 'position'
+        });
+        //custom item template
+        if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
+          popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
+        }
+
+        if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) {
+          popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl);
+        }
+
+        var resetMatches = function() {
+          scope.matches = [];
+          scope.activeIdx = -1;
+          element.attr('aria-expanded', false);
+        };
+
+        var getMatchId = function(index) {
+          return popupId + '-option-' + index;
+        };
+
+        // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.
+        // This attribute is added or removed automatically when the `activeIdx` changes.
+        scope.$watch('activeIdx', function(index) {
+          if (index < 0) {
+            element.removeAttr('aria-activedescendant');
+          } else {
+            element.attr('aria-activedescendant', getMatchId(index));
+          }
+        });
+
+        var inputIsExactMatch = function(inputValue, index) {
+          if (scope.matches.length > index && inputValue) {
+            return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase();
+          }
+
+          return false;
+        };
+
+        var getMatchesAsync = function(inputValue) {
+          var locals = {$viewValue: inputValue};
+          isLoadingSetter(originalScope, true);
+          isNoResultsSetter(originalScope, false);
+          $q.when(parserResult.source(originalScope, locals)).then(function(matches) {
+            //it might happen that several async queries were in progress if a user were typing fast
+            //but we are interested only in responses that correspond to the current view value
+            var onCurrentRequest = (inputValue === modelCtrl.$viewValue);
+            if (onCurrentRequest && hasFocus) {
+              if (matches && matches.length > 0) {
+                scope.activeIdx = focusFirst ? 0 : -1;
+                isNoResultsSetter(originalScope, false);
+                scope.matches.length = 0;
+
+                //transform labels
+                for (var i = 0; i < matches.length; i++) {
+                  locals[parserResult.itemName] = matches[i];
+                  scope.matches.push({
+                    id: getMatchId(i),
+                    label: parserResult.viewMapper(scope, locals),
+                    model: matches[i]
+                  });
+                }
+
+                scope.query = inputValue;
+                //position pop-up with matches - we need to re-calculate its position each time we are opening a window
+                //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
+                //due to other elements being rendered
+                recalculatePosition();
+
+                element.attr('aria-expanded', true);
+
+                //Select the single remaining option if user input matches
+                if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) {
+                  scope.select(0);
+                }
+              } else {
+                resetMatches();
+                isNoResultsSetter(originalScope, true);
+              }
+            }
+            if (onCurrentRequest) {
+              isLoadingSetter(originalScope, false);
+            }
+          }, function() {
+            resetMatches();
+            isLoadingSetter(originalScope, false);
+            isNoResultsSetter(originalScope, true);
+          });
+        };
+
+        // bind events only if appendToBody params exist - performance feature
+        if (appendToBody) {
+          angular.element($window).bind('resize', fireRecalculating);
+          $document.find('body').bind('scroll', fireRecalculating);
+        }
+
+        // Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
+        var timeoutEventPromise;
+
+        // Default progress type
+        scope.moveInProgress = false;
+
+        function fireRecalculating() {
+          if (!scope.moveInProgress) {
+            scope.moveInProgress = true;
+            scope.$digest();
+          }
+
+          // Cancel previous timeout
+          if (timeoutEventPromise) {
+            $timeout.cancel(timeoutEventPromise);
+          }
+
+          // Debounced executing recalculate after events fired
+          timeoutEventPromise = $timeout(function() {
+            // if popup is visible
+            if (scope.matches.length) {
+              recalculatePosition();
+            }
+
+            scope.moveInProgress = false;
+          }, eventDebounceTime);
+        }
+
+        // recalculate actual position and set new values to scope
+        // after digest loop is popup in right position
+        function recalculatePosition() {
+          scope.position = appendToBody ? $position.offset(element) : $position.position(element);
+          scope.position.top += element.prop('offsetHeight');
+        }
+
+        resetMatches();
+
+        //we need to propagate user's query so we can higlight matches
+        scope.query = undefined;
+
+        //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
+        var timeoutPromise;
+
+        var scheduleSearchWithTimeout = function(inputValue) {
+          timeoutPromise = $timeout(function() {
+            getMatchesAsync(inputValue);
+          }, waitTime);
+        };
+
+        var cancelPreviousTimeout = function() {
+          if (timeoutPromise) {
+            $timeout.cancel(timeoutPromise);
+          }
+        };
+
+        //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
+        //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
+        modelCtrl.$parsers.unshift(function(inputValue) {
+          hasFocus = true;
+
+          if (minLength === 0 || inputValue && inputValue.length >= minLength) {
+            if (waitTime > 0) {
+              cancelPreviousTimeout();
+              scheduleSearchWithTimeout(inputValue);
+            } else {
+              getMatchesAsync(inputValue);
+            }
+          } else {
+            isLoadingSetter(originalScope, false);
+            cancelPreviousTimeout();
+            resetMatches();
+          }
+
+          if (isEditable) {
+            return inputValue;
+          } else {
+            if (!inputValue) {
+              // Reset in case user had typed something previously.
+              modelCtrl.$setValidity('editable', true);
+              return null;
+            } else {
+              modelCtrl.$setValidity('editable', false);
+              return undefined;
+            }
+          }
+        });
+
+        modelCtrl.$formatters.push(function(modelValue) {
+          var candidateViewValue, emptyViewValue;
+          var locals = {};
+
+          // The validity may be set to false via $parsers (see above) if
+          // the model is restricted to selected values. If the model
+          // is set manually it is considered to be valid.
+          if (!isEditable) {
+            modelCtrl.$setValidity('editable', true);
+          }
+
+          if (inputFormatter) {
+            locals.$model = modelValue;
+            return inputFormatter(originalScope, locals);
+          } else {
+            //it might happen that we don't have enough info to properly render input value
+            //we need to check for this situation and simply return model value if we can't apply custom formatting
+            locals[parserResult.itemName] = modelValue;
+            candidateViewValue = parserResult.viewMapper(originalScope, locals);
+            locals[parserResult.itemName] = undefined;
+            emptyViewValue = parserResult.viewMapper(originalScope, locals);
+
+            return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue;
+          }
+        });
+
+        scope.select = function(activeIdx) {
+          //called from within the $digest() cycle
+          var locals = {};
+          var model, item;
+
+          selected = true;
+          locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
+          model = parserResult.modelMapper(originalScope, locals);
+          $setModelValue(originalScope, model);
+          modelCtrl.$setValidity('editable', true);
+          modelCtrl.$setValidity('parse', true);
+
+          onSelectCallback(originalScope, {
+            $item: item,
+            $model: model,
+            $label: parserResult.viewMapper(originalScope, locals)
+          });
+
+          resetMatches();
+
+          //return focus to the input element if a match was selected via a mouse click event
+          // use timeout to avoid $rootScope:inprog error
+          if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) {
+            $timeout(function() { element[0].focus(); }, 0, false);
+          }
+        };
+
+        //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
+        element.bind('keydown', function(evt) {
+          //typeahead is open and an "interesting" key was pressed
+          if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
+            return;
+          }
+
+          // if there's nothing selected (i.e. focusFirst) and enter or tab is hit, clear the results
+          if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13)) {
+            resetMatches();
+            scope.$digest();
+            return;
+          }
+
+          evt.preventDefault();
+
+          if (evt.which === 40) {
+            scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
+            scope.$digest();
+          } else if (evt.which === 38) {
+            scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
+            scope.$digest();
+          } else if (evt.which === 13 || evt.which === 9) {
+            scope.$apply(function () {
+              scope.select(scope.activeIdx);
+            });
+          } else if (evt.which === 27) {
+            evt.stopPropagation();
+
+            resetMatches();
+            scope.$digest();
+          }
+        });
+
+        element.bind('blur', function() {
+          if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) {
+            selected = true;
+            scope.$apply(function() {
+              scope.select(scope.activeIdx);
+            });
+          }
+          hasFocus = false;
+          selected = false;
+        });
+
+        // Keep reference to click handler to unbind it.
+        var dismissClickHandler = function(evt) {
+          // Issue #3973
+          // Firefox treats right click as a click on document
+          if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) {
+            resetMatches();
+            if (!$rootScope.$$phase) {
+              scope.$digest();
+            }
+          }
+        };
+
+        $document.bind('click', dismissClickHandler);
+
+        originalScope.$on('$destroy', function() {
+          $document.unbind('click', dismissClickHandler);
+          if (appendToBody || appendToElementId) {
+            $popup.remove();
+          }
+
+          if (appendToBody) {
+            angular.element($window).unbind('resize', fireRecalculating);
+            $document.find('body').unbind('scroll', fireRecalculating);
+          }
+          // Prevent jQuery cache memory leak
+          popUpEl.remove();
+        });
+
+        var $popup = $compile(popUpEl)(scope);
+
+        if (appendToBody) {
+          $document.find('body').append($popup);
+        } else if (appendToElementId !== false) {
+          angular.element($document[0].getElementById(appendToElementId)).append($popup);
+        } else {
+          element.after($popup);
+        }
+      }
+    };
+  }])
+  
+  .directive('typeaheadPopup', ['$typeaheadSuppressWarning', '$log', function($typeaheadSuppressWarning, $log) {
+    return {
+      scope: {
+        matches: '=',
+        query: '=',
+        active: '=',
+        position: '&',
+        moveInProgress: '=',
+        select: '&'
+      },
+      replace: true,
+      templateUrl: function(element, attrs) {
+        return attrs.popupTemplateUrl || 'template/typeahead/typeahead-popup.html';
+      },
+      link: function(scope, element, attrs) {
+        
+        if (!$typeaheadSuppressWarning) {
+          $log.warn('typeahead-popup is now deprecated. Use uib-typeahead-popup instead.');
+        }
+        scope.templateUrl = attrs.templateUrl;
+
+        scope.isOpen = function() {
+          return scope.matches.length > 0;
+        };
+
+        scope.isActive = function(matchIdx) {
+          return scope.active == matchIdx;
+        };
+
+        scope.selectActive = function(matchIdx) {
+          scope.active = matchIdx;
+        };
+
+        scope.selectMatch = function(activeIdx) {
+          scope.select({activeIdx:activeIdx});
+        };
+      }
+    };
+  }])
+  
+  .directive('typeaheadMatch', ['$templateRequest', '$compile', '$parse', '$typeaheadSuppressWarning', '$log', function($templateRequest, $compile, $parse, $typeaheadSuppressWarning, $log) {
+    return {
+      restrict: 'EA',
+      scope: {
+        index: '=',
+        match: '=',
+        query: '='
+      },
+      link:function(scope, element, attrs) {
+        if (!$typeaheadSuppressWarning) {
+          $log.warn('typeahead-match is now deprecated. Use uib-typeahead-match instead.');
+        }
+
+        var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html';
+        $templateRequest(tplUrl).then(function(tplContent) {
+          $compile(tplContent.trim())(scope, function(clonedElement) {
+            element.replaceWith(clonedElement);
+          });
+        });
+      }
+    };
+  }])
+  
+  .filter('typeaheadHighlight', ['$sce', '$injector', '$log', '$typeaheadSuppressWarning', function($sce, $injector, $log, $typeaheadSuppressWarning) {
+    var isSanitizePresent;
+    isSanitizePresent = $injector.has('$sanitize');
+
+    function escapeRegexp(queryToEscape) {
+      // Regex: capture the whole query string and replace it with the string that will be used to match
+      // the results, for example if the capture is "a" the result will be \a
+      return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
+    }
+
+    function containsHtml(matchItem) {
+      return /<.*>/g.test(matchItem);
+    }
+
+    return function(matchItem, query) {
+      if (!$typeaheadSuppressWarning) {
+        $log.warn('typeaheadHighlight is now deprecated. Use uibTypeaheadHighlight instead.');
+      }
+
+      if (!isSanitizePresent && containsHtml(matchItem)) {
+        $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger
+      }
+
+      matchItem = query? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem; // Replaces the capture string with a the same string inside of a "strong" tag
+      if (!isSanitizePresent) {
+        matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive
+      }
+
+      return matchItem;
+    };
+  }]);
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/accordion/accordion-group.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/accordion/accordion-group.html
new file mode 100644
index 0000000..1dc4c8e
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/accordion/accordion-group.html
@@ -0,0 +1,10 @@
+<div class="panel {{panelClass || 'panel-default'}}">
+  <div class="panel-heading" ng-keypress="toggleOpen($event)">
+    <h4 class="panel-title">
+      <a href tabindex="0" class="accordion-toggle" ng-click="toggleOpen()" uib-accordion-transclude="heading"><span ng-class="{'text-muted': isDisabled}">{{heading}}</span></a>
+    </h4>
+  </div>
+  <div class="panel-collapse collapse" uib-collapse="!isOpen">
+	  <div class="panel-body" ng-transclude></div>
+  </div>
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/accordion/accordion.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/accordion/accordion.html
new file mode 100644
index 0000000..ba428f3
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/accordion/accordion.html
@@ -0,0 +1 @@
+<div class="panel-group" ng-transclude></div>
\ No newline at end of file
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/alert/alert.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/alert/alert.html
new file mode 100644
index 0000000..0885587
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/alert/alert.html
@@ -0,0 +1,7 @@
+<div class="alert" ng-class="['alert-' + (type || 'warning'), closeable ? 'alert-dismissible' : null]" role="alert">
+    <button ng-show="closeable" type="button" class="close" ng-click="close({$event: $event})">
+        <span aria-hidden="true">&times;</span>
+        <span class="sr-only">Close</span>
+    </button>
+    <div ng-transclude></div>
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/carousel/carousel.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/carousel/carousel.html
new file mode 100644
index 0000000..372c547
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/carousel/carousel.html
@@ -0,0 +1,16 @@
+<div ng-mouseenter="pause()" ng-mouseleave="play()" class="carousel" ng-swipe-right="prev()" ng-swipe-left="next()">
+  <div class="carousel-inner" ng-transclude></div>
+  <a role="button" href class="left carousel-control" ng-click="prev()" ng-show="slides.length > 1">
+    <span aria-hidden="true" class="glyphicon glyphicon-chevron-left"></span>
+    <span class="sr-only">previous</span>
+  </a>
+  <a role="button" href class="right carousel-control" ng-click="next()" ng-show="slides.length > 1">
+    <span aria-hidden="true" class="glyphicon glyphicon-chevron-right"></span>
+    <span class="sr-only">next</span>
+  </a>
+  <ol class="carousel-indicators" ng-show="slides.length > 1">
+    <li ng-repeat="slide in slides | orderBy:indexOfSlide track by $index" ng-class="{ active: isActive(slide) }" ng-click="select(slide)">
+      <span class="sr-only">slide {{ $index + 1 }} of {{ slides.length }}<span ng-if="isActive(slide)">, currently active</span></span>
+    </li>
+  </ol>
+</div>
\ No newline at end of file
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/carousel/slide.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/carousel/slide.html
new file mode 100644
index 0000000..5220139
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/carousel/slide.html
@@ -0,0 +1,3 @@
+<div ng-class="{
+    'active': active
+  }" class="item text-center" ng-transclude></div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/datepicker/datepicker.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/datepicker/datepicker.html
new file mode 100644
index 0000000..d515832
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/datepicker/datepicker.html
@@ -0,0 +1,5 @@
+<div ng-switch="datepickerMode" role="application" ng-keydown="keydown($event)">
+  <uib-daypicker ng-switch-when="day" tabindex="0"></uib-daypicker>
+  <uib-monthpicker ng-switch-when="month" tabindex="0"></uib-monthpicker>
+  <uib-yearpicker ng-switch-when="year" tabindex="0"></uib-yearpicker>
+</div>
\ No newline at end of file
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/datepicker/day.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/datepicker/day.html
new file mode 100644
index 0000000..91cf57a
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/datepicker/day.html
@@ -0,0 +1,21 @@
+<table role="grid" aria-labelledby="{{::uniqueId}}-title" aria-activedescendant="{{activeDateId}}">
+  <thead>
+    <tr>
+      <th><button type="button" class="btn btn-default btn-sm pull-left" ng-click="move(-1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-left"></i></button></th>
+      <th colspan="{{::5 + showWeeks}}"><button id="{{::uniqueId}}-title" role="heading" aria-live="assertive" aria-atomic="true" type="button" class="btn btn-default btn-sm" ng-click="toggleMode()" ng-disabled="datepickerMode === maxMode" tabindex="-1" style="width:100%;"><strong>{{title}}</strong></button></th>
+      <th><button type="button" class="btn btn-default btn-sm pull-right" ng-click="move(1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-right"></i></button></th>
+    </tr>
+    <tr>
+      <th ng-if="showWeeks" class="text-center"></th>
+      <th ng-repeat="label in ::labels track by $index" class="text-center"><small aria-label="{{::label.full}}">{{::label.abbr}}</small></th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr ng-repeat="row in rows track by $index">
+      <td ng-if="showWeeks" class="text-center h6"><em>{{ weekNumbers[$index] }}</em></td>
+      <td ng-repeat="dt in row track by dt.date" class="text-center" role="gridcell" id="{{::dt.uid}}" ng-class="::dt.customClass">
+        <button type="button" style="min-width:100%;" class="btn btn-default btn-sm" ng-class="{'btn-info': dt.selected, active: isActive(dt)}" ng-click="select(dt.date)" ng-disabled="dt.disabled" tabindex="-1"><span ng-class="::{'text-muted': dt.secondary, 'text-info': dt.current}">{{::dt.label}}</span></button>
+      </td>
+    </tr>
+  </tbody>
+</table>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/datepicker/month.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/datepicker/month.html
new file mode 100644
index 0000000..cebd053
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/datepicker/month.html
@@ -0,0 +1,16 @@
+<table role="grid" aria-labelledby="{{::uniqueId}}-title" aria-activedescendant="{{activeDateId}}">
+  <thead>
+    <tr>
+      <th><button type="button" class="btn btn-default btn-sm pull-left" ng-click="move(-1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-left"></i></button></th>
+      <th><button id="{{::uniqueId}}-title" role="heading" aria-live="assertive" aria-atomic="true" type="button" class="btn btn-default btn-sm" ng-click="toggleMode()" ng-disabled="datepickerMode === maxMode" tabindex="-1" style="width:100%;"><strong>{{title}}</strong></button></th>
+      <th><button type="button" class="btn btn-default btn-sm pull-right" ng-click="move(1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-right"></i></button></th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr ng-repeat="row in rows track by $index">
+      <td ng-repeat="dt in row track by dt.date" class="text-center" role="gridcell" id="{{::dt.uid}}" ng-class="::dt.customClass">
+        <button type="button" style="min-width:100%;" class="btn btn-default" ng-class="{'btn-info': dt.selected, active: isActive(dt)}" ng-click="select(dt.date)" ng-disabled="dt.disabled" tabindex="-1"><span ng-class="::{'text-info': dt.current}">{{::dt.label}}</span></button>
+      </td>
+    </tr>
+  </tbody>
+</table>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/datepicker/popup.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/datepicker/popup.html
new file mode 100644
index 0000000..e3dda67
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/datepicker/popup.html
@@ -0,0 +1,10 @@
+<ul class="dropdown-menu" dropdown-nested ng-if="isOpen" style="display: block" ng-style="{top: position.top+'px', left: position.left+'px'}" ng-keydown="keydown($event)" ng-click="$event.stopPropagation()">
+	<li ng-transclude></li>
+	<li ng-if="showButtonBar" style="padding:10px 9px 2px">
+		<span class="btn-group pull-left">
+			<button type="button" class="btn btn-sm btn-info" ng-click="select('today')" ng-disabled="isDisabled('today')">{{ getText('current') }}</button>
+			<button type="button" class="btn btn-sm btn-danger" ng-click="select(null)">{{ getText('clear') }}</button>
+		</span>
+		<button type="button" class="btn btn-sm btn-success pull-right" ng-click="close()">{{ getText('close') }}</button>
+	</li>
+</ul>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/datepicker/year.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/datepicker/year.html
new file mode 100644
index 0000000..8606f0d
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/datepicker/year.html
@@ -0,0 +1,16 @@
+<table role="grid" aria-labelledby="{{::uniqueId}}-title" aria-activedescendant="{{activeDateId}}">
+  <thead>
+    <tr>
+      <th><button type="button" class="btn btn-default btn-sm pull-left" ng-click="move(-1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-left"></i></button></th>
+      <th colspan="3"><button id="{{::uniqueId}}-title" role="heading" aria-live="assertive" aria-atomic="true" type="button" class="btn btn-default btn-sm" ng-click="toggleMode()" ng-disabled="datepickerMode === maxMode" tabindex="-1" style="width:100%;"><strong>{{title}}</strong></button></th>
+      <th><button type="button" class="btn btn-default btn-sm pull-right" ng-click="move(1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-right"></i></button></th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr ng-repeat="row in rows track by $index">
+      <td ng-repeat="dt in row track by dt.date" class="text-center" role="gridcell" id="{{::dt.uid}}" ng-class="::dt.customClass">
+        <button type="button" style="min-width:100%;" class="btn btn-default" ng-class="{'btn-info': dt.selected, active: isActive(dt)}" ng-click="select(dt.date)" ng-disabled="dt.disabled" tabindex="-1"><span ng-class="::{'text-info': dt.current}">{{::dt.label}}</span></button>
+      </td>
+    </tr>
+  </tbody>
+</table>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/modal/backdrop.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/modal/backdrop.html
new file mode 100644
index 0000000..214273e
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/modal/backdrop.html
@@ -0,0 +1,4 @@
+<div uib-modal-animation-class="fade"
+     modal-in-class="in"
+     ng-style="{'z-index': 1040 + (index && 1 || 0) + index*10}"
+></div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/modal/window.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/modal/window.html
new file mode 100644
index 0000000..ae17e40
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/modal/window.html
@@ -0,0 +1,6 @@
+<div modal-render="{{$isRendered}}" tabindex="-1" role="dialog" class="modal"
+    uib-modal-animation-class="fade"
+    modal-in-class="in"
+    ng-style="{'z-index': 1050 + index*10, display: 'block'}">
+    <div class="modal-dialog" ng-class="size ? 'modal-' + size : ''"><div class="modal-content" uib-modal-transclude></div></div>
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/pagination/pager.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/pagination/pager.html
new file mode 100644
index 0000000..46f227f
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/pagination/pager.html
@@ -0,0 +1,4 @@
+<ul class="pager">
+  <li ng-class="{disabled: noPrevious()||ngDisabled, previous: align}"><a href ng-click="selectPage(page - 1, $event)">{{::getText('previous')}}</a></li>
+  <li ng-class="{disabled: noNext()||ngDisabled, next: align}"><a href ng-click="selectPage(page + 1, $event)">{{::getText('next')}}</a></li>
+</ul>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/pagination/pagination.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/pagination/pagination.html
new file mode 100644
index 0000000..f55a7b6
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/pagination/pagination.html
@@ -0,0 +1,7 @@
+<ul class="pagination">
+  <li ng-if="::boundaryLinks" ng-class="{disabled: noPrevious()||ngDisabled}" class="pagination-first"><a href ng-click="selectPage(1, $event)">{{::getText('first')}}</a></li>
+  <li ng-if="::directionLinks" ng-class="{disabled: noPrevious()||ngDisabled}" class="pagination-prev"><a href ng-click="selectPage(page - 1, $event)">{{::getText('previous')}}</a></li>
+  <li ng-repeat="page in pages track by $index" ng-class="{active: page.active,disabled: ngDisabled&&!page.active}" class="pagination-page"><a href ng-click="selectPage(page.number, $event)">{{page.text}}</a></li>
+  <li ng-if="::directionLinks" ng-class="{disabled: noNext()||ngDisabled}" class="pagination-next"><a href ng-click="selectPage(page + 1, $event)">{{::getText('next')}}</a></li>
+  <li ng-if="::boundaryLinks" ng-class="{disabled: noNext()||ngDisabled}" class="pagination-last"><a href ng-click="selectPage(totalPages, $event)">{{::getText('last')}}</a></li>
+</ul>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/popover/popover-html.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/popover/popover-html.html
new file mode 100644
index 0000000..3751ca8
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/popover/popover-html.html
@@ -0,0 +1,10 @@
+<div tooltip-animation-class="fade"
+  uib-tooltip-classes
+  ng-class="{ in: isOpen() }">
+  <div class="arrow"></div>
+
+  <div class="popover-inner">
+      <h3 class="popover-title" ng-bind="title" ng-if="title"></h3>
+      <div class="popover-content" ng-bind-html="contentExp()"></div>
+  </div>
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/popover/popover-template.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/popover/popover-template.html
new file mode 100644
index 0000000..914d1d4
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/popover/popover-template.html
@@ -0,0 +1,12 @@
+<div tooltip-animation-class="fade"
+  uib-tooltip-classes
+  ng-class="{ in: isOpen() }">
+  <div class="arrow"></div>
+
+  <div class="popover-inner">
+      <h3 class="popover-title" ng-bind="title" ng-if="title"></h3>
+      <div class="popover-content"
+        uib-tooltip-template-transclude="contentExp()"
+        tooltip-template-transclude-scope="originScope()"></div>
+  </div>
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/popover/popover.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/popover/popover.html
new file mode 100644
index 0000000..91a7883
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/popover/popover.html
@@ -0,0 +1,10 @@
+<div tooltip-animation-class="fade"
+  uib-tooltip-classes
+  ng-class="{ in: isOpen() }">
+  <div class="arrow"></div>
+
+  <div class="popover-inner">
+      <h3 class="popover-title" ng-bind="title" ng-if="title"></h3>
+      <div class="popover-content" ng-bind="content"></div>
+  </div>
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/progressbar/bar.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/progressbar/bar.html
new file mode 100644
index 0000000..b5b61e0
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/progressbar/bar.html
@@ -0,0 +1 @@
+<div class="progress-bar" ng-class="type && 'progress-bar-' + type" role="progressbar" aria-valuenow="{{value}}" aria-valuemin="0" aria-valuemax="{{max}}" ng-style="{width: (percent < 100 ? percent : 100) + '%'}" aria-valuetext="{{percent | number:0}}%" aria-labelledby="{{::title}}" style="min-width: 0;" ng-transclude></div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/progressbar/progress.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/progressbar/progress.html
new file mode 100644
index 0000000..38ee9f7
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/progressbar/progress.html
@@ -0,0 +1 @@
+<div class="progress" ng-transclude aria-labelledby="{{::title}}"></div>
\ No newline at end of file
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/progressbar/progressbar.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/progressbar/progressbar.html
new file mode 100644
index 0000000..dbfa5aa
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/progressbar/progressbar.html
@@ -0,0 +1,3 @@
+<div class="progress">
+  <div class="progress-bar" ng-class="type && 'progress-bar-' + type" role="progressbar" aria-valuenow="{{value}}" aria-valuemin="0" aria-valuemax="{{max}}" ng-style="{width: (percent < 100 ? percent : 100) + '%'}" aria-valuetext="{{percent | number:0}}%" aria-labelledby="{{::title}}" style="min-width: 0;" ng-transclude></div>
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/rating/rating.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/rating/rating.html
new file mode 100644
index 0000000..5543b03
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/rating/rating.html
@@ -0,0 +1,4 @@
+<span ng-mouseleave="reset()" ng-keydown="onKeydown($event)" tabindex="0" role="slider" aria-valuemin="0" aria-valuemax="{{range.length}}" aria-valuenow="{{value}}">
+    <span ng-repeat-start="r in range track by $index" class="sr-only">({{ $index < value ? '*' : ' ' }})</span>
+    <i ng-repeat-end ng-mouseenter="enter($index + 1)" ng-click="rate($index + 1)" class="glyphicon" ng-class="$index < value && (r.stateOn || 'glyphicon-star') || (r.stateOff || 'glyphicon-star-empty')" ng-attr-title="{{r.title}}" aria-valuetext="{{r.title}}"></i>
+</span>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/tabs/tab.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/tabs/tab.html
new file mode 100644
index 0000000..0d8a42e
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/tabs/tab.html
@@ -0,0 +1,3 @@
+<li ng-class="{active: active, disabled: disabled}">
+  <a href ng-click="select()" uib-tab-heading-transclude>{{heading}}</a>
+</li>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/tabs/tabset.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/tabs/tabset.html
new file mode 100644
index 0000000..294c86a
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/tabs/tabset.html
@@ -0,0 +1,10 @@
+<div>
+  <ul class="nav nav-{{type || 'tabs'}}" ng-class="{'nav-stacked': vertical, 'nav-justified': justified}" ng-transclude></ul>
+  <div class="tab-content">
+    <div class="tab-pane" 
+         ng-repeat="tab in tabs" 
+         ng-class="{active: tab.active}"
+         uib-tab-content-transclude="tab">
+    </div>
+  </div>
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/timepicker/timepicker.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/timepicker/timepicker.html
new file mode 100644
index 0000000..1873841
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/timepicker/timepicker.html
@@ -0,0 +1,26 @@
+<table>
+  <tbody>
+    <tr class="text-center" ng-show="::showSpinners">
+      <td><a ng-click="incrementHours()" ng-class="{disabled: noIncrementHours()}" class="btn btn-link" ng-disabled="noIncrementHours()" tabindex="{{::tabindex}}"><span class="glyphicon glyphicon-chevron-up"></span></a></td>
+      <td>&nbsp;</td>
+      <td><a ng-click="incrementMinutes()" ng-class="{disabled: noIncrementMinutes()}" class="btn btn-link" ng-disabled="noIncrementMinutes()" tabindex="{{::tabindex}}"><span class="glyphicon glyphicon-chevron-up"></span></a></td>
+      <td ng-show="showMeridian"></td>
+    </tr>
+    <tr>
+      <td class="form-group" ng-class="{'has-error': invalidHours}">
+        <input style="width:50px;" type="text" ng-model="hours" ng-change="updateHours()" class="form-control text-center" ng-readonly="::readonlyInput" maxlength="2" tabindex="{{::tabindex}}">
+      </td>
+      <td>:</td>
+      <td class="form-group" ng-class="{'has-error': invalidMinutes}">
+        <input style="width:50px;" type="text" ng-model="minutes" ng-change="updateMinutes()" class="form-control text-center" ng-readonly="::readonlyInput" maxlength="2" tabindex="{{::tabindex}}">
+      </td>
+      <td ng-show="showMeridian"><button type="button" ng-class="{disabled: noToggleMeridian()}" class="btn btn-default text-center" ng-click="toggleMeridian()" ng-disabled="noToggleMeridian()" tabindex="{{::tabindex}}">{{meridian}}</button></td>
+    </tr>
+    <tr class="text-center" ng-show="::showSpinners">
+      <td><a ng-click="decrementHours()" ng-class="{disabled: noDecrementHours()}" class="btn btn-link" ng-disabled="noDecrementHours()" tabindex="{{::tabindex}}"><span class="glyphicon glyphicon-chevron-down"></span></a></td>
+      <td>&nbsp;</td>
+      <td><a ng-click="decrementMinutes()" ng-class="{disabled: noDecrementMinutes()}" class="btn btn-link" ng-disabled="noDecrementMinutes()" tabindex="{{::tabindex}}"><span class="glyphicon glyphicon-chevron-down"></span></a></td>
+      <td ng-show="showMeridian"></td>
+    </tr>
+  </tbody>
+</table>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/tooltip/tooltip-html-popup.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/tooltip/tooltip-html-popup.html
new file mode 100644
index 0000000..dae22b7
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/tooltip/tooltip-html-popup.html
@@ -0,0 +1,7 @@
+<div
+  tooltip-animation-class="fade"
+  uib-tooltip-classes
+  ng-class="{ in: isOpen() }">
+  <div class="tooltip-arrow"></div>
+  <div class="tooltip-inner" ng-bind-html="contentExp()"></div>
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/tooltip/tooltip-popup.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/tooltip/tooltip-popup.html
new file mode 100644
index 0000000..711fc56
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/tooltip/tooltip-popup.html
@@ -0,0 +1,7 @@
+<div
+  tooltip-animation-class="fade"
+  uib-tooltip-classes
+  ng-class="{ in: isOpen() }">
+  <div class="tooltip-arrow"></div>
+  <div class="tooltip-inner" ng-bind="content"></div>
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/tooltip/tooltip-template-popup.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/tooltip/tooltip-template-popup.html
new file mode 100644
index 0000000..e83bff3
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/tooltip/tooltip-template-popup.html
@@ -0,0 +1,9 @@
+<div
+  tooltip-animation-class="fade"
+  uib-tooltip-classes
+  ng-class="{ in: isOpen() }">
+  <div class="tooltip-arrow"></div>
+  <div class="tooltip-inner"
+    uib-tooltip-template-transclude="contentExp()"
+    tooltip-template-transclude-scope="originScope()"></div>
+</div>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/typeahead/typeahead-match.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/typeahead/typeahead-match.html
new file mode 100644
index 0000000..c64462b
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/typeahead/typeahead-match.html
@@ -0,0 +1 @@
+<a href tabindex="-1" ng-bind-html="match.label | uibTypeaheadHighlight:query"></a>
diff --git a/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/typeahead/typeahead-popup.html b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/typeahead/typeahead-popup.html
new file mode 100644
index 0000000..20a362b
--- /dev/null
+++ b/xos-apps/auto-scale/gui/src/vendor/ui.bootstrap/template/typeahead/typeahead-popup.html
@@ -0,0 +1,5 @@
+<ul class="dropdown-menu" ng-show="isOpen() && !moveInProgress" ng-style="{top: position().top+'px', left: position().left+'px'}" style="display: block;" role="listbox" aria-hidden="{{!isOpen()}}">
+    <li ng-repeat="match in matches track by $index" ng-class="{active: isActive($index) }" ng-mouseenter="selectActive($index)" ng-click="selectMatch($index)" role="option" id="{{::match.id}}">
+        <div uib-typeahead-match index="$index" match="match" query="query" template-url="templateUrl"></div>
+    </li>
+</ul>