diff --git a/dashboard/.bowerrc b/dashboard/.bowerrc
new file mode 100644
index 000000000..ba0accc5a
--- /dev/null
+++ b/dashboard/.bowerrc
@@ -0,0 +1,3 @@
+{
+ "directory": "app/bower_components"
+}
diff --git a/dashboard/.editorconfig b/dashboard/.editorconfig
new file mode 100644
index 000000000..c2cdfb8ad
--- /dev/null
+++ b/dashboard/.editorconfig
@@ -0,0 +1,21 @@
+# EditorConfig helps developers define and maintain consistent
+# coding styles between different editors and IDEs
+# editorconfig.org
+
+root = true
+
+
+[*]
+
+# Change these settings to your own preference
+indent_style = space
+indent_size = 2
+
+# We recommend you to keep these unchanged
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/dashboard/.gitattributes b/dashboard/.gitattributes
new file mode 100644
index 000000000..212566614
--- /dev/null
+++ b/dashboard/.gitattributes
@@ -0,0 +1 @@
+* text=auto
\ No newline at end of file
diff --git a/dashboard/.gitignore b/dashboard/.gitignore
new file mode 100644
index 000000000..7911b28d5
--- /dev/null
+++ b/dashboard/.gitignore
@@ -0,0 +1,5 @@
+node_modules
+dist
+.tmp
+.sass-cache
+app/bower_components
diff --git a/dashboard/.jshintignore b/dashboard/.jshintignore
new file mode 100644
index 000000000..ddbd3d2bc
--- /dev/null
+++ b/dashboard/.jshintignore
@@ -0,0 +1,3 @@
+app/scripts/vega.js
+app/scripts/moment.min.js
+app/scripts/ng-time-relative.min.js
diff --git a/dashboard/.jshintrc b/dashboard/.jshintrc
new file mode 100644
index 000000000..950c65247
--- /dev/null
+++ b/dashboard/.jshintrc
@@ -0,0 +1,27 @@
+{
+ "node": true,
+ "browser": true,
+ "esnext": true,
+ "bitwise": true,
+ "camelcase": true,
+ "curly": true,
+ "eqeqeq": true,
+ "immed": true,
+ "indent": 2,
+ "latedef": true,
+ "newcap": true,
+ "noarg": true,
+ "quotmark": "single",
+ "regexp": true,
+ "undef": true,
+ "unused": false,
+ "strict": true,
+ "trailing": true,
+ "smarttabs": true,
+ "globals": {
+ "angular": false,
+ "$": false,
+ "vg": false,
+ "moment": false
+ }
+}
diff --git a/dashboard/.travis.yml b/dashboard/.travis.yml
new file mode 100644
index 000000000..83f4e22f0
--- /dev/null
+++ b/dashboard/.travis.yml
@@ -0,0 +1,7 @@
+language: node_js
+node_js:
+ - '0.8'
+ - '0.10'
+before_script:
+ - 'npm install -g bower grunt-cli'
+ - 'bower install'
diff --git a/dashboard/Gruntfile.js b/dashboard/Gruntfile.js
new file mode 100644
index 000000000..39870153a
--- /dev/null
+++ b/dashboard/Gruntfile.js
@@ -0,0 +1,345 @@
+// Generated on 2013-10-07 using generator-webapp 0.4.3
+'use strict';
+
+// # Globbing
+// for performance reasons we're only matching one level down:
+// 'test/spec/{,*/}*.js'
+// use this if you want to recursively match all subfolders:
+// 'test/spec/**/*.js'
+
+module.exports = function (grunt) {
+ // show elapsed time at the end
+ require('time-grunt')(grunt);
+ // load all grunt tasks
+ require('load-grunt-tasks')(grunt);
+
+ grunt.initConfig({
+ // configurable paths
+ uglify: {
+ options: {
+ mangle: false
+ },
+ },
+ yeoman: {
+ app: 'app',
+ dist: 'dist'
+ },
+ watch: {
+ compass: {
+ files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
+ tasks: ['compass:server', 'autoprefixer']
+ },
+ styles: {
+ files: ['<%= yeoman.app %>/styles/{,*/}*.css'],
+ tasks: ['copy:styles', 'autoprefixer']
+ },
+ livereload: {
+ options: {
+ livereload: '<%= connect.options.livereload %>'
+ },
+ files: [
+ '<%= yeoman.app %>/*.html',
+ '.tmp/styles/{,*/}*.css',
+ '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js',
+ '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
+ ]
+ }
+ },
+ connect: {
+ options: {
+ port: 9000,
+ livereload: 35729,
+ // change this to '0.0.0.0' to access the server from outside
+ hostname: 'localhost'
+ },
+ livereload: {
+ options: {
+ open: true,
+ base: [
+ '.tmp',
+ '<%= yeoman.app %>'
+ ]
+ }
+ },
+ test: {
+ options: {
+ base: [
+ '.tmp',
+ 'test',
+ '<%= yeoman.app %>'
+ ]
+ }
+ },
+ dist: {
+ options: {
+ open: true,
+ base: '<%= yeoman.dist %>'
+ }
+ }
+ },
+ clean: {
+ dist: {
+ files: [{
+ dot: true,
+ src: [
+ '.tmp',
+ '<%= yeoman.dist %>/*',
+ '!<%= yeoman.dist %>/.git*'
+ ]
+ }]
+ },
+ server: '.tmp'
+ },
+ jshint: {
+ options: {
+ jshintrc: '.jshintrc'
+ },
+ all: [
+ '<%= yeoman.app %>/scripts/{,*/}*.js',
+ '!<%= yeoman.app %>/scripts/vendor/*',
+ ]
+ },
+ mocha: {
+ all: {
+ options: {
+ run: true,
+ urls: ['http://<%= connect.test.options.hostname %>:<%= connect.test.options.port %>/index.html']
+ }
+ }
+ },
+ compass: {
+ options: {
+ sassDir: '<%= yeoman.app %>/styles',
+ cssDir: '.tmp/styles',
+ generatedImagesDir: '.tmp/images/generated',
+ imagesDir: '<%= yeoman.app %>/images',
+ javascriptsDir: '<%= yeoman.app %>/scripts',
+ fontsDir: '<%= yeoman.app %>/styles/fonts',
+ importPath: '<%= yeoman.app %>/bower_components',
+ httpImagesPath: '/images',
+ httpGeneratedImagesPath: '/images/generated',
+ httpFontsPath: '/styles/fonts',
+ relativeAssets: false,
+ assetCacheBuster: false
+ },
+ dist: {
+ options: {
+ generatedImagesDir: '<%= yeoman.dist %>/images/generated'
+ }
+ },
+ server: {
+ options: {
+ debugInfo: true
+ }
+ }
+ },
+ autoprefixer: {
+ options: {
+ browsers: ['last 1 version']
+ },
+ dist: {
+ files: [{
+ expand: true,
+ cwd: '.tmp/styles/',
+ src: '{,*/}*.css',
+ dest: '.tmp/styles/'
+ }]
+ }
+ },
+ // not used since Uglify task does concat,
+ // but still available if needed
+ /*concat: {
+ dist: {}
+ },*/
+ requirejs: {
+ dist: {
+ // Options: https://github.com/jrburke/r.js/blob/master/build/example.build.js
+ options: {
+ // `name` and `out` is set by grunt-usemin
+ baseUrl: '<%= yeoman.app %>/scripts',
+ optimize: 'none',
+ // TODO: Figure out how to make sourcemaps work with grunt-usemin
+ // https://github.com/yeoman/grunt-usemin/issues/30
+ //generateSourceMaps: true,
+ // required to support SourceMaps
+ // http://requirejs.org/docs/errors.html#sourcemapcomments
+ preserveLicenseComments: false,
+ useStrict: true,
+ wrap: true
+ //uglify2: {} // https://github.com/mishoo/UglifyJS2
+ }
+ }
+ },
+ useminPrepare: {
+ options: {
+ dest: '<%= yeoman.dist %>'
+ },
+ html: ['<%= yeoman.app %>/**/*.html']
+ },
+ usemin: {
+ options: {
+ dirs: ['<%= yeoman.dist %>']
+ },
+ html: ['<%= yeoman.dist %>/{,*/}*.html'],
+ css: ['<%= yeoman.dist %>/styles/{,*/}*.css']
+ },
+ imagemin: {
+ dist: {
+ files: [{
+ expand: true,
+ cwd: '<%= yeoman.app %>/images',
+ src: '{,*/}*.{png,jpg,jpeg}',
+ dest: '<%= yeoman.dist %>/images'
+ }]
+ }
+ },
+ svgmin: {
+ dist: {
+ files: [{
+ expand: true,
+ cwd: '<%= yeoman.app %>/images',
+ src: '{,*/}*.svg',
+ dest: '<%= yeoman.dist %>/images'
+ }]
+ }
+ },
+ cssmin: {
+ // This task is pre-configured if you do not wish to use Usemin
+ // blocks for your CSS. By default, the Usemin block from your
+ // `index.html` will take care of minification, e.g.
+ //
+ //
+ //
+ // dist: {
+ // files: {
+ // '<%= yeoman.dist %>/styles/main.css': [
+ // '.tmp/styles/{,*/}*.css',
+ // '<%= yeoman.app %>/styles/{,*/}*.css'
+ // ]
+ // }
+ // }
+ },
+ htmlmin: {
+ dist: {
+ options: {
+ /*removeCommentsFromCDATA: true,
+ // https://github.com/yeoman/grunt-usemin/issues/44
+ //collapseWhitespace: true,
+ collapseBooleanAttributes: true,
+ removeAttributeQuotes: true,
+ removeRedundantAttributes: true,
+ useShortDoctype: true,
+ removeEmptyAttributes: true,
+ removeOptionalTags: true*/
+ },
+ files: [{
+ expand: true,
+ cwd: '<%= yeoman.app %>',
+ src: '*.html',
+ dest: '<%= yeoman.dist %>'
+ }]
+ }
+ },
+ // Put files not handled in other tasks here
+ copy: {
+ dist: {
+ files: [{
+ expand: true,
+ dot: true,
+ cwd: '<%= yeoman.app %>',
+ dest: '<%= yeoman.dist %>',
+ src: [
+ '*.{ico,png,txt}',
+ '.htaccess',
+ 'images/{,*/}*.{webp,gif}',
+ 'styles/fonts/{,*/}*.*',
+ 'views/*.*',
+ 'index.html',
+ 'bower_components/sass-bootstrap/fonts/*.*'
+ ]
+ }]
+ },
+ styles: {
+ expand: true,
+ dot: true,
+ cwd: '<%= yeoman.app %>/styles',
+ dest: '.tmp/styles/',
+ src: '{,*/}*.css'
+ }
+ },
+ modernizr: {
+ devFile: '<%= yeoman.app %>/bower_components/modernizr/modernizr.js',
+ outputFile: '<%= yeoman.dist %>/bower_components/modernizr/modernizr.js',
+ files: [
+ '<%= yeoman.dist %>/scripts/{,*/}*.js',
+ '<%= yeoman.dist %>/styles/{,*/}*.css',
+ '!<%= yeoman.dist %>/scripts/vendor/*'
+ ],
+ uglify: true
+ },
+ concurrent: {
+ server: [
+ 'compass',
+ 'copy:styles'
+ ],
+ test: [
+ 'copy:styles'
+ ],
+ dist: [
+ 'compass',
+ 'copy:styles',
+ 'imagemin',
+ 'svgmin',
+ 'htmlmin'
+ ]
+ },
+ bower: {
+ options: {
+ exclude: ['modernizr']
+ },
+ all: {
+ rjsConfig: '<%= yeoman.app %>/scripts/main.js'
+ }
+ }
+ });
+
+ grunt.registerTask('server', function (target) {
+ if (target === 'dist') {
+ return grunt.task.run(['build', 'connect:dist:keepalive']);
+ }
+
+ grunt.task.run([
+ 'clean:server',
+ 'concurrent:server',
+ 'autoprefixer',
+ 'connect:livereload',
+ 'watch'
+ ]);
+ });
+
+ grunt.registerTask('test', [
+ 'clean:server',
+ 'concurrent:test',
+ 'autoprefixer',
+ 'connect:test',
+ 'mocha'
+ ]);
+
+ grunt.registerTask('build', [
+ 'clean:dist',
+ 'useminPrepare',
+ 'concurrent:dist',
+ 'autoprefixer',
+ 'concat',
+ 'cssmin',
+ 'uglify',
+ 'usemin',
+ 'copy:dist'
+ ]);
+
+ grunt.registerTask('default', [
+ 'jshint',
+ 'test',
+ 'build'
+ ]);
+};
diff --git a/dashboard/LICENSE b/dashboard/LICENSE
new file mode 100644
index 000000000..d64569567
--- /dev/null
+++ b/dashboard/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/dashboard/README.md b/dashboard/README.md
index a87b7eecb..45f27af95 100644
--- a/dashboard/README.md
+++ b/dashboard/README.md
@@ -1,5 +1,13 @@
-This directory holds the frontend for the etcd dashboard. To have etcd serve from this directory run:
+# etcd Dashboard
+
+## Developing
+
+### Install yeoman
+
+http://yeoman.io/
+
+### Hacking
```
-ETCD_DASHBOARD_DIR=`pwd`/dashboard ./etcd
+grunt server
```
diff --git a/dashboard/app/.buildignore b/dashboard/app/.buildignore
new file mode 100644
index 000000000..fc98b8eb5
--- /dev/null
+++ b/dashboard/app/.buildignore
@@ -0,0 +1 @@
+*.coffee
\ No newline at end of file
diff --git a/dashboard/app/browser.html b/dashboard/app/browser.html
new file mode 100644
index 000000000..27127833e
--- /dev/null
+++ b/dashboard/app/browser.html
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+ etcd Browser
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dashboard/app/index.html b/dashboard/app/index.html
new file mode 100644
index 000000000..e3995e6d1
--- /dev/null
+++ b/dashboard/app/index.html
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+
+
+ etcd dashboard
+
+
+
+
+
+
+
+ etcd Dashboard
+
+
+
+
+
diff --git a/dashboard/app/scripts/common/services/etcd.js b/dashboard/app/scripts/common/services/etcd.js
new file mode 100644
index 000000000..5a303bac8
--- /dev/null
+++ b/dashboard/app/scripts/common/services/etcd.js
@@ -0,0 +1,81 @@
+'use strict';
+
+angular.module('etcd', [])
+
+.factory('EtcdV1', ['$http', function($http) {
+ var keyPrefix = '/v1/keys/'
+ var statsPrefix = '/v1/stats/'
+ var baseURL = '/v1/'
+
+ delete $http.defaults.headers.common['X-Requested-With'];
+
+ function cleanupPath(path) {
+ var parts = path.split('/');
+ if (parts.length === 0) {
+ return '';
+ }
+ parts = parts.filter(function(v){return v!=='';});
+ return parts.join('/');
+ }
+
+ function newKey(keyName) {
+ var self = {};
+ self.name = cleanupPath(keyName);
+
+ self.getParent = function() {
+ var parts = self.name.split('/');
+ if (parts.length === 0) {
+ return newKey('');
+ }
+ parts.pop();
+ return newKey(parts.join('/'));
+ };
+
+ self.path = function() {
+ return '/' + cleanupPath(keyPrefix + self.name);
+ };
+
+ self.get = function() {
+ return $http.get(self.path());
+ };
+
+ self.set = function(keyValue) {
+ return $http({
+ url: self.path(),
+ data: $.param({value: keyValue}),
+ method: 'POST',
+ headers: {'Content-Type': 'application/x-www-form-urlencoded'}
+ });
+ };
+
+ self.deleteKey = function(keyValue) {
+ return $http({
+ url: self.path(),
+ method: 'DELETE',
+ headers: {'Content-Type': 'application/x-www-form-urlencoded'}
+ });
+ };
+
+ return self;
+ }
+
+ function newStat(statName) {
+ var self = {};
+ self.name = cleanupPath(statName);
+
+ self.path = function() {
+ return '/' + cleanupPath(statsPrefix + self.name);
+ };
+
+ self.get = function() {
+ return $http.get(self.path());
+ };
+
+ return self
+ }
+
+ return {
+ getStat: newStat,
+ getKey: newKey
+ }
+}]);
diff --git a/dashboard/app/scripts/controllers/browser.js b/dashboard/app/scripts/controllers/browser.js
new file mode 100644
index 000000000..ecb420222
--- /dev/null
+++ b/dashboard/app/scripts/controllers/browser.js
@@ -0,0 +1,191 @@
+'use strict';
+
+angular.module('etcdBrowser', ['ngRoute', 'etcd', 'timeRelative'])
+
+.constant('keyPrefix', '/v1/keys')
+
+.config(['$routeProvider', 'keyPrefix', function ($routeProvider, keyPrefix) {
+ //read localstorage
+ var previousPath = localStorage.getItem('etcd_path');
+
+ $routeProvider
+ .when('/', {
+ redirectTo: keyPrefix
+ })
+ .otherwise({
+ templateUrl: 'views/browser.html',
+ controller: 'MainCtrl'
+ });
+}])
+
+.controller('MainCtrl', ['$scope', '$location', 'EtcdV1', 'keyPrefix', function ($scope, $location, EtcdV1, keyPrefix) {
+ $scope.save = 'etcd-save-hide';
+ $scope.preview = 'etcd-preview-hide';
+ $scope.enableBack = true;
+ $scope.writingNew = false;
+
+ // etcdPath is the path to the key that is currenly being looked at.
+ $scope.etcdPath = $location.path();
+
+ $scope.$watch('etcdPath', function() {
+ function etcdPathKey() {
+ return pathKey($scope.etcdPath);
+ }
+
+ function pathKey(path) {
+ var parts = path.split(keyPrefix);
+ if (parts.length === 1) {
+ return '';
+ }
+ return parts[1];
+ }
+
+ // Notify everyone of the update
+ localStorage.setItem('etcdPath', $scope.etcdPath);
+ $scope.enableBack = true;
+ //disable back button if at root (/v1/keys/)
+ if($scope.etcdPath === '') {
+ $scope.enableBack = false;
+ }
+
+ $scope.key = EtcdV1.getKey(etcdPathKey($scope.etcdPath));
+ });
+
+ $scope.$watch('key', function() {
+ if ($scope.writingNew === true) {
+ return;
+ }
+ $scope.key.get().success(function (data, status, headers, config) {
+ //hide any errors
+ $('#etcd-browse-error').hide();
+ // Looking at a directory if we got an array
+ if (data.length) {
+ $scope.list = data;
+ $scope.preview = 'etcd-preview-hide';
+ } else {
+ $scope.singleValue = data.value;
+ $scope.preview = 'etcd-preview-reveal';
+ $scope.key.getParent().get().success(function(data) {
+ $scope.list = data;
+ });
+ }
+ $scope.previewMessage = 'No key selected.';
+ }).error(function (data, status, headers, config) {
+ $scope.previewMessage = 'Key does not exist.';
+ $scope.showBrowseError(data.message);
+ });
+ });
+
+ //back button click
+ $scope.back = function() {
+ $scope.etcdPath = $scope.key.getParent().path();
+ $scope.syncLocation();
+ $scope.preview = 'etcd-preview-hide';
+ $scope.writingNew = false;
+ };
+
+ $scope.syncLocation = function() {
+ $location.path($scope.etcdPath);
+ };
+
+ $scope.showSave = function() {
+ $scope.save = 'etcd-save-reveal';
+ };
+
+ $scope.saveData = function() {
+ // TODO: fixup etcd to allow for empty values
+ $scope.key.set($scope.singleValue || ' ').success(function (data, status, headers, config) {
+ $scope.save = 'etcd-save-hide';
+ $scope.preview = 'etcd-preview-hide';
+ $scope.back();
+ $scope.writingNew = false;
+ }).error(function (data, status, headers, config) {
+ $scope.showSaveError(data.message);
+ });
+ };
+
+ $scope.deleteKey = function() {
+ $scope.key.deleteKey().success(function (data, status, headers, config) {
+ //TODO: remove loader
+ $scope.save = 'etcd-save-hide';
+ $scope.preview = 'etcd-preview-hide';
+ $scope.back();
+ }).error(function (data, status, headers, config) {
+ //TODO: remove loader
+ //show errors
+ $scope.showBrowseError('Error: Could not delete the key');
+ });
+ };
+
+ $scope.add = function() {
+ $scope.save = 'etcd-save-reveal';
+ $scope.preview = 'etcd-preview-reveal';
+ $scope.singleValue = '';
+ $('.etcd-browser-path').find('input').focus();
+ $scope.writingNew = true;
+ };
+
+ $scope.showBrowseError = function(message) {
+ $('#etcd-browse-error').find('.etcd-popover-content').text('Error: ' + message);
+ $('#etcd-browse-error').addClass('etcd-popover-right').show();
+ };
+
+ $scope.showSaveError = function(message) {
+ $('#etcd-save-error').find('.etcd-popover-content').text('Error: ' + message);
+ $('#etcd-save-error').addClass('etcd-popover-left').show();
+ };
+
+ $scope.getHeight = function() {
+ return $(window).height();
+ };
+ $scope.$watch($scope.getHeight, function() {
+ $('.etcd-body').css('height', $scope.getHeight()-45);
+ });
+ window.onresize = function(){
+ $scope.$apply();
+ };
+
+}])
+
+.directive('ngEnter', function() {
+ return function(scope, element, attrs) {
+ element.bind('keydown keypress', function(event) {
+ if(event.which === 13) {
+ scope.$apply(function(){
+ scope.$eval(attrs.ngEnter);
+ });
+
+ event.preventDefault();
+ }
+ });
+ };
+})
+
+.directive('highlight', function() {
+ return {
+ restrict: 'A',
+ link: function(scope, element, attrs) {
+ if('#' + scope.etcdPath === attrs.href) {
+ element.parent().parent().addClass('etcd-selected');
+ }
+ }
+ };
+});
+
+moment.lang('en', {
+ relativeTime : {
+ future: 'Expires in %s',
+ past: 'Expired %s ago',
+ s: 'seconds',
+ m: 'a minute',
+ mm: '%d minutes',
+ h: 'an hour',
+ hh: '%d hours',
+ d: 'a day',
+ dd: '%d days',
+ M: 'a month',
+ MM: '%d months',
+ y: 'a year',
+ yy: '%d years'
+ }
+});
diff --git a/dashboard/app/scripts/controllers/stats.js b/dashboard/app/scripts/controllers/stats.js
new file mode 100644
index 000000000..e689147b0
--- /dev/null
+++ b/dashboard/app/scripts/controllers/stats.js
@@ -0,0 +1,181 @@
+'use strict';
+
+angular.module('etcdStats', ['ngRoute', 'etcd'])
+
+.config(['$routeProvider', function ($routeProvider) {
+ $routeProvider
+ .when('/', {
+ templateUrl: 'views/stats.html',
+ controller: 'StatsCtrl'
+ })
+ .otherwise({
+ templateUrl: 'views/stats.html',
+ controller: 'StatsCtrl'
+ });
+}])
+
+.controller('StatsCtrl', ['$scope', 'EtcdV1', 'statsVega', function ($scope, EtcdV1, statsVega) {
+ $scope.graphContainer = '#latency';
+ $scope.graphVisibility = 'etcd-graph-show';
+ $scope.tableVisibility = 'etcd-table-hide';
+
+ //make requests
+ function readStats() {
+ EtcdV1.getStat('leader').get().success(function(data) {
+ $scope.leaderStats = data;
+ $scope.followers = [];
+ $.each(data.followers, function(index, value) {
+ value.name = index;
+ $scope.followers.push(value);
+ });
+ drawGraph();
+ });
+ }
+
+ function drawGraph () {
+ //hardcoded padding from chart json
+ var vertPadding = 30;
+ var horzPadding = 15;
+ //fetch width and height of graph area
+ var width = $($scope.graphContainer).width() - horzPadding;
+ var height = $($scope.graphContainer).height() - vertPadding;
+
+ // parse a spec and create a visualization view
+ function parse(spec) {
+ vg.parse.spec(spec, function(chart) {
+ chart({
+ el: $scope.graphContainer,
+ data: {
+ 'stats': $scope.followers
+ }
+ }).width(width).height(height).update();
+ });
+ }
+ parse(statsVega);
+ }
+
+ $scope.showTable = function() {
+ $scope.tableVisibility = 'etcd-table-reveal';
+ };
+
+ $scope.showGraph = function() {
+ $scope.tableVisibility = 'etcd-table-hide';
+ };
+
+ $scope.getHeight = function() {
+ return $(window).height();
+ };
+ $scope.getWidth = function() {
+ return $(window).width();
+ };
+ $scope.$watch($scope.getHeight, function() {
+ $('.etcd-body').css('height', $scope.getHeight()-5);
+ readStats();
+ });
+ $scope.$watch($scope.getWidth, function() {
+ readStats();
+ });
+ window.onresize = function(){
+ $scope.$apply();
+ };
+
+ // Update the graphs live
+ setInterval(function() {
+ readStats();
+ $scope.$apply();
+ }, 500);
+}])
+
+
+/* statsVega returns the vega configuration for the stats dashboard */
+.factory('statsVega', function () {
+ return {
+ 'padding': {'top': 10, 'left': 5, 'bottom': 40, 'right': 10},
+ 'data': [
+ {
+ 'name': 'stats'
+ },
+ {
+ 'name': 'thresholds',
+ 'values': [50, 100]
+ }
+ ],
+ 'scales': [
+ {
+ 'name': 'y',
+ 'type': 'ordinal',
+ 'range': 'height',
+ 'domain': {'data': 'stats', 'field': 'index'}
+ },
+ {
+ 'name': 'x',
+ 'range': 'width',
+ 'domainMin': 0,
+ 'domainMax': 100,
+ 'nice': true,
+ 'zero': true,
+ 'domain': {'data': 'stats', 'field': 'data.latency.current'}
+ },
+ {
+ 'name': 'color',
+ 'type': 'linear',
+ 'domain': [10, 50, 100, 1000000000],
+ 'range': ['#00DB24', '#FFC000', '#c40022', '#c40022']
+ }
+ ],
+ 'axes': [
+ {
+ 'type': 'x',
+ 'scale': 'x',
+ 'ticks': 6,
+ 'name': 'Latency (ms)'
+ },
+ {
+ 'type': 'y',
+ 'scale': 'y',
+ 'properties': {
+ 'ticks': {
+ 'stroke': {'value': 'transparent'}
+ },
+ 'majorTicks': {
+ 'stroke': {'value': 'transparent'}
+ },
+ 'labels': {
+ 'fill': {'value': 'transparent'}
+ },
+ 'axis': {
+ 'stroke': {'value': '#333'},
+ 'strokeWidth': {'value': 1}
+ }
+ }
+ }
+ ],
+ 'marks': [
+ {
+ 'type': 'rect',
+ 'from': {'data': 'stats'},
+ 'properties': {
+ 'enter': {
+ 'x': {'scale': 'x', 'value': 0},
+ 'x2': {'scale': 'x', 'field': 'data.latency.current'},
+ 'y': {'scale': 'y', 'field': 'index', 'offset': -1},
+ 'height': {'value': 3},
+ 'fill': {'scale':'color', 'field':'data.latency.current'}
+ }
+ }
+ },
+ {
+ 'type': 'symbol',
+ 'from': {'data': 'stats'},
+ 'properties': {
+ 'enter': {
+ 'x': {'scale': 'x', 'field': 'data.latency.current'},
+ 'y': {'scale': 'y', 'field': 'index'},
+ 'size': {'value': 50},
+ 'fill': {'value': '#000'}
+ }
+ }
+ }
+ ]
+ };
+});
diff --git a/dashboard/app/scripts/ng-time-relative.min.js b/dashboard/app/scripts/ng-time-relative.min.js
new file mode 100644
index 000000000..3c09675bd
--- /dev/null
+++ b/dashboard/app/scripts/ng-time-relative.min.js
@@ -0,0 +1 @@
+(function(e){if("function"==typeof bootstrap)bootstrap("ng-time-relative",e);else if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else if("undefined"!=typeof ses){if(!ses.ok())return;ses.makeNgTimeRelative=e}else"undefined"!=typeof window?window.ngTimeRelative=e():global.ngTimeRelative=e()})(function(){var define,ses,bootstrap,module,exports;return function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0](function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s 1
+ ? function(x) { return s.reduce(function(x,f) { return x[f]; }, x); }
+ : function(x) { return x[f]; };
+};
+
+vg.comparator = function(sort) {
+ var sign = [];
+ if (sort === undefined) sort = [];
+ sort = vg.array(sort).map(function(f) {
+ var s = 1;
+ if (f[0] === "-") { s = -1; f = f.slice(1); }
+ else if (f[0] === "+") { s = +1; f = f.slice(1); }
+ sign.push(s);
+ return vg.accessor(f);
+ });
+ return function(a,b) {
+ var i, n, f, x, y;
+ for (i=0, n=sort.length; i y) return sign[i];
+ }
+ return 0;
+ };
+};
+
+vg.cmp = function(a, b) { return ab ? 1 : 0; };
+
+vg.numcmp = function(a, b) { return a - b; };
+
+vg.array = function(x) {
+ return x != null ? (vg.isArray(x) ? x : [x]) : [];
+};
+
+vg.values = function(x) {
+ return (vg.isObject(x) && !vg.isArray(x) && x.values) ? x.values : x;
+};
+
+vg.str = function(x) {
+ return vg.isArray(x) ? "[" + x.map(vg.str) + "]"
+ : vg.isObject(x) ? JSON.stringify(x)
+ : vg.isString(x) ? ("'"+vg_escape_str(x)+"'") : x;
+};
+
+var escape_str_re = /(^|[^\\])'/g;
+
+function vg_escape_str(x) {
+ return x.replace(escape_str_re, "$1\\'");
+}
+
+vg.keys = function(x) {
+ var keys = [];
+ for (var key in x) keys.push(key);
+ return keys;
+};
+
+vg.unique = function(data, f, results) {
+ if (!vg.isArray(data) || data.length==0) return [];
+ f = f || vg.identity;
+ results = results || [];
+ for (var v, i=0, n=data.length; i max) { max = v; idx = i; }
+ }
+ return idx;
+};
+
+vg.truncate = function(s, length, pos, word, ellipsis) {
+ var len = s.length;
+ if (len <= length) return s;
+ ellipsis = ellipsis || "...";
+ var l = Math.max(0, length - ellipsis.length);
+
+ switch (pos) {
+ case "left":
+ return ellipsis + (word ? vg_truncateOnWord(s,l,1) : s.slice(len-l));
+ case "middle":
+ case "center":
+ var l1 = Math.ceil(l/2), l2 = Math.floor(l/2);
+ return (word ? vg_truncateOnWord(s,l1) : s.slice(0,l1)) + ellipsis
+ + (word ? vg_truncateOnWord(s,l2,1) : s.slice(len-l2));
+ default:
+ return (word ? vg_truncateOnWord(s,l) : s.slice(0,l)) + ellipsis;
+ }
+}
+
+function vg_truncateOnWord(s, len, rev) {
+ var cnt = 0, tok = s.split(vg_truncate_word_re);
+ if (rev) {
+ s = (tok = tok.reverse())
+ .filter(function(w) { cnt += w.length; return cnt <= len; })
+ .reverse();
+ } else {
+ s = tok.filter(function(w) { cnt += w.length; return cnt <= len; });
+ }
+ return s.length ? s.join("").trim() : tok[0].slice(0, len);
+}
+
+var vg_truncate_word_re = /([\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u3000\uFEFF])/;
+
+// Logging
+
+function vg_write(msg) {
+ vg.config.isNode
+ ? process.stderr.write(msg + "\n")
+ : console.log(msg);
+}
+
+vg.log = function(msg) {
+ vg_write("[Vega Log] " + msg);
+};
+
+vg.error = function(msg) {
+ msg = "[Vega Err] " + msg;
+ vg_write(msg);
+ if (typeof alert !== "undefined") alert(msg);
+};vg.config = {};
+
+// are we running in node.js?
+// via timetler.com/2012/10/13/environment-detection-in-javascript/
+vg.config.isNode = typeof exports !== 'undefined' && this.exports !== exports;
+
+// base url for loading external data files
+// used only for server-side operation
+vg.config.baseURL = "";
+
+// version and namepsaces for exported svg
+vg.config.svgNamespace =
+ 'version="1.1" xmlns="http://www.w3.org/2000/svg" ' +
+ 'xmlns:xlink="http://www.w3.org/1999/xlink"';
+
+// inset padding for automatic padding calculation
+vg.config.autopadInset = 5;
+
+// extensible scale lookup table
+// all d3.scale.* instances also supported
+vg.config.scale = {
+ time: d3.time.scale,
+ utc: d3.time.scale.utc
+};
+
+// default rendering settings
+vg.config.render = {
+ lineWidth: 1,
+ lineCap: "butt",
+ font: "sans-serif",
+ fontSize: 11
+};
+
+// default axis properties
+vg.config.axis = {
+ orient: "bottom",
+ ticks: 10,
+ padding: 3,
+ axisColor: "#000",
+ gridColor: "#d8d8d8",
+ tickColor: "#000",
+ tickLabelColor: "#000",
+ axisWidth: 1,
+ tickWidth: 1,
+ tickSize: 6,
+ tickLabelFontSize: 11,
+ tickLabelFont: "sans-serif",
+ titleColor: "#000",
+ titleFont: "sans-serif",
+ titleFontSize: 11,
+ titleFontWeight: "bold",
+ titleOffset: 35
+};
+
+// default legend properties
+vg.config.legend = {
+ orient: "right",
+ offset: 10,
+ padding: 3,
+ gradientStrokeColor: "#888",
+ gradientStrokeWidth: 1,
+ gradientHeight: 16,
+ gradientWidth: 100,
+ labelColor: "#000",
+ labelFontSize: 10,
+ labelFont: "sans-serif",
+ labelAlign: "left",
+ labelBaseline: "middle",
+ labelOffset: 8,
+ symbolShape: "circle",
+ symbolSize: 50,
+ symbolColor: "#888",
+ symbolStrokeWidth: 1,
+ titleColor: "#000",
+ titleFont: "sans-serif",
+ titleFontSize: 11,
+ titleFontWeight: "bold"
+};
+
+// default color values
+vg.config.color = {
+ rgb: [128, 128, 128],
+ lab: [50, 0, 0],
+ hcl: [0, 0, 50],
+ hsl: [0, 0, 0.5]
+};
+
+// default scale ranges
+vg.config.range = {
+ category10: [
+ "#1f77b4",
+ "#ff7f0e",
+ "#2ca02c",
+ "#d62728",
+ "#9467bd",
+ "#8c564b",
+ "#e377c2",
+ "#7f7f7f",
+ "#bcbd22",
+ "#17becf"
+ ],
+ category20: [
+ "#1f77b4",
+ "#aec7e8",
+ "#ff7f0e",
+ "#ffbb78",
+ "#2ca02c",
+ "#98df8a",
+ "#d62728",
+ "#ff9896",
+ "#9467bd",
+ "#c5b0d5",
+ "#8c564b",
+ "#c49c94",
+ "#e377c2",
+ "#f7b6d2",
+ "#7f7f7f",
+ "#c7c7c7",
+ "#bcbd22",
+ "#dbdb8d",
+ "#17becf",
+ "#9edae5"
+ ],
+ shapes: [
+ "circle",
+ "cross",
+ "diamond",
+ "square",
+ "triangle-down",
+ "triangle-up"
+ ]
+};vg.Bounds = (function() {
+ var bounds = function(b) {
+ this.clear();
+ if (b) this.union(b);
+ };
+
+ var prototype = bounds.prototype;
+
+ prototype.clear = function() {
+ this.x1 = +Number.MAX_VALUE;
+ this.y1 = +Number.MAX_VALUE;
+ this.x2 = -Number.MAX_VALUE;
+ this.y2 = -Number.MAX_VALUE;
+ return this;
+ };
+
+ prototype.set = function(x1, y1, x2, y2) {
+ this.x1 = x1;
+ this.y1 = y1;
+ this.x2 = x2;
+ this.y2 = y2;
+ return this;
+ };
+
+ prototype.add = function(x, y) {
+ if (x < this.x1) this.x1 = x;
+ if (y < this.y1) this.y1 = y;
+ if (x > this.x2) this.x2 = x;
+ if (y > this.y2) this.y2 = y;
+ return this;
+ };
+
+ prototype.expand = function(d) {
+ this.x1 -= d;
+ this.y1 -= d;
+ this.x2 += d;
+ this.y2 += d;
+ return this;
+ };
+
+ prototype.round = function() {
+ this.x1 = Math.floor(this.x1);
+ this.y1 = Math.floor(this.y1);
+ this.x2 = Math.ceil(this.x2);
+ this.y2 = Math.ceil(this.y2);
+ return this;
+ };
+
+ prototype.translate = function(dx, dy) {
+ this.x1 += dx;
+ this.x2 += dx;
+ this.y1 += dy;
+ this.y2 += dy;
+ return this;
+ };
+
+ prototype.rotate = function(angle, x, y) {
+ var cos = Math.cos(angle),
+ sin = Math.sin(angle),
+ cx = x - x*cos + y*sin,
+ cy = y - x*sin - y*cos,
+ x1 = this.x1, x2 = this.x2,
+ y1 = this.y1, y2 = this.y2;
+
+ return this.clear()
+ .add(cos*x1 - sin*y1 + cx, sin*x1 + cos*y1 + cy)
+ .add(cos*x1 - sin*y2 + cx, sin*x1 + cos*y2 + cy)
+ .add(cos*x2 - sin*y1 + cx, sin*x2 + cos*y1 + cy)
+ .add(cos*x2 - sin*y2 + cx, sin*x2 + cos*y2 + cy);
+ }
+
+ prototype.union = function(b) {
+ if (b.x1 < this.x1) this.x1 = b.x1;
+ if (b.y1 < this.y1) this.y1 = b.y1;
+ if (b.x2 > this.x2) this.x2 = b.x2;
+ if (b.y2 > this.y2) this.y2 = b.y2;
+ return this;
+ };
+
+ prototype.encloses = function(b) {
+ return b && (
+ this.x1 <= b.x1 &&
+ this.x2 >= b.x2 &&
+ this.y1 <= b.y1 &&
+ this.y2 >= b.y2
+ );
+ };
+
+ prototype.intersects = function(b) {
+ return b && !(
+ this.x2 < b.x1 ||
+ this.x1 > b.x2 ||
+ this.y2 < b.y1 ||
+ this.y1 > b.y2
+ );
+ };
+
+ prototype.contains = function(x, y) {
+ return !(
+ x < this.x1 ||
+ x > this.x2 ||
+ y < this.y1 ||
+ y > this.y2
+ );
+ };
+
+ prototype.width = function() {
+ return this.x2 - this.x1;
+ };
+
+ prototype.height = function() {
+ return this.y2 - this.y1;
+ };
+
+ return bounds;
+})();vg.Gradient = (function() {
+
+ function gradient(type) {
+ this.id = "grad_" + (vg_gradient_id++);
+ this.type = type || "linear";
+ this.stops = [];
+ this.x1 = 0;
+ this.x2 = 1;
+ this.y1 = 0;
+ this.y2 = 0;
+ };
+
+ var prototype = gradient.prototype;
+
+ prototype.stop = function(offset, color) {
+ this.stops.push({
+ offset: offset,
+ color: color
+ });
+ return this;
+ };
+
+ return gradient;
+})();
+
+var vg_gradient_id = 0;vg.canvas = {};vg.canvas.path = (function() {
+
+ // Path parsing and rendering code taken from fabric.js -- Thanks!
+ var cmdLength = { m:2, l:2, h:1, v:1, c:6, s:4, q:4, t:2, a:7 },
+ re = [/([MLHVCSQTAZmlhvcsqtaz])/g, /###/, /(\d)-/g, /\s|,|###/];
+
+ function parse(path) {
+ var result = [],
+ currentPath,
+ chunks,
+ parsed;
+
+ // First, break path into command sequence
+ path = path.slice().replace(re[0], '###$1').split(re[1]).slice(1);
+
+ // Next, parse each command in turn
+ for (var i=0, j, chunksParsed, len=path.length; i commandLength) {
+ for (var k = 1, klen = chunksParsed.length; k < klen; k += commandLength) {
+ result.push([ chunksParsed[0] ].concat(chunksParsed.slice(k, k + commandLength)));
+ }
+ }
+ else {
+ result.push(chunksParsed);
+ }
+ }
+
+ return result;
+ }
+
+ function drawArc(g, x, y, coords, bounds, l, t) {
+ var rx = coords[0];
+ var ry = coords[1];
+ var rot = coords[2];
+ var large = coords[3];
+ var sweep = coords[4];
+ var ex = coords[5];
+ var ey = coords[6];
+ var segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y);
+ for (var i=0; i 1) {
+ pl = Math.sqrt(pl);
+ rx *= pl;
+ ry *= pl;
+ }
+
+ var a00 = cos_th / rx;
+ var a01 = sin_th / rx;
+ var a10 = (-sin_th) / ry;
+ var a11 = (cos_th) / ry;
+ var x0 = a00 * ox + a01 * oy;
+ var y0 = a10 * ox + a11 * oy;
+ var x1 = a00 * x + a01 * y;
+ var y1 = a10 * x + a11 * y;
+
+ var d = (x1-x0) * (x1-x0) + (y1-y0) * (y1-y0);
+ var sfactor_sq = 1 / d - 0.25;
+ if (sfactor_sq < 0) sfactor_sq = 0;
+ var sfactor = Math.sqrt(sfactor_sq);
+ if (sweep == large) sfactor = -sfactor;
+ var xc = 0.5 * (x0 + x1) - sfactor * (y1-y0);
+ var yc = 0.5 * (y0 + y1) + sfactor * (x1-x0);
+
+ var th0 = Math.atan2(y0-yc, x0-xc);
+ var th1 = Math.atan2(y1-yc, x1-xc);
+
+ var th_arc = th1-th0;
+ if (th_arc < 0 && sweep == 1){
+ th_arc += 2*Math.PI;
+ } else if (th_arc > 0 && sweep == 0) {
+ th_arc -= 2 * Math.PI;
+ }
+
+ var segments = Math.ceil(Math.abs(th_arc / (Math.PI * 0.5 + 0.001)));
+ var result = [];
+ for (var i=0; i 0) {
+ g.globalAlpha = opac * (o.strokeOpacity==null ? 1 : o.strokeOpacity);
+ g.strokeStyle = color(g, o, stroke);
+ g.lineWidth = lw;
+ g.lineCap = (lc = o.strokeCap) != null ? lc : vg.config.render.lineCap;
+ g.vgLineDash(o.strokeDash || null);
+ g.vgLineDashOffset(o.strokeDashOffset || 0);
+ g.stroke();
+ }
+ }
+ }
+
+ function drawPathAll(path, g, scene, bounds) {
+ var i, len, item;
+ for (i=0, len=scene.items.length; i 0) {
+ g.globalAlpha = opac * (o.strokeOpacity==null ? 1 : o.strokeOpacity);
+ g.strokeStyle = color(g, o, stroke);
+ g.lineWidth = lw;
+ g.lineCap = (lc = o.strokeCap) != null ? lc : vg.config.render.lineCap;
+ g.vgLineDash(o.strokeDash || null);
+ g.vgLineDashOffset(o.strokeDashOffset || 0);
+ g.strokeRect(x, y, w, h);
+ }
+ }
+ }
+ }
+
+ function drawRule(g, scene, bounds) {
+ if (!scene.items.length) return;
+ var items = scene.items,
+ o, stroke, opac, lc, lw, x1, y1, x2, y2;
+
+ for (var i=0, len=items.length; i 0) {
+ g.globalAlpha = opac * (o.strokeOpacity==null ? 1 : o.strokeOpacity);
+ g.strokeStyle = color(g, o, stroke);
+ g.lineWidth = lw;
+ g.lineCap = (lc = o.strokeCap) != null ? lc : vg.config.render.lineCap;
+ g.vgLineDash(o.strokeDash || null);
+ g.vgLineDashOffset(o.strokeDashOffset || 0);
+ g.beginPath();
+ g.moveTo(x1, y1);
+ g.lineTo(x2, y2);
+ g.stroke();
+ }
+ }
+ }
+ }
+
+ function drawImage(g, scene, bounds) {
+ if (!scene.items.length) return;
+ var renderer = this,
+ items = scene.items, o;
+
+ for (var i=0, len=items.length; i 0) {
+ g.globalAlpha = opac * (o.strokeOpacity==null ? 1 : o.strokeOpacity);
+ g.strokeStyle = color(o, stroke);
+ g.lineWidth = lw;
+ g.strokeText(o.text, x, y);
+ }
+ }
+
+ if (o.angle) g.restore();
+ }
+ }
+
+ function drawAll(pathFunc) {
+ return function(g, scene, bounds) {
+ drawPathAll(pathFunc, g, scene, bounds);
+ }
+ }
+
+ function drawOne(pathFunc) {
+ return function(g, scene, bounds) {
+ if (!scene.items.length) return;
+ if (bounds && !bounds.intersects(scene.items[0].bounds))
+ return; // bounds check
+ drawPathOne(pathFunc, g, scene.items[0], scene.items);
+ }
+ }
+
+ function drawGroup(g, scene, bounds) {
+ if (!scene.items.length) return;
+ var items = scene.items, group, axes, legends,
+ renderer = this, gx, gy, gb, i, n, j, m;
+
+ drawRect(g, scene, bounds);
+
+ for (i=0, n=items.length; i=0;) {
+ group = items[i];
+ dx = group.x || 0;
+ dy = group.y || 0;
+
+ g.save();
+ g.translate(dx, dy);
+ for (j=group.items.length; --j >= 0;) {
+ subscene = group.items[j];
+ if (subscene.interactive === false) continue;
+ hit = handler.pick(subscene, x, y, gx-dx, gy-dy);
+ if (hit) {
+ g.restore();
+ return hit;
+ }
+ }
+ g.restore();
+ }
+
+ return scene.interactive
+ ? pickAll(hitTests.rect, g, scene, x, y, gx, gy)
+ : false;
+ }
+
+ function pickAll(test, g, scene, x, y, gx, gy) {
+ if (!scene.items.length) return false;
+ var o, b, i;
+
+ if (g._ratio !== 1) {
+ x *= g._ratio;
+ y *= g._ratio;
+ }
+
+ for (i=scene.items.length; --i >= 0;) {
+ o = scene.items[i]; b = o.bounds;
+ // first hit test against bounding box
+ if ((b && !b.contains(gx, gy)) || !b) continue;
+ // if in bounding box, perform more careful test
+ if (test(g, o, x, y, gx, gy)) return o;
+ }
+ return false;
+ }
+
+ function pickArea(g, scene, x, y, gx, gy) {
+ if (!scene.items.length) return false;
+ var items = scene.items,
+ o, b, i, di, dd, od, dx, dy;
+
+ b = items[0].bounds;
+ if (b && !b.contains(gx, gy)) return false;
+ if (g._ratio !== 1) {
+ x *= g._ratio;
+ y *= g._ratio;
+ }
+ if (!hitTests.area(g, items, x, y)) return false;
+ return items[0];
+ }
+
+ function pickLine(g, scene, x, y, gx, gy) {
+ if (!scene.items.length) return false;
+ var items = scene.items,
+ o, b, i, di, dd, od, dx, dy;
+
+ b = items[0].bounds;
+ if (b && !b.contains(gx, gy)) return false;
+ if (g._ratio !== 1) {
+ x *= g._ratio;
+ y *= g._ratio;
+ }
+ if (!hitTests.line(g, items, x, y)) return false;
+ return items[0];
+ }
+
+ function pick(test) {
+ return function (g, scene, x, y, gx, gy) {
+ return pickAll(test, g, scene, x, y, gx, gy);
+ };
+ }
+
+ function textHit(g, o, x, y, gx, gy) {
+ if (!o.fontSize) return false;
+ if (!o.angle) return true; // bounds sufficient if no rotation
+
+ var b = vg.scene.bounds.text(o, tmpBounds, true),
+ a = -o.angle * Math.PI / 180,
+ cos = Math.cos(a),
+ sin = Math.sin(a),
+ x = o.x,
+ y = o.y,
+ px = cos*gx - sin*gy + (x - x*cos + y*sin),
+ py = sin*gx + cos*gy + (y - x*sin - y*cos);
+
+ return b.contains(px, py);
+ }
+
+ var hitTests = {
+ text: textHit,
+ rect: function(g,o,x,y) { return true; }, // bounds test is sufficient
+ image: function(g,o,x,y) { return true; }, // bounds test is sufficient
+ rule: function(g,o,x,y) {
+ if (!g.isPointInStroke) return false;
+ ruleStroke(g,o); return g.isPointInStroke(x,y);
+ },
+ line: function(g,s,x,y) {
+ if (!g.isPointInStroke) return false;
+ lineStroke(g,s); return g.isPointInStroke(x,y);
+ },
+ arc: function(g,o,x,y) { arcPath(g,o); return g.isPointInPath(x,y); },
+ area: function(g,s,x,y) { areaPath(g,s); return g.isPointInPath(x,y); },
+ path: function(g,o,x,y) { pathPath(g,o); return g.isPointInPath(x,y); },
+ symbol: function(g,o,x,y) { symbolPath(g,o); return g.isPointInPath(x,y); }
+ };
+
+ return {
+ draw: {
+ group: drawGroup,
+ area: drawOne(areaPath),
+ line: drawOne(linePath),
+ arc: drawAll(arcPath),
+ path: drawAll(pathPath),
+ symbol: drawAll(symbolPath),
+ rect: drawRect,
+ rule: drawRule,
+ text: drawText,
+ image: drawImage,
+ drawOne: drawOne, // expose for extensibility
+ drawAll: drawAll // expose for extensibility
+ },
+ pick: {
+ group: pickGroup,
+ area: pickArea,
+ line: pickLine,
+ arc: pick(hitTests.arc),
+ path: pick(hitTests.path),
+ symbol: pick(hitTests.symbol),
+ rect: pick(hitTests.rect),
+ rule: pick(hitTests.rule),
+ text: pick(hitTests.text),
+ image: pick(hitTests.image),
+ pickAll: pickAll // expose for extensibility
+ }
+ };
+
+})();vg.canvas.Renderer = (function() {
+ var renderer = function() {
+ this._ctx = null;
+ this._el = null;
+ this._imgload = 0;
+ };
+
+ var prototype = renderer.prototype;
+
+ prototype.initialize = function(el, width, height, pad) {
+ this._el = el;
+
+ if (!el) return this; // early exit if no DOM element
+
+ // select canvas element
+ var canvas = d3.select(el)
+ .selectAll("canvas.marks")
+ .data([1]);
+
+ // create new canvas element if needed
+ canvas.enter()
+ .append("canvas")
+ .attr("class", "marks");
+
+ // remove extraneous canvas if needed
+ canvas.exit().remove();
+
+ return this.resize(width, height, pad);
+ };
+
+ prototype.resize = function(width, height, pad) {
+ this._width = width;
+ this._height = height;
+ this._padding = pad;
+
+ if (this._el) {
+ var canvas = d3.select(this._el).select("canvas.marks");
+
+ // initialize canvas attributes
+ canvas
+ .attr("width", width + pad.left + pad.right)
+ .attr("height", height + pad.top + pad.bottom);
+
+ // get the canvas graphics context
+ var s;
+ this._ctx = canvas.node().getContext("2d");
+ this._ctx._ratio = (s = scaleCanvas(canvas.node(), this._ctx) || 1);
+ this._ctx.setTransform(s, 0, 0, s, s*pad.left, s*pad.top);
+ }
+
+ initializeLineDash(this._ctx);
+ return this;
+ };
+
+ function scaleCanvas(canvas, ctx) {
+ // get canvas pixel data
+ var devicePixelRatio = window.devicePixelRatio || 1,
+ backingStoreRatio = (
+ ctx.webkitBackingStorePixelRatio ||
+ ctx.mozBackingStorePixelRatio ||
+ ctx.msBackingStorePixelRatio ||
+ ctx.oBackingStorePixelRatio ||
+ ctx.backingStorePixelRatio) || 1,
+ ratio = devicePixelRatio / backingStoreRatio;
+
+ if (devicePixelRatio !== backingStoreRatio) {
+ var w = canvas.width, h = canvas.height;
+ // set actual and visible canvas size
+ canvas.setAttribute("width", w * ratio);
+ canvas.setAttribute("height", h * ratio);
+ canvas.style.width = w + 'px';
+ canvas.style.height = h + 'px';
+ }
+ return ratio;
+ }
+
+ function initializeLineDash(ctx) {
+ if (ctx.vgLineDash) return; // already set
+
+ var NODASH = [];
+ if (ctx.setLineDash) {
+ ctx.vgLineDash = function(dash) { this.setLineDash(dash || NODASH); };
+ ctx.vgLineDashOffset = function(off) { this.lineDashOffset = off; };
+ } else if (ctx.webkitLineDash !== undefined) {
+ ctx.vgLineDash = function(dash) { this.webkitLineDash = dash || NODASH; };
+ ctx.vgLineDashOffset = function(off) { this.webkitLineDashOffset = off; };
+ } else if (ctx.mozDash !== undefined) {
+ ctx.vgLineDash = function(dash) { this.mozDash = dash; };
+ ctx.vgLineDashOffset = function(off) { /* unsupported */ };
+ } else {
+ ctx.vgLineDash = function(dash) { /* unsupported */ };
+ ctx.vgLineDashOffset = function(off) { /* unsupported */ };
+ }
+ }
+
+ prototype.context = function(ctx) {
+ if (ctx) { this._ctx = ctx; return this; }
+ else return this._ctx;
+ };
+
+ prototype.element = function() {
+ return this._el;
+ };
+
+ prototype.pendingImages = function() {
+ return this._imgload;
+ };
+
+ function translatedBounds(item, bounds) {
+ var b = new vg.Bounds(bounds);
+ while ((item = item.mark.group) != null) {
+ b.translate(item.x || 0, item.y || 0);
+ }
+ return b;
+ }
+
+ function getBounds(items) {
+ return !items ? null :
+ vg.array(items).reduce(function(b, item) {
+ return b.union(translatedBounds(item, item.bounds))
+ .union(translatedBounds(item, item['bounds:prev']));
+ }, new vg.Bounds());
+ }
+
+ function setBounds(g, bounds) {
+ var bbox = null;
+ if (bounds) {
+ bbox = (new vg.Bounds(bounds)).round();
+ g.beginPath();
+ g.rect(bbox.x1, bbox.y1, bbox.width(), bbox.height());
+ g.clip();
+ }
+ return bbox;
+ }
+
+ prototype.render = function(scene, items) {
+ var g = this._ctx,
+ pad = this._padding,
+ w = this._width + pad.left + pad.right,
+ h = this._height + pad.top + pad.bottom,
+ bb = null, bb2;
+
+ // setup
+ this._scene = scene;
+ g.save();
+ bb = setBounds(g, getBounds(items));
+ g.clearRect(-pad.left, -pad.top, w, h);
+
+ // render
+ this.draw(g, scene, bb);
+
+ // render again to handle possible bounds change
+ if (items) {
+ g.restore();
+ g.save();
+ bb2 = setBounds(g, getBounds(items));
+ if (!bb.encloses(bb2)) {
+ g.clearRect(-pad.left, -pad.top, w, h);
+ this.draw(g, scene, bb2);
+ }
+ }
+
+ // takedown
+ g.restore();
+ this._scene = null;
+ };
+
+ prototype.draw = function(ctx, scene, bounds) {
+ var marktype = scene.marktype,
+ renderer = vg.canvas.marks.draw[marktype];
+ renderer.call(this, ctx, scene, bounds);
+
+ // compute mark-level bounds
+ scene.bounds = scene.items.reduce(function(b, item) {
+ return item.bounds ? b.union(item.bounds) : b;
+ }, scene.bounds || new vg.Bounds());
+ };
+
+ prototype.renderAsync = function(scene) {
+ // TODO make safe for multiple scene rendering?
+ var renderer = this;
+ if (renderer._async_id) {
+ clearTimeout(renderer._async_id);
+ }
+ renderer._async_id = setTimeout(function() {
+ renderer.render(scene);
+ delete renderer._async_id;
+ }, 50);
+ };
+
+ prototype.loadImage = function(uri) {
+ var renderer = this,
+ scene = renderer._scene,
+ image = null, url;
+
+ renderer._imgload += 1;
+ if (vg.config.isNode) {
+ image = new (require("canvas").Image)();
+ vg.data.load(uri, function(err, data) {
+ if (err) { vg.error(err); return; }
+ image.src = data;
+ image.loaded = true;
+ renderer._imgload -= 1;
+ });
+ } else {
+ image = new Image();
+ url = vg.config.baseURL + uri;
+ image.onload = function() {
+ vg.log("LOAD IMAGE: "+url);
+ image.loaded = true;
+ renderer._imgload -= 1;
+ renderer.renderAsync(scene);
+ };
+ image.src = url;
+ }
+
+ return image;
+ };
+
+ return renderer;
+})();vg.canvas.Handler = (function() {
+ var handler = function(el, model) {
+ this._active = null;
+ this._handlers = {};
+ if (el) this.initialize(el);
+ if (model) this.model(model);
+ };
+
+ var prototype = handler.prototype;
+
+ prototype.initialize = function(el, pad, obj) {
+ this._el = d3.select(el).node();
+ this._canvas = d3.select(el).select("canvas.marks").node();
+ this._padding = pad;
+ this._obj = obj || null;
+
+ // add event listeners
+ var canvas = this._canvas, that = this;
+ events.forEach(function(type) {
+ canvas.addEventListener(type, function(evt) {
+ prototype[type].call(that, evt);
+ });
+ });
+
+ return this;
+ };
+
+ prototype.padding = function(pad) {
+ this._padding = pad;
+ return this;
+ };
+
+ prototype.model = function(model) {
+ if (!arguments.length) return this._model;
+ this._model = model;
+ return this;
+ };
+
+ prototype.handlers = function() {
+ var h = this._handlers;
+ return vg.keys(h).reduce(function(a, k) {
+ return h[k].reduce(function(a, x) { return (a.push(x), a); }, a);
+ }, []);
+ };
+
+ // setup events
+ var events = [
+ "mousedown",
+ "mouseup",
+ "click",
+ "dblclick",
+ "wheel",
+ "keydown",
+ "keypress",
+ "keyup",
+ "mousewheel"
+ ];
+ events.forEach(function(type) {
+ prototype[type] = function(evt) {
+ this.fire(type, evt);
+ };
+ });
+ events.push("mousemove");
+ events.push("mouseout");
+
+ function eventName(name) {
+ var i = name.indexOf(".");
+ return i < 0 ? name : name.slice(0,i);
+ }
+
+ prototype.mousemove = function(evt) {
+ var pad = this._padding,
+ b = evt.target.getBoundingClientRect(),
+ x = evt.clientX - b.left,
+ y = evt.clientY - b.top,
+ a = this._active,
+ p = this.pick(this._model.scene(), x, y, x-pad.left, y-pad.top);
+
+ if (p === a) {
+ this.fire("mousemove", evt);
+ return;
+ } else if (a) {
+ this.fire("mouseout", evt);
+ }
+ this._active = p;
+ if (p) {
+ this.fire("mouseover", evt);
+ }
+ };
+
+ prototype.mouseout = function(evt) {
+ if (this._active) {
+ this.fire("mouseout", evt);
+ }
+ this._active = null;
+ };
+
+ // to keep firefox happy
+ prototype.DOMMouseScroll = function(evt) {
+ this.fire("mousewheel", evt);
+ };
+
+ // fire an event
+ prototype.fire = function(type, evt) {
+ var a = this._active,
+ h = this._handlers[type];
+ if (a && h) {
+ for (var i=0, len=h.length; i=0;) {
+ if (h[i].type !== type) continue;
+ if (!handler || h[i].handler === handler) h.splice(i, 1);
+ }
+ return this;
+ };
+
+ // retrieve the current canvas context
+ prototype.context = function() {
+ return this._canvas.getContext("2d");
+ };
+
+ // find the scenegraph item at the current mouse position
+ // returns an array of scenegraph items, from leaf node up to the root
+ // x, y -- the absolute x, y mouse coordinates on the canvas element
+ // gx, gy -- the relative coordinates within the current group
+ prototype.pick = function(scene, x, y, gx, gy) {
+ var g = this.context(),
+ marktype = scene.marktype,
+ picker = vg.canvas.marks.pick[marktype];
+ return picker.call(this, g, scene, x, y, gx, gy);
+ };
+
+ return handler;
+})();vg.svg = {};vg.svg.marks = (function() {
+
+ function x(o) { return o.x || 0; }
+ function y(o) { return o.y || 0; }
+ function yh(o) { return o.y + o.height || 0; }
+ function key(o) { return o.key; }
+ function size(o) { return o.size==null ? 100 : o.size; }
+ function shape(o) { return o.shape || "circle"; }
+
+ var arc_path = d3.svg.arc(),
+ area_path = d3.svg.area().x(x).y1(y).y0(yh),
+ line_path = d3.svg.line().x(x).y(y),
+ symbol_path = d3.svg.symbol().type(shape).size(size);
+
+ var mark_id = 0;
+
+ var textAlign = {
+ "left": "start",
+ "center": "middle",
+ "right": "end"
+ };
+
+ var styles = {
+ "fill": "fill",
+ "fillOpacity": "fill-opacity",
+ "stroke": "stroke",
+ "strokeWidth": "stroke-width",
+ "strokeOpacity": "stroke-opacity",
+ "strokeCap": "stroke-linecap",
+ "strokeDash": "stroke-dasharray",
+ "strokeDashOffset": "stroke-dashoffset",
+ "opacity": "opacity"
+ };
+ var styleProps = vg.keys(styles);
+
+ function style(d) {
+ var i, n, prop, name, value,
+ o = d.mark ? d : d.length ? d[0] : null;
+ if (o === null) return;
+
+ for (i=0, n=styleProps.length; i " + tag,
+ m = p.selectAll(s).data(data),
+ e = m.enter().append(tag);
+
+ if (notG) {
+ p.style("pointer-events", evts);
+ e.each(function(d) {
+ if (d.mark) d._svg = this;
+ else if (d.length) d[0]._svg = this;
+ });
+ } else {
+ e.append("rect").attr("class","background").style("pointer-events",evts);
+ }
+
+ m.exit().remove();
+ m.each(attr);
+ if (notG) m.each(style);
+ else p.selectAll(s+" > rect.background").each(group_bg).each(style);
+
+ return p;
+ }
+
+ function drawGroup(g, scene, index, prefix) {
+ var p = drawMark(g, scene, index, prefix || "group_", "g", group),
+ c = p.node().childNodes, n = c.length, i, j, m;
+
+ for (i=0; i=0;) {
+ if (h[i].type !== type) continue;
+ if (!handler || h[i].handler === handler) {
+ dom.removeEventListener(name, h[i].svg);
+ h.splice(i, 1);
+ }
+ }
+ return this;
+ };
+
+ return handler;
+})();vg.data = {};
+
+vg.data.ingestAll = function(data) {
+ return vg.isTree(data)
+ ? vg_make_tree(vg.data.ingestTree(data[0], data.children))
+ : data.map(vg.data.ingest);
+};
+
+vg.data.ingest = function(datum, index) {
+ return {
+ data: datum,
+ index: index
+ };
+};
+
+vg.data.ingestTree = function(node, children) {
+ var d = vg.data.ingest(node),
+ c = node[children], n, i;
+ if (c && (n = c.length)) {
+ d.values = Array(n);
+ for (i=0; i= 0) file = file.slice(vg_load_fileProtocol.length);
+ require("fs").readFile(file, callback);
+}
+
+function vg_load_http(url, callback) {
+ vg.log("LOAD HTTP: " + url);
+ var req = require("http").request(url, function(res) {
+ var pos=0, data = new Buffer(parseInt(res.headers['content-length'],10));
+ res.on("error", function(err) { callback(err, null); });
+ res.on("data", function(x) { x.copy(data, pos); pos += x.length; });
+ res.on("end", function() { callback(null, data); });
+ });
+ req.on("error", function(err) { callback(err); });
+ req.end();
+}vg.data.read = (function() {
+ var formats = {},
+ parsers = {
+ "number": vg.number,
+ "boolean": vg.boolean,
+ "date": Date.parse
+ };
+
+ function read(data, format) {
+ var type = (format && format.type) || "json";
+ data = formats[type](data, format);
+ if (format && format.parse) parseValues(data, format.parse);
+ return data;
+ }
+
+ formats.json = function(data, format) {
+ var d = JSON.parse(data);
+ if (format && format.property) {
+ d = vg.accessor(format.property)(d);
+ }
+ return d;
+ };
+
+ formats.csv = function(data, format) {
+ var d = d3.csv.parse(data);
+ return d;
+ };
+
+ formats.tsv = function(data, format) {
+ var d = d3.tsv.parse(data);
+ return d;
+ };
+
+ formats.topojson = function(data, format) {
+ if (topojson == null) {
+ vg.error("TopoJSON library not loaded.");
+ return [];
+ }
+ var t = JSON.parse(data), obj = [];
+
+ if (format && format.feature) {
+ obj = (obj = t.objects[format.feature])
+ ? topojson.feature(t, obj).features
+ : (vg.error("Invalid TopoJSON object: "+format.feature), []);
+ } else if (format && format.mesh) {
+ obj = (obj = t.objects[format.mesh])
+ ? [topojson.mesh(t, t.objects[format.mesh])]
+ : (vg.error("Invalid TopoJSON object: " + format.mesh), []);
+ }
+ else { vg.error("Missing TopoJSON feature or mesh parameter."); }
+
+ return obj;
+ };
+
+ formats.treejson = function(data, format) {
+ var d = [JSON.parse(data)];
+ d.__vgtree__ = true;
+ d.children = format.children || "children";
+ return d;
+ };
+
+ function parseValues(data, types) {
+ var cols = vg.keys(types),
+ p = cols.map(function(col) { return parsers[types[col]]; }),
+ tree = vg.isTree(data);
+ vg_parseArray(tree ? [data] : data, cols, p, tree);
+ }
+
+ function vg_parseArray(data, cols, p, tree) {
+ var d, i, j, len, clen;
+ for (i=0, len=data.length; i0 ? "|" : "") + String(kv);
+ }
+ obj = map[kstr];
+ if (obj === undefined) {
+ vals.push(obj = map[kstr] = {
+ key: kstr,
+ keys: klist,
+ index: vals.length,
+ values: []
+ });
+ }
+ obj.values.push(data[i]);
+ }
+
+ if (sort) {
+ for (i=0, len=vals.length; i b ? 1 : 0;
+ });
+ data = [data[~~(list.length/2)]];
+ } else {
+ var idx = vg.array(by);
+ data = data.slice(idx[0], idx[1]);
+ }
+ return data;
+ }
+
+ slice.by = function(x) {
+ by = x;
+ return slice;
+ };
+
+ slice.field = function(f) {
+ field = vg.accessor(f);
+ return slice;
+ };
+
+ return slice;
+};vg.data.sort = function() {
+ var by = null;
+
+ function sort(data) {
+ data = (vg.isArray(data) ? data : data.values || []);
+ data.sort(by);
+ for (var i=0, n=data.length; ib.x ? 1 : (a.zb.z ? 1 : 0);
+ });
+
+ // emit data series for stack layout
+ for (x=points[0].x, i=0, j=0, k=0, n=points.length; k i) series[i++].push({x:j, y:0});
+ p.x = j;
+ series[i++].push(p);
+ }
+ while (i < series.length) series[i++].push({x:j, y:0});
+
+ return series;
+ }
+
+ stack.point = function(field) {
+ point = vg.accessor(field);
+ return stack;
+ };
+
+ stack.height = function(field) {
+ height = vg.accessor(field);
+ return stack;
+ };
+
+ params.forEach(function(name) {
+ stack[name] = function(x) {
+ layout[name](x);
+ return stack;
+ }
+ });
+
+ stack.output = function(map) {
+ d3.keys(output).forEach(function(k) {
+ if (map[k] !== undefined) {
+ output[k] = map[k];
+ }
+ });
+ return stack;
+ };
+
+ return stack;
+};vg.data.stats = function() {
+ var value = vg.accessor("data"),
+ assign = false,
+ median = false,
+ output = {
+ "count": "count",
+ "min": "min",
+ "max": "max",
+ "sum": "sum",
+ "mean": "mean",
+ "variance": "variance",
+ "stdev": "stdev",
+ "median": "median"
+ };
+
+ function reduce(data) {
+ var min = +Infinity,
+ max = -Infinity,
+ sum = 0,
+ mean = 0,
+ M2 = 0,
+ i, len, v, delta;
+
+ var list = (vg.isArray(data) ? data : data.values || []).map(value);
+
+ // compute aggregates
+ for (i=0, len=list.length; i max) max = v;
+ sum += v;
+ delta = v - mean;
+ mean = mean + delta / (i+1);
+ M2 = M2 + delta * (v - mean);
+ }
+ M2 = M2 / (len - 1);
+
+ var o = vg.isArray(data) ? {} : data;
+ if (median) {
+ list.sort(vg.numcmp);
+ i = list.length >> 1;
+ o[output.median] = list.length % 2
+ ? list[i]
+ : (list[i-1] + list[i])/2;
+ }
+ o[output.count] = len;
+ o[output.min] = min;
+ o[output.max] = max;
+ o[output.sum] = sum;
+ o[output.mean] = mean;
+ o[output.variance] = M2;
+ o[output.stdev] = Math.sqrt(M2);
+
+ if (assign) {
+ list = (vg.isArray(data) ? data : data.values);
+ v = {};
+ v[output.count] = len;
+ v[output.min] = min;
+ v[output.max] = max;
+ v[output.sum] = sum;
+ v[output.mean] = mean;
+ v[output.variance] = M2;
+ v[output.stdev] = Math.sqrt(M2);
+ if (median) v[output.median] = o[output.median];
+ for (i=0, len=list.length; i\~\&\|\?\:\+\-\/\*\%\!\^\,\;\[\]\{\}\(\) ]+)/;
+
+ return function(x) {
+ var tokens = x.split(lexer),
+ t, v, i, n, sq, dq;
+
+ for (sq=0, dq=0, i=0, n=tokens.length; i 0) ? "\n " : " ";
+ code += "o."+name+" = "+valueRef(name, ref)+";";
+ vars[name] = true;
+ }
+
+ if (vars.x2) {
+ if (vars.x) {
+ code += "\n if (o.x > o.x2) { "
+ + "var t = o.x; o.x = o.x2; o.x2 = t; };";
+ code += "\n o.width = (o.x2 - o.x);";
+ } else if (vars.width && !vars.x1) {
+ code += "\n o.x = (o.x2 - o.width);";
+ }
+ }
+
+ if (vars.y2) {
+ if (vars.y) {
+ code += "\n if (o.y > o.y2) { "
+ + "var t = o.y; o.y = o.y2; o.y2 = t; };";
+ code += "\n o.height = (o.y2 - o.y);";
+ } else if (vars.height && !vars.y1) {
+ code += "\n o.y = (o.y2 - o.height);";
+ }
+ }
+
+ if (hasPath(mark, vars)) {
+ code += "\n if (o['path:parsed']) o['path:parsed'] = null;"
+ }
+ code += "\n if (trans) trans.interpolate(item, o);";
+
+ try {
+ return Function("item", "group", "trans", code);
+ } catch (e) {
+ vg.error(e);
+ vg.log(code);
+ }
+ }
+
+ function hasPath(mark, vars) {
+ return vars.path ||
+ ((mark==="area" || mark==="line") &&
+ (vars.x || vars.x2 || vars.width ||
+ vars.y || vars.y2 || vars.height ||
+ vars.tension || vars.interpolate));
+ }
+
+ var GROUP_VARS = {
+ "width": 1,
+ "height": 1,
+ "mark.group.width": 1,
+ "mark.group.height": 1
+ };
+
+ function valueRef(name, ref) {
+ if (ref == null) return null;
+ var isColor = name==="fill" || name==="stroke";
+
+ if (isColor) {
+ if (ref.c) {
+ return colorRef("hcl", ref.h, ref.c, ref.l);
+ } else if (ref.h || ref.s) {
+ return colorRef("hsl", ref.h, ref.s, ref.l);
+ } else if (ref.l || ref.a) {
+ return colorRef("lab", ref.l, ref.a, ref.b);
+ } else if (ref.r || ref.g || ref.b) {
+ return colorRef("rgb", ref.r, ref.g, ref.b);
+ }
+ }
+
+ // initialize value
+ var val = "item.datum.data";
+ if (ref.value !== undefined) {
+ val = vg.str(ref.value);
+ }
+
+ // get field reference for enclosing group
+ if (ref.group != null) {
+ var grp = "";
+ if (vg.isString(ref.group)) {
+ grp = GROUP_VARS[ref.group]
+ ? "group." + ref.group
+ : "group.datum["+vg.field(ref.group).map(vg.str).join("][")+"]";
+ }
+ }
+
+ // get data field value
+ if (ref.field != null) {
+ if (vg.isString(ref.field)) {
+ val = "item.datum["+vg.field(ref.field).map(vg.str).join("][")+"]";
+ if (ref.group != null) { val = grp+"["+val+"]"; }
+ } else {
+ val = "this.accessor(group.datum["
+ + vg.field(ref.field.group).map(vg.str).join("][")
+ + "])(item.datum.data)";
+ }
+ } else if (ref.group != null) {
+ val = grp;
+ }
+
+ // run through scale function
+ if (ref.scale != null) {
+ var scale = vg.isString(ref.scale)
+ ? vg.str(ref.scale)
+ : (ref.scale.group ? "group" : "item")
+ + ".datum[" + vg.str(ref.scale.group || ref.scale.field) + "]";
+ scale = "group.scales[" + scale + "]";
+ val = scale + (ref.band ? ".rangeBand()" : "("+val+")");
+ }
+
+ // multiply, offset, return value
+ val = "(" + (ref.mult?(vg.number(ref.mult)+" * "):"") + val + ")"
+ + (ref.offset ? " + " + vg.number(ref.offset) : "");
+ if (isColor) val = '('+val+')+""';
+ return val;
+ }
+
+ function colorRef(type, x, y, z) {
+ var xx = x ? valueRef("", x) : vg.config.color[type][0],
+ yy = y ? valueRef("", y) : vg.config.color[type][1],
+ zz = z ? valueRef("", z) : vg.config.color[type][2];
+ return "(this.d3." + type + "(" + [xx,yy,zz].join(",") + ') + "")';
+ }
+
+ return compile;
+})();vg.parse.scales = (function() {
+ var LINEAR = "linear",
+ ORDINAL = "ordinal",
+ LOG = "log",
+ POWER = "pow",
+ TIME = "time",
+ GROUP_PROPERTY = {width: 1, height: 1};
+
+ function scales(spec, scales, db, group) {
+ return (spec || []).reduce(function(o, def) {
+ var name = def.name, prev = name + ":prev";
+ o[name] = scale(def, o[name], db, group);
+ o[prev] = o[prev] || o[name];
+ return o;
+ }, scales || {});
+ }
+
+ function scale(def, scale, db, group) {
+ var s = instance(def, scale),
+ m = s.type===ORDINAL ? ordinal : quantitative,
+ rng = range(def, group),
+ data = vg.values(group.datum);
+
+ m(def, s, rng, db, data);
+ return s;
+ }
+
+ function instance(def, scale) {
+ var type = def.type || LINEAR;
+ if (!scale || type !== scale.type) {
+ var ctor = vg.config.scale[type] || d3.scale[type];
+ if (!ctor) vg.error("Unrecognized scale type: " + type);
+ (scale = ctor()).type = scale.type || type;
+ scale.scaleName = def.name;
+ }
+ return scale;
+ }
+
+ function ordinal(def, scale, rng, db, data) {
+ var domain, refs, values, str;
+
+ // domain
+ domain = def.domain;
+ if (vg.isArray(domain)) {
+ scale.domain(domain);
+ } else if (vg.isObject(domain)) {
+ refs = def.domain.fields || vg.array(def.domain);
+ values = refs.reduce(function(values, r) {
+ var dat = vg.values(db[r.data] || data),
+ get = vg.accessor(vg.isString(r.field)
+ ? r.field : "data." + vg.accessor(r.field.group)(data));
+ return vg.unique(dat, get, values);
+ }, []);
+ if (def.sort) values.sort(vg.cmp);
+ scale.domain(values);
+ }
+
+ // range
+ str = typeof rng[0] === 'string';
+ if (str || rng.length > 2) {
+ scale.range(rng); // color or shape values
+ } else if (def.points) {
+ scale.rangePoints(rng, def.padding||0);
+ } else if (def.round || def.round===undefined) {
+ scale.rangeRoundBands(rng, def.padding||0);
+ } else {
+ scale.rangeBands(rng, def.padding||0);
+ }
+ }
+
+ function quantitative(def, scale, rng, db, data) {
+ var domain, refs, interval, z;
+
+ // domain
+ domain = [null, null];
+ function extract(ref, min, max, z) {
+ var dat = vg.values(db[ref.data] || data);
+ var fields = vg.array(ref.field).map(function(f) {
+ return vg.isString(f) ? f
+ : "data." + vg.accessor(f.group)(data);
+ });
+
+ fields.forEach(function(f,i) {
+ f = vg.accessor(f);
+ if (min) domain[0] = d3.min([domain[0], d3.min(dat, f)]);
+ if (max) domain[z] = d3.max([domain[z], d3.max(dat, f)]);
+ });
+ }
+ if (def.domain !== undefined) {
+ if (vg.isArray(def.domain)) {
+ domain = def.domain.slice();
+ } else if (vg.isObject(def.domain)) {
+ refs = def.domain.fields || vg.array(def.domain);
+ refs.forEach(function(r) { extract(r,1,1,1); });
+ } else {
+ domain = def.domain;
+ }
+ }
+ z = domain.length - 1;
+ if (def.domainMin !== undefined) {
+ if (vg.isObject(def.domainMin)) {
+ domain[0] = null;
+ refs = def.domainMin.fields || vg.array(def.domainMin);
+ refs.forEach(function(r) { extract(r,1,0,z); });
+ } else {
+ domain[0] = def.domainMin;
+ }
+ }
+ if (def.domainMax !== undefined) {
+ if (vg.isObject(def.domainMax)) {
+ domain[z] = null;
+ refs = def.domainMax.fields || vg.array(def.domainMax);
+ refs.forEach(function(r) { extract(r,0,1,z); });
+ } else {
+ domain[z] = def.domainMax;
+ }
+ }
+ if (def.type !== LOG && def.type !== TIME && (def.zero || def.zero===undefined)) {
+ domain[0] = Math.min(0, domain[0]);
+ domain[z] = Math.max(0, domain[z]);
+ }
+ scale.domain(domain);
+
+ // range
+ // vertical scales should flip by default, so use XOR here
+ if (def.range === "height") rng = rng.reverse();
+ scale[def.round && scale.rangeRound ? "rangeRound" : "range"](rng);
+
+ if (def.exponent && def.type===POWER) scale.exponent(def.exponent);
+ if (def.clamp) scale.clamp(true);
+ if (def.nice) {
+ if (def.type === TIME) {
+ interval = d3.time[def.nice];
+ if (!interval) vg.error("Unrecognized interval: " + interval);
+ scale.nice(interval);
+ } else {
+ scale.nice();
+ }
+ }
+ }
+
+ function range(def, group) {
+ var rng = [null, null];
+
+ if (def.range !== undefined) {
+ if (typeof def.range === 'string') {
+ if (GROUP_PROPERTY[def.range]) {
+ rng = [0, group[def.range]];
+ } else if (vg.config.range[def.range]) {
+ rng = vg.config.range[def.range];
+ } else {
+ vg.error("Unrecogized range: "+def.range);
+ return rng;
+ }
+ } else if (vg.isArray(def.range)) {
+ rng = def.range;
+ } else {
+ rng = [0, def.range];
+ }
+ }
+ if (def.rangeMin !== undefined) {
+ rng[0] = def.rangeMin;
+ }
+ if (def.rangeMax !== undefined) {
+ rng[rng.length-1] = def.rangeMax;
+ }
+
+ if (def.reverse !== undefined) {
+ var rev = def.reverse;
+ if (vg.isObject(rev)) {
+ rev = vg.accessor(rev.field)(group.datum);
+ }
+ if (rev) rng = rng.reverse();
+ }
+
+ return rng;
+ }
+
+ return scales;
+})();
+vg.parse.spec = function(spec, callback, viewFactory) {
+
+ viewFactory = viewFactory || vg.ViewFactory;
+
+ function parse(spec) {
+ // protect against subsequent spec modification
+ spec = vg.duplicate(spec);
+
+ var width = spec.width || 500,
+ height = spec.height || 500,
+ viewport = spec.viewport || null;
+
+ var defs = {
+ width: width,
+ height: height,
+ viewport: viewport,
+ padding: vg.parse.padding(spec.padding),
+ marks: vg.parse.marks(spec, width, height),
+ data: vg.parse.data(spec.data, function() { callback(viewConstructor); })
+ };
+
+ var viewConstructor = viewFactory(defs);
+ }
+
+ vg.isObject(spec) ? parse(spec) :
+ d3.json(spec, function(error, json) {
+ error ? vg.error(error) : parse(json);
+ });
+};vg.parse.transform = function(def) {
+ var tx = vg.data[def.type]();
+
+ vg.keys(def).forEach(function(k) {
+ if (k === 'type') return;
+ (tx[k])(def[k]);
+ });
+
+ return tx;
+};vg.scene = {};
+
+vg.scene.GROUP = "group",
+vg.scene.ENTER = 0,
+vg.scene.UPDATE = 1,
+vg.scene.EXIT = 2;
+
+vg.scene.DEFAULT_DATA = {"sentinel":1}
+
+vg.scene.data = function(data, parentData) {
+ var DEFAULT = vg.scene.DEFAULT_DATA;
+
+ // if data is undefined, inherit or use default
+ data = vg.values(data || parentData || [DEFAULT]);
+
+ // if inheriting default data, ensure its in an array
+ if (data === DEFAULT) data = [DEFAULT];
+
+ return data;
+};
+
+vg.scene.fontString = function(o) {
+ return (o.fontStyle ? o.fontStyle + " " : "")
+ + (o.fontVariant ? o.fontVariant + " " : "")
+ + (o.fontWeight ? o.fontWeight + " " : "")
+ + (o.fontSize != null ? o.fontSize : vg.config.render.fontSize) + "px "
+ + (o.font || vg.config.render.font);
+};vg.scene.Item = (function() {
+ function item(mark) {
+ this.mark = mark;
+ }
+
+ var prototype = item.prototype;
+
+ prototype.hasPropertySet = function(name) {
+ var props = this.mark.def.properties;
+ return props && props[name] != null;
+ };
+
+ prototype.cousin = function(offset, index) {
+ if (offset === 0) return this;
+ offset = offset || -1;
+ var mark = this.mark,
+ group = mark.group,
+ iidx = index==null ? mark.items.indexOf(this) : index,
+ midx = group.items.indexOf(mark) + offset;
+ return group.items[midx].items[iidx];
+ };
+
+ prototype.sibling = function(offset) {
+ if (offset === 0) return this;
+ offset = offset || -1;
+ var mark = this.mark,
+ iidx = mark.items.indexOf(this) + offset;
+ return mark.items[iidx];
+ };
+
+ prototype.remove = function() {
+ var item = this,
+ list = item.mark.items,
+ i = list.indexOf(item);
+ if (i >= 0) (i===list.length-1) ? list.pop() : list.splice(i, 1);
+ return item;
+ };
+
+ return item;
+})();
+
+vg.scene.item = function(mark) {
+ return new vg.scene.Item(mark);
+};vg.scene.visit = function(node, func) {
+ var i, n, items;
+ if (func(node)) return true;
+ if (items = node.items) {
+ for (i=0, n=items.length; i0) s += "|";
+ s += String(f[i](d));
+ }
+ return s;
+ }
+ }
+
+ return build;
+})();vg.scene.bounds = (function() {
+
+ var parse = vg.canvas.path.parse,
+ boundPath = vg.canvas.path.bounds,
+ areaPath = vg.canvas.path.area,
+ linePath = vg.canvas.path.line,
+ halfpi = Math.PI / 2,
+ sqrt3 = Math.sqrt(3),
+ tan30 = Math.tan(30 * Math.PI / 180),
+ gfx = null;
+
+ function context() {
+ return gfx || (gfx = (vg.config.isNode
+ ? new (require("canvas"))(1,1)
+ : d3.select("body").append("canvas")
+ .attr("class", "vega_hidden")
+ .attr("width", 1)
+ .attr("height", 1)
+ .style("display", "none")
+ .node())
+ .getContext("2d"));
+ }
+
+ function pathBounds(o, path, bounds) {
+ if (path == null) {
+ bounds.set(0, 0, 0, 0);
+ } else {
+ boundPath(path, bounds);
+ if (o.stroke && o.opacity !== 0 && o.strokeWidth > 0) {
+ bounds.expand(o.strokeWidth);
+ }
+ }
+ return bounds;
+ }
+
+ function path(o, bounds) {
+ var p = o.path
+ ? o["path:parsed"] || (o["path:parsed"] = parse(o.path))
+ : null;
+ return pathBounds(o, p, bounds);
+ }
+
+ function area(o, bounds) {
+ var items = o.mark.items, o = items[0];
+ var p = o["path:parsed"] || (o["path:parsed"]=parse(areaPath(items)));
+ return pathBounds(items[0], p, bounds);
+ }
+
+ function line(o, bounds) {
+ var items = o.mark.items, o = items[0];
+ var p = o["path:parsed"] || (o["path:parsed"]=parse(linePath(items)));
+ return pathBounds(items[0], p, bounds);
+ }
+
+ function rect(o, bounds) {
+ var x = o.x || 0,
+ y = o.y || 0,
+ w = (x + o.width) || 0,
+ h = (y + o.height) || 0;
+ bounds.set(x, y, w, h);
+ if (o.stroke && o.opacity !== 0 && o.strokeWidth > 0) {
+ bounds.expand(o.strokeWidth);
+ }
+ return bounds;
+ }
+
+ function image(o, bounds) {
+ var w = o.width || 0,
+ h = o.height || 0,
+ x = (o.x||0) - (o.align === "center"
+ ? w/2 : (o.align === "right" ? w : 0)),
+ y = (o.y||0) - (o.baseline === "middle"
+ ? h/2 : (o.baseline === "bottom" ? h : 0));
+ return bounds.set(x, y, x+w, y+h);
+ }
+
+ function rule(o, bounds) {
+ var x1, y1;
+ bounds.set(
+ x1 = o.x || 0,
+ y1 = o.y || 0,
+ o.x2 != null ? o.x2 : x1,
+ o.y2 != null ? o.y2 : y1
+ );
+ if (o.stroke && o.opacity !== 0 && o.strokeWidth > 0) {
+ bounds.expand(o.strokeWidth);
+ }
+ return bounds;
+ }
+
+ function arc(o, bounds) {
+ var cx = o.x || 0,
+ cy = o.y || 0,
+ ir = o.innerRadius || 0,
+ or = o.outerRadius || 0,
+ sa = (o.startAngle || 0) - halfpi,
+ ea = (o.endAngle || 0) - halfpi,
+ xmin = Infinity, xmax = -Infinity,
+ ymin = Infinity, ymax = -Infinity,
+ a, i, n, x, y, ix, iy, ox, oy;
+
+ var angles = [sa, ea],
+ s = sa - (sa%halfpi);
+ for (i=0; i<4 && s 0) {
+ bounds.expand(o.strokeWidth);
+ }
+ return bounds;
+ }
+
+ function symbol(o, bounds) {
+ var size = o.size != null ? o.size : 100,
+ x = o.x || 0,
+ y = o.y || 0,
+ r, t, rx, ry;
+
+ switch (o.shape) {
+ case "cross":
+ r = Math.sqrt(size / 5) / 2;
+ t = 3*r;
+ bounds.set(x-t, y-t, x+y, y+t);
+ break;
+
+ case "diamond":
+ ry = Math.sqrt(size / (2 * tan30));
+ rx = ry * tan30;
+ bounds.set(x-rx, y-ry, x+rx, y+ry);
+ break;
+
+ case "square":
+ t = Math.sqrt(size);
+ r = t / 2;
+ bounds.set(x-r, y-r, x+r, y+r);
+ break;
+
+ case "triangle-down":
+ rx = Math.sqrt(size / sqrt3);
+ ry = rx * sqrt3 / 2;
+ bounds.set(x-rx, y-ry, x+rx, y+ry);
+ break;
+
+ case "triangle-up":
+ rx = Math.sqrt(size / sqrt3);
+ ry = rx * sqrt3 / 2;
+ bounds.set(x-rx, y-ry, x+rx, y+ry);
+ break;
+
+ default:
+ r = Math.sqrt(size/Math.PI);
+ bounds.set(x-r, y-r, x+r, y+r);
+ }
+ if (o.stroke && o.opacity !== 0 && o.strokeWidth > 0) {
+ bounds.expand(o.strokeWidth);
+ }
+ return bounds;
+ }
+
+ function text(o, bounds, noRotate) {
+ var x = (o.x || 0) + (o.dx || 0),
+ y = (o.y || 0) + (o.dy || 0),
+ h = o.fontSize || vg.config.render.fontSize,
+ a = o.align,
+ b = o.baseline,
+ g = context(), w;
+
+ g.font = vg.scene.fontString(o);
+ g.textAlign = a || "left";
+ g.textBaseline = b || "alphabetic";
+ w = g.measureText(o.text || "").width;
+
+ // horizontal
+ if (a === "center") {
+ x = x - (w / 2);
+ } else if (a === "right") {
+ x = x - w;
+ } else {
+ // left by default, do nothing
+ }
+
+ /// TODO find a robust solution for heights.
+ /// These offsets work for some but not all fonts.
+
+ // vertical
+ if (b === "top") {
+ y = y + (h/5);
+ } else if (b === "bottom") {
+ y = y - h;
+ } else if (b === "middle") {
+ y = y - (h/2) + (h/10);
+ } else {
+ y = y - 4*h/5; // alphabetic by default
+ }
+
+ bounds.set(x, y, x+w, y+h);
+ if (o.angle && !noRotate) {
+ bounds.rotate(o.angle*Math.PI/180, o.x||0, o.y||0);
+ }
+ return bounds.expand(noRotate ? 0 : 1);
+ }
+
+ function group(g, bounds, includeLegends) {
+ var axes = g.axisItems || [],
+ legends = g.legendItems || [], j, m;
+
+ for (j=0, m=axes.length; j 1) f = 1;
+ e = curr.ease(f);
+
+ for (i=0, n=curr.length; i 1 ? +y : tickMajorSize,
+ end = n > 0 ? +arguments[n] : tickMajorSize;
+
+ if (tickMajorSize !== major ||
+ tickMinorSize !== minor ||
+ tickEndSize !== end) {
+ reset();
+ }
+
+ tickMajorSize = major;
+ tickMinorSize = minor;
+ tickEndSize = end;
+ return axis;
+ };
+
+ axis.tickSubdivide = function(x) {
+ if (!arguments.length) return tickSubdivide;
+ tickSubdivide = +x;
+ return axis;
+ };
+
+ axis.offset = function(x) {
+ if (!arguments.length) return offset;
+ offset = vg.isObject(x) ? x : +x;
+ return axis;
+ };
+
+ axis.tickPadding = function(x) {
+ if (!arguments.length) return tickPadding;
+ if (tickPadding !== +x) { tickPadding = +x; reset(); }
+ return axis;
+ };
+
+ axis.titleOffset = function(x) {
+ if (!arguments.length) return titleOffset;
+ if (titleOffset !== +x) { titleOffset = +x; reset(); }
+ return axis;
+ };
+
+ axis.layer = function(x) {
+ if (!arguments.length) return layer;
+ if (layer !== x) { layer = x; reset(); }
+ return axis;
+ };
+
+ axis.grid = function(x) {
+ if (!arguments.length) return grid;
+ if (grid !== x) { grid = x; reset(); }
+ return axis;
+ };
+
+ axis.gridLineProperties = function(x) {
+ if (!arguments.length) return gridLineStyle;
+ if (gridLineStyle !== x) { gridLineStyle = x; }
+ return axis;
+ };
+
+ axis.majorTickProperties = function(x) {
+ if (!arguments.length) return majorTickStyle;
+ if (majorTickStyle !== x) { majorTickStyle = x; }
+ return axis;
+ };
+
+ axis.minorTickProperties = function(x) {
+ if (!arguments.length) return minorTickStyle;
+ if (minorTickStyle !== x) { minorTickStyle = x; }
+ return axis;
+ };
+
+ axis.tickLabelProperties = function(x) {
+ if (!arguments.length) return tickLabelStyle;
+ if (tickLabelStyle !== x) { tickLabelStyle = x; }
+ return axis;
+ };
+
+ axis.titleProperties = function(x) {
+ if (!arguments.length) return titleStyle;
+ if (titleStyle !== x) { titleStyle = x; }
+ return axis;
+ };
+
+ axis.domainProperties = function(x) {
+ if (!arguments.length) return domainStyle;
+ if (domainStyle !== x) { domainStyle = x; }
+ return axis;
+ };
+
+ axis.reset = function() { reset(); };
+
+ return axis;
+};
+
+var vg_axisOrients = {top: 1, right: 1, bottom: 1, left: 1};
+
+function vg_axisSubdivide(scale, ticks, m) {
+ subticks = [];
+ if (m && ticks.length > 1) {
+ var extent = vg_axisScaleExtent(scale.domain()),
+ subticks,
+ i = -1,
+ n = ticks.length,
+ d = (ticks[1] - ticks[0]) / ++m,
+ j,
+ v;
+ while (++i < n) {
+ for (j = m; --j > 0;) {
+ if ((v = +ticks[i] - j * d) >= extent[0]) {
+ subticks.push(v);
+ }
+ }
+ }
+ for (--i, j = 0; ++j < m && (v = +ticks[i] + j * d) < extent[1];) {
+ subticks.push(v);
+ }
+ }
+ return subticks;
+}
+
+function vg_axisScaleExtent(domain) {
+ var start = domain[0], stop = domain[domain.length - 1];
+ return start < stop ? [start, stop] : [stop, start];
+}
+
+function vg_axisScaleRange(scale) {
+ return scale.rangeExtent
+ ? scale.rangeExtent()
+ : vg_axisScaleExtent(scale.range());
+}
+
+var vg_axisAlign = {
+ bottom: "center",
+ top: "center",
+ left: "right",
+ right: "left"
+};
+
+var vg_axisBaseline = {
+ bottom: "top",
+ top: "bottom",
+ left: "middle",
+ right: "middle"
+};
+
+function vg_axisLabelExtend(orient, labels, oldScale, newScale, size, pad) {
+ size = Math.max(size, 0) + pad;
+ if (orient === "left" || orient === "top") {
+ size *= -1;
+ }
+ if (orient === "top" || orient === "bottom") {
+ vg.extend(labels.properties.enter, {
+ x: oldScale,
+ y: {value: size},
+ });
+ vg.extend(labels.properties.update, {
+ x: newScale,
+ y: {value: size},
+ align: {value: "center"},
+ baseline: {value: vg_axisBaseline[orient]}
+ });
+ } else {
+ vg.extend(labels.properties.enter, {
+ x: {value: size},
+ y: oldScale,
+ });
+ vg.extend(labels.properties.update, {
+ x: {value: size},
+ y: newScale,
+ align: {value: vg_axisAlign[orient]},
+ baseline: {value: "middle"}
+ });
+ }
+}
+
+function vg_axisTicksExtend(orient, ticks, oldScale, newScale, size) {
+ var sign = (orient === "left" || orient === "top") ? -1 : 1;
+ if (size === Infinity) {
+ size = (orient === "top" || orient === "bottom")
+ ? {group: "mark.group.height", mult: -sign}
+ : {group: "mark.group.width", mult: -sign};
+ } else {
+ size = {value: sign * size};
+ }
+ if (orient === "top" || orient === "bottom") {
+ vg.extend(ticks.properties.enter, {
+ x: oldScale,
+ y: {value: 0},
+ y2: size
+ });
+ vg.extend(ticks.properties.update, {
+ x: newScale,
+ y: {value: 0},
+ y2: size
+ });
+ vg.extend(ticks.properties.exit, {
+ x: newScale,
+ });
+ } else {
+ vg.extend(ticks.properties.enter, {
+ x: {value: 0},
+ x2: size,
+ y: oldScale
+ });
+ vg.extend(ticks.properties.update, {
+ x: {value: 0},
+ x2: size,
+ y: newScale
+ });
+ vg.extend(ticks.properties.exit, {
+ y: newScale,
+ });
+ }
+}
+
+function vg_axisTitleExtend(orient, title, range, offset) {
+ var mid = ~~((range[1] - range[0]) / 2),
+ sign = (orient === "top" || orient === "left") ? -1 : 1;
+
+ if (orient === "bottom" || orient === "top") {
+ vg.extend(title.properties.update, {
+ x: {value: mid},
+ y: {value: sign*offset},
+ angle: {value: 0}
+ });
+ } else {
+ vg.extend(title.properties.update, {
+ x: {value: sign*offset},
+ y: {value: mid},
+ angle: {value: -90}
+ });
+ }
+}
+
+function vg_axisDomainExtend(orient, domain, range, size) {
+ var path;
+ if (orient === "top" || orient === "left") {
+ size = -1 * size;
+ }
+ if (orient === "bottom" || orient === "top") {
+ path = "M" + range[0] + "," + size + "V0H" + range[1] + "V" + size;
+ } else {
+ path = "M" + size + "," + range[0] + "H0V" + range[1] + "H" + size;
+ }
+ domain.properties.update.path = {value: path};
+}
+
+function vg_axisUpdate(item, group, trans) {
+ var o = trans ? {} : item,
+ offset = item.mark.def.offset,
+ orient = item.mark.def.orient,
+ width = group.width,
+ height = group.height; // TODO fallback to global w,h?
+
+ if (vg.isObject(offset)) {
+ offset = -group.scales[offset.scale](offset.value);
+ }
+
+ switch (orient) {
+ case "left": { o.x = -offset; o.y = 0; break; }
+ case "right": { o.x = width + offset; o.y = 0; break; }
+ case "bottom": { o.x = 0; o.y = height + offset; break; }
+ case "top": { o.x = 0; o.y = -offset; break; }
+ default: { o.x = 0; o.y = 0; }
+ }
+
+ if (trans) trans.interpolate(item, o);
+}
+
+function vg_axisTicks() {
+ return {
+ type: "rule",
+ interactive: false,
+ key: "data",
+ properties: {
+ enter: {
+ stroke: {value: vg.config.axis.tickColor},
+ strokeWidth: {value: vg.config.axis.tickWidth},
+ opacity: {value: 1e-6}
+ },
+ exit: { opacity: {value: 1e-6} },
+ update: { opacity: {value: 1} }
+ }
+ };
+}
+
+function vg_axisTickLabels() {
+ return {
+ type: "text",
+ interactive: true,
+ key: "data",
+ properties: {
+ enter: {
+ fill: {value: vg.config.axis.tickLabelColor},
+ font: {value: vg.config.axis.tickLabelFont},
+ fontSize: {value: vg.config.axis.tickLabelFontSize},
+ opacity: {value: 1e-6},
+ text: {field: "label"}
+ },
+ exit: { opacity: {value: 1e-6} },
+ update: { opacity: {value: 1} }
+ }
+ };
+}
+
+function vg_axisTitle() {
+ return {
+ type: "text",
+ interactive: true,
+ properties: {
+ enter: {
+ font: {value: vg.config.axis.titleFont},
+ fontSize: {value: vg.config.axis.titleFontSize},
+ fontWeight: {value: vg.config.axis.titleFontWeight},
+ fill: {value: vg.config.axis.titleColor},
+ align: {value: "center"},
+ baseline: {value: "middle"},
+ text: {field: "data"}
+ },
+ update: {}
+ }
+ };
+}
+
+function vg_axisDomain() {
+ return {
+ type: "path",
+ interactive: false,
+ properties: {
+ enter: {
+ x: {value: 0.5},
+ y: {value: 0.5},
+ stroke: {value: vg.config.axis.axisColor},
+ strokeWidth: {value: vg.config.axis.axisWidth}
+ },
+ update: {}
+ }
+ };
+}
+vg.scene.legend = function() {
+ var size = null,
+ shape = null,
+ fill = null,
+ stroke = null,
+ spacing = null,
+ values = null,
+ format = null,
+ title = undefined,
+ orient = "right",
+ offset = vg.config.legend.offset,
+ padding = vg.config.legend.padding,
+ legendDef,
+ tickArguments = [5],
+ legendStyle = {},
+ symbolStyle = {},
+ gradientStyle = {},
+ titleStyle = {},
+ labelStyle = {};
+
+ var legend = {},
+ legendDef = null;
+
+ function reset() { legendDef = null; }
+
+ legend.def = function() {
+ var scale = size || shape || fill || stroke;
+ if (!legendDef) {
+ legendDef = (scale===fill || scale===stroke) && !discrete(scale.type)
+ ? quantDef(scale)
+ : ordinalDef(scale);
+ }
+ legendDef.orient = orient;
+ legendDef.offset = offset;
+ legendDef.padding = padding;
+ return legendDef;
+ };
+
+ function discrete(type) {
+ return type==="ordinal" || type==="quantize"
+ || type==="quantile" || type==="threshold";
+ }
+
+ function ordinalDef(scale) {
+ var def = o_legend_def(size, shape, fill, stroke);
+
+ // generate data
+ var data = (values == null
+ ? (scale.ticks ? scale.ticks.apply(scale, tickArguments) : scale.domain())
+ : values).map(vg.data.ingest);
+ var fmt = format==null ? (scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments) : String) : format;
+
+ // determine spacing between legend entries
+ var fs, range, offset, pad=5, domain = d3.range(data.length);
+ if (size) {
+ range = data.map(function(x) { return Math.sqrt(size(x.data)); });
+ offset = d3.max(range);
+ range = range.reduce(function(a,b,i,z) {
+ if (i > 0) a[i] = a[i-1] + z[i-1]/2 + pad;
+ return (a[i] += b/2, a); }, [0]).map(Math.round);
+ } else {
+ offset = Math.round(Math.sqrt(vg.config.legend.symbolSize));
+ range = spacing
+ || (fs = labelStyle.fontSize) && (fs.value + pad)
+ || (vg.config.legend.labelFontSize + pad);
+ range = domain.map(function(d,i) {
+ return Math.round(offset/2 + i*range);
+ });
+ }
+
+ // account for padding and title size
+ var sz = padding, ts;
+ if (title) {
+ ts = titleStyle.fontSize;
+ sz += 5 + ((ts && ts.value) || vg.config.legend.titleFontSize);
+ }
+ for (var i=0, n=range.length; i this._width ? Math.ceil(+b.x2 - this._width) + inset : 0,
+ b = b.y2 > this._height ? Math.ceil(+b.y2 - this._height) + inset : 0;
+ pad = {left:l, top:t, right:r, bottom:b};
+
+ if (this._strict) {
+ this._autopad = 0;
+ this._padding = pad;
+ this._width = Math.max(0, this.__width - (l+r));
+ this._height = Math.max(0, this.__height - (t+b));
+ this._model.width(this._width);
+ this._model.height(this._height);
+ if (this._el) this.initialize(this._el.parentNode);
+ this.update({props:"enter"}).update({props:"update"});
+ } else {
+ this.padding(pad).update(opt);
+ }
+ return this;
+ };
+
+ prototype.viewport = function(size) {
+ if (!arguments.length) return this._viewport;
+ if (this._viewport !== size) {
+ this._viewport = size;
+ if (this._el) this.initialize(this._el.parentNode);
+ }
+ return this;
+ };
+
+ prototype.renderer = function(type) {
+ if (!arguments.length) return this._io;
+ if (type === "canvas") type = vg.canvas;
+ if (type === "svg") type = vg.svg;
+ if (this._io !== type) {
+ this._io = type;
+ this._renderer = null;
+ if (this._el) this.initialize(this._el.parentNode);
+ if (this._build) this.render();
+ }
+ return this;
+ };
+
+ prototype.defs = function(defs) {
+ if (!arguments.length) return this._model.defs();
+ this._model.defs(defs);
+ return this;
+ };
+
+ prototype.data = function(data) {
+ if (!arguments.length) return this._model.data();
+ var ingest = vg.keys(data).reduce(function(d, k) {
+ return (d[k] = vg.data.ingestAll(data[k]), d);
+ }, {});
+ this._model.data(ingest);
+ this._build = false;
+ return this;
+ };
+
+ prototype.model = function(model) {
+ if (!arguments.length) return this._model;
+ if (this._model !== model) {
+ this._model = model;
+ if (this._handler) this._handler.model(model);
+ }
+ return this;
+ };
+
+ prototype.initialize = function(el) {
+ var v = this, prevHandler,
+ w = v._width, h = v._height, pad = v._padding;
+
+ // clear pre-existing container
+ d3.select(el).select("div.vega").remove();
+
+ // add div container
+ this._el = el = d3.select(el)
+ .append("div")
+ .attr("class", "vega")
+ .style("position", "relative")
+ .node();
+ if (v._viewport) {
+ d3.select(el)
+ .style("width", (v._viewport[0] || w)+"px")
+ .style("height", (v._viewport[1] || h)+"px")
+ .style("overflow", "auto");
+ }
+
+ // renderer
+ v._renderer = (v._renderer || new this._io.Renderer())
+ .initialize(el, w, h, pad);
+
+ // input handler
+ prevHandler = v._handler;
+ v._handler = new this._io.Handler()
+ .initialize(el, pad, v)
+ .model(v._model);
+
+ if (prevHandler) {
+ prevHandler.handlers().forEach(function(h) {
+ v._handler.on(h.type, h.handler);
+ });
+ }
+
+ return this;
+ };
+
+ prototype.render = function(items) {
+ this._renderer.render(this._model.scene(), items);
+ return this;
+ };
+
+ prototype.on = function() {
+ this._handler.on.apply(this._handler, arguments);
+ return this;
+ };
+
+ prototype.off = function() {
+ this._handler.off.apply(this._handler, arguments);
+ return this;
+ };
+
+ prototype.update = function(opt) {
+ opt = opt || {};
+ var view = this,
+ trans = opt.duration
+ ? vg.scene.transition(opt.duration, opt.ease)
+ : null;
+
+ view._build = view._build || (view._model.build(), true);
+ view._model.encode(trans, opt.props, opt.items);
+
+ if (trans) {
+ trans.start(function(items) {
+ view._renderer.render(view._model.scene(), items);
+ });
+ }
+ else view.render(opt.items);
+
+ return view.autopad(opt);
+ };
+
+ return view;
+})();
+
+// view constructor factory
+// takes definitions from parsed specification as input
+// returns a view constructor
+vg.ViewFactory = function(defs) {
+ return function(opt) {
+ opt = opt || {};
+ var v = new vg.View()
+ .width(defs.width)
+ .height(defs.height)
+ .padding(defs.padding)
+ .viewport(defs.viewport)
+ .renderer(opt.renderer || "canvas")
+ .defs(defs);
+
+ if (defs.data.load) v.data(defs.data.load);
+ if (opt.data) v.data(opt.data);
+ if (opt.el) v.initialize(opt.el);
+
+ if (opt.hover !== false) {
+ v.on("mouseover", function(evt, item) {
+ if (item.hasPropertySet("hover")) {
+ this.update({props:"hover", items:item});
+ }
+ })
+ .on("mouseout", function(evt, item) {
+ if (item.hasPropertySet("hover")) {
+ this.update({props:"update", items:item});
+ }
+ });
+ }
+
+ return v;
+ };
+};
+vg.Spec = (function() {
+ var spec = function(s) {
+ this.spec = {
+ width: 500,
+ height: 500,
+ padding: 0,
+ data: [],
+ scales: [],
+ axes: [],
+ marks: []
+ };
+ if (s) vg.extend(this.spec, s);
+ };
+
+ var prototype = spec.prototype;
+
+ prototype.width = function(w) {
+ this.spec.width = w;
+ return this;
+ };
+
+ prototype.height = function(h) {
+ this.spec.height = h;
+ return this;
+ };
+
+ prototype.padding = function(p) {
+ this.spec.padding = p;
+ return this;
+ };
+
+ prototype.viewport = function(v) {
+ this.spec.viewport = v;
+ return this;
+ };
+
+ prototype.data = function(name, params) {
+ if (!params) params = vg.isString(name) ? {name: name} : name;
+ else params.name = name;
+ this.spec.data.push(params);
+ return this;
+ };
+
+ prototype.scale = function(name, params) {
+ if (!params) params = vg.isString(name) ? {name: name} : name;
+ else params.name = name;
+ this.spec.scales.push(params);
+ return this;
+ };
+
+ prototype.axis = function(params) {
+ this.spec.axes.push(params);
+ return this;
+ };
+
+ prototype.mark = function(type, mark) {
+ if (!mark) mark = {type: type};
+ else mark.type = type;
+ mark.properties = {};
+ this.spec.marks.push(mark);
+
+ var that = this;
+ return {
+ from: function(name, obj) {
+ mark.from = obj
+ ? (obj.data = name, obj)
+ : vg.isString(name) ? {data: name} : name;
+ return this;
+ },
+ prop: function(name, obj) {
+ mark.properties[name] = vg.keys(obj).reduce(function(o,k) {
+ var v = obj[k];
+ return (o[k] = vg.isObject(v) ? v : {value: v}, o);
+ }, {});
+ return this;
+ },
+ done: function() { return that; }
+ };
+ };
+
+ prototype.parse = function(callback) {
+ vg.parse.spec(this.spec, callback);
+ };
+
+ prototype.json = function() {
+ return this.spec;
+ };
+
+ return spec;
+})();
+
+vg.spec = function(s) {
+ return new vg.Spec(s);
+};
+vg.headless = {};vg.headless.View = (function() {
+
+ var view = function(width, height, pad, type) {
+ this._canvas = null;
+ this._type = type;
+ this._el = "body";
+ this._build = false;
+ this._model = new vg.Model();
+ this._width = this.__width = width || 500;
+ this._height = this.__height = height || 500;
+ this._autopad = 1;
+ this._padding = pad || {top:0, left:0, bottom:0, right:0};
+ this._renderer = new vg[type].Renderer();
+ this.initialize();
+ };
+
+ var prototype = view.prototype;
+
+ prototype.el = function(el) {
+ if (!arguments.length) return this._el;
+ if (this._el !== el) {
+ this._el = el;
+ this.initialize();
+ }
+ return this;
+ };
+
+ prototype.width = function(width) {
+ if (!arguments.length) return this._width;
+ if (this._width !== width) {
+ this._width = width;
+ this.initialize();
+ this._model.width(width);
+ }
+ return this;
+ };
+
+ prototype.height = function(height) {
+ if (!arguments.length) return this._height;
+ if (this._height !== height) {
+ this._height = height;
+ this.initialize();
+ this._model.height(this._height);
+ }
+ return this;
+ };
+
+ prototype.padding = function(pad) {
+ if (!arguments.length) return this._padding;
+ if (this._padding !== pad) {
+ if (vg.isString(pad)) {
+ this._autopad = 1;
+ this._padding = {top:0, left:0, bottom:0, right:0};
+ this._strict = (pad === "strict");
+ } else {
+ this._autopad = 0;
+ this._padding = pad;
+ this._strict = false;
+ }
+ this.initialize();
+ }
+ return this;
+ };
+
+ prototype.autopad = function(opt) {
+ if (this._autopad < 1) return this;
+ else this._autopad = 0;
+
+ var pad = this._padding,
+ b = this._model.scene().bounds,
+ inset = vg.config.autopadInset,
+ l = b.x1 < 0 ? Math.ceil(-b.x1) + inset : 0,
+ t = b.y1 < 0 ? Math.ceil(-b.y1) + inset : 0,
+ r = b.x2 > this._width ? Math.ceil(+b.x2 - this._width) + inset : 0,
+ b = b.y2 > this._height ? Math.ceil(+b.y2 - this._height) + inset : 0;
+ pad = {left:l, top:t, right:r, bottom:b};
+
+ if (this._strict) {
+ this._autopad = 0;
+ this._padding = pad;
+ this._width = Math.max(0, this.__width - (l+r));
+ this._height = Math.max(0, this.__height - (t+b));
+ this._model.width(this._width);
+ this._model.height(this._height);
+ if (this._el) this.initialize();
+ this.update({props:"enter"}).update({props:"update"});
+ } else {
+ this.padding(pad).update(opt);
+ }
+ return this;
+ };
+
+ prototype.viewport = function() {
+ if (!arguments.length) return null;
+ return this;
+ };
+
+ prototype.defs = function(defs) {
+ if (!arguments.length) return this._model.defs();
+ this._model.defs(defs);
+ return this;
+ };
+
+ prototype.data = function(data) {
+ if (!arguments.length) return this._model.data();
+ var ingest = vg.keys(data).reduce(function(d, k) {
+ return (d[k] = vg.data.ingestAll(data[k]), d);
+ }, {});
+ this._model.data(ingest);
+ this._build = false;
+ return this;
+ };
+
+ prototype.renderer = function() {
+ return this._renderer;
+ };
+
+ prototype.canvas = function() {
+ return this._canvas;
+ };
+
+ prototype.canvasAsync = function(callback) {
+ var r = this._renderer, view = this;
+
+ function wait() {
+ if (r.pendingImages() === 0) {
+ view.render(); // re-render with all images
+ callback(view._canvas);
+ } else {
+ setTimeout(wait, 10);
+ }
+ }
+
+ // if images loading, poll until ready
+ (r.pendingImages() > 0) ? wait() : callback(this._canvas);
+ };
+
+ prototype.svg = function() {
+ if (this._type !== "svg") return null;
+
+ var p = this._padding,
+ w = this._width + (p ? p.left + p.right : 0),
+ h = this._height + (p ? p.top + p.bottom : 0);
+
+ // build svg text
+ var svg = d3.select(this._el)
+ .select("svg").node().innerHTML
+ .replace(/ href=/g, " xlink:href="); // ns hack. sigh.
+
+ return '' + svg + ' '
+ };
+
+ prototype.initialize = function() {
+ var w = this._width,
+ h = this._height,
+ pad = this._padding;
+
+ if (this._type === "svg") {
+ this.initSVG(w, h, pad);
+ } else {
+ this.initCanvas(w, h, pad);
+ }
+
+ return this;
+ };
+
+ prototype.initCanvas = function(w, h, pad) {
+ var Canvas = require("canvas"),
+ tw = w + pad.left + pad.right,
+ th = h + pad.top + pad.bottom,
+ canvas = this._canvas = new Canvas(tw, th),
+ ctx = canvas.getContext("2d");
+
+ // setup canvas context
+ ctx.setTransform(1, 0, 0, 1, pad.left, pad.top);
+
+ // configure renderer
+ this._renderer.context(ctx);
+ this._renderer.resize(w, h, pad);
+ };
+
+ prototype.initSVG = function(w, h, pad) {
+ var tw = w + pad.left + pad.right,
+ th = h + pad.top + pad.bottom;
+
+ // configure renderer
+ this._renderer.initialize(this._el, w, h, pad);
+ }
+
+ prototype.render = function(items) {
+ this._renderer.render(this._model.scene(), items);
+ return this;
+ };
+
+ prototype.update = function(opt) {
+ opt = opt || {};
+ var view = this;
+ view._build = view._build || (view._model.build(), true);
+ view._model.encode(null, opt.props, opt.items);
+ view.render(opt.items);
+ return view.autopad(opt);
+ };
+
+ return view;
+})();
+
+// headless view constructor factory
+// takes definitions from parsed specification as input
+// returns a view constructor
+vg.headless.View.Factory = function(defs) {
+ return function(opt) {
+ opt = opt || {};
+ var w = defs.width,
+ h = defs.height,
+ p = defs.padding,
+ r = opt.renderer || "canvas",
+ v = new vg.headless.View(w, h, p, r).defs(defs);
+ if (defs.data.load) v.data(defs.data.load);
+ if (opt.data) v.data(opt.data);
+ return v;
+ };
+};vg.headless.render = function(opt, callback) {
+ function draw(chart) {
+ try {
+ // create and render view
+ var view = chart({
+ data: opt.data,
+ renderer: opt.renderer
+ }).update();
+
+ if (opt.renderer === "svg") {
+ // extract rendered svg
+ callback(null, {svg: view.svg()});
+ } else {
+ // extract rendered canvas, waiting for any images to load
+ view.canvasAsync(function(canvas) {
+ callback(null, {canvas: canvas});
+ });
+ }
+ } catch (err) {
+ callback(err, null);
+ }
+ }
+
+ vg.parse.spec(opt.spec, draw, vg.headless.View.Factory);
+}; return vg;
+})(d3, typeof topojson === "undefined" ? null : topojson);
+// assumes D3 and topojson in global namespace
diff --git a/dashboard/app/stats.html b/dashboard/app/stats.html
new file mode 100644
index 000000000..eb324f1b2
--- /dev/null
+++ b/dashboard/app/stats.html
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+ etcd Browser
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dashboard/app/styles/bootstrap.css b/dashboard/app/styles/bootstrap.css
new file mode 100644
index 000000000..b725064aa
--- /dev/null
+++ b/dashboard/app/styles/bootstrap.css
@@ -0,0 +1,6167 @@
+/*!
+ * Bootstrap v2.3.2
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+
+.clearfix {
+ *zoom: 1;
+}
+
+.clearfix:before,
+.clearfix:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.clearfix:after {
+ clear: both;
+}
+
+.hide-text {
+ font: 0/0 a;
+ color: transparent;
+ text-shadow: none;
+ background-color: transparent;
+ border: 0;
+}
+
+.input-block-level {
+ display: block;
+ width: 100%;
+ min-height: 30px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+nav,
+section {
+ display: block;
+}
+
+audio,
+canvas,
+video {
+ display: inline-block;
+ *display: inline;
+ *zoom: 1;
+}
+
+audio:not([controls]) {
+ display: none;
+}
+
+html {
+ font-size: 100%;
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+}
+
+a:focus {
+ outline: thin dotted #333;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+
+a:hover,
+a:active {
+ outline: 0;
+}
+
+sub,
+sup {
+ position: relative;
+ font-size: 75%;
+ line-height: 0;
+ vertical-align: baseline;
+}
+
+sup {
+ top: -0.5em;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+img {
+ width: auto\9;
+ height: auto;
+ max-width: 100%;
+ vertical-align: middle;
+ border: 0;
+ -ms-interpolation-mode: bicubic;
+}
+
+#map_canvas img,
+.google-maps img {
+ max-width: none;
+}
+
+button,
+input,
+select,
+textarea {
+ margin: 0;
+ font-size: 100%;
+ vertical-align: middle;
+}
+
+button,
+input {
+ *overflow: visible;
+ line-height: normal;
+}
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+}
+
+button,
+html input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+ cursor: pointer;
+ -webkit-appearance: button;
+}
+
+label,
+select,
+button,
+input[type="button"],
+input[type="reset"],
+input[type="submit"],
+input[type="radio"],
+input[type="checkbox"] {
+ cursor: pointer;
+}
+
+input[type="search"] {
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ -webkit-appearance: textfield;
+}
+
+input[type="search"]::-webkit-search-decoration,
+input[type="search"]::-webkit-search-cancel-button {
+ -webkit-appearance: none;
+}
+
+textarea {
+ overflow: auto;
+ vertical-align: top;
+}
+
+@media print {
+ * {
+ color: #000 !important;
+ text-shadow: none !important;
+ background: transparent !important;
+ box-shadow: none !important;
+ }
+ a,
+ a:visited {
+ text-decoration: underline;
+ }
+ a[href]:after {
+ content: " (" attr(href) ")";
+ }
+ abbr[title]:after {
+ content: " (" attr(title) ")";
+ }
+ .ir a:after,
+ a[href^="javascript:"]:after,
+ a[href^="#"]:after {
+ content: "";
+ }
+ pre,
+ blockquote {
+ border: 1px solid #999;
+ page-break-inside: avoid;
+ }
+ thead {
+ display: table-header-group;
+ }
+ tr,
+ img {
+ page-break-inside: avoid;
+ }
+ img {
+ max-width: 100% !important;
+ }
+ @page {
+ margin: 0.5cm;
+ }
+ p,
+ h2,
+ h3 {
+ orphans: 3;
+ widows: 3;
+ }
+ h2,
+ h3 {
+ page-break-after: avoid;
+ }
+}
+
+body {
+ margin: 0;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 14px;
+ line-height: 20px;
+ color: #333333;
+ background-color: #ffffff;
+}
+
+a {
+ color: #0088cc;
+ text-decoration: none;
+}
+
+a:hover,
+a:focus {
+ color: #005580;
+ text-decoration: underline;
+}
+
+.img-rounded {
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+}
+
+.img-polaroid {
+ padding: 4px;
+ background-color: #fff;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.img-circle {
+ -webkit-border-radius: 500px;
+ -moz-border-radius: 500px;
+ border-radius: 500px;
+}
+
+.row {
+ margin-left: -20px;
+ *zoom: 1;
+}
+
+.row:before,
+.row:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.row:after {
+ clear: both;
+}
+
+[class*="span"] {
+ float: left;
+ min-height: 1px;
+ margin-left: 20px;
+}
+
+.container,
+.navbar-static-top .container,
+.navbar-fixed-top .container,
+.navbar-fixed-bottom .container {
+ width: 940px;
+}
+
+.span12 {
+ width: 940px;
+}
+
+.span11 {
+ width: 860px;
+}
+
+.span10 {
+ width: 780px;
+}
+
+.span9 {
+ width: 700px;
+}
+
+.span8 {
+ width: 620px;
+}
+
+.span7 {
+ width: 540px;
+}
+
+.span6 {
+ width: 460px;
+}
+
+.span5 {
+ width: 380px;
+}
+
+.span4 {
+ width: 300px;
+}
+
+.span3 {
+ width: 220px;
+}
+
+.span2 {
+ width: 140px;
+}
+
+.span1 {
+ width: 60px;
+}
+
+.offset12 {
+ margin-left: 980px;
+}
+
+.offset11 {
+ margin-left: 900px;
+}
+
+.offset10 {
+ margin-left: 820px;
+}
+
+.offset9 {
+ margin-left: 740px;
+}
+
+.offset8 {
+ margin-left: 660px;
+}
+
+.offset7 {
+ margin-left: 580px;
+}
+
+.offset6 {
+ margin-left: 500px;
+}
+
+.offset5 {
+ margin-left: 420px;
+}
+
+.offset4 {
+ margin-left: 340px;
+}
+
+.offset3 {
+ margin-left: 260px;
+}
+
+.offset2 {
+ margin-left: 180px;
+}
+
+.offset1 {
+ margin-left: 100px;
+}
+
+.row-fluid {
+ width: 100%;
+ *zoom: 1;
+}
+
+.row-fluid:before,
+.row-fluid:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.row-fluid:after {
+ clear: both;
+}
+
+.row-fluid [class*="span"] {
+ display: block;
+ float: left;
+ width: 100%;
+ min-height: 30px;
+ margin-left: 2.127659574468085%;
+ *margin-left: 2.074468085106383%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+.row-fluid [class*="span"]:first-child {
+ margin-left: 0;
+}
+
+.row-fluid .controls-row [class*="span"] + [class*="span"] {
+ margin-left: 2.127659574468085%;
+}
+
+.row-fluid .span12 {
+ width: 100%;
+ *width: 99.94680851063829%;
+}
+
+.row-fluid .span11 {
+ width: 91.48936170212765%;
+ *width: 91.43617021276594%;
+}
+
+.row-fluid .span10 {
+ width: 82.97872340425532%;
+ *width: 82.92553191489361%;
+}
+
+.row-fluid .span9 {
+ width: 74.46808510638297%;
+ *width: 74.41489361702126%;
+}
+
+.row-fluid .span8 {
+ width: 65.95744680851064%;
+ *width: 65.90425531914893%;
+}
+
+.row-fluid .span7 {
+ width: 57.44680851063829%;
+ *width: 57.39361702127659%;
+}
+
+.row-fluid .span6 {
+ width: 48.93617021276595%;
+ *width: 48.88297872340425%;
+}
+
+.row-fluid .span5 {
+ width: 40.42553191489362%;
+ *width: 40.37234042553192%;
+}
+
+.row-fluid .span4 {
+ width: 31.914893617021278%;
+ *width: 31.861702127659576%;
+}
+
+.row-fluid .span3 {
+ width: 23.404255319148934%;
+ *width: 23.351063829787233%;
+}
+
+.row-fluid .span2 {
+ width: 14.893617021276595%;
+ *width: 14.840425531914894%;
+}
+
+.row-fluid .span1 {
+ width: 6.382978723404255%;
+ *width: 6.329787234042553%;
+}
+
+.row-fluid .offset12 {
+ margin-left: 104.25531914893617%;
+ *margin-left: 104.14893617021275%;
+}
+
+.row-fluid .offset12:first-child {
+ margin-left: 102.12765957446808%;
+ *margin-left: 102.02127659574467%;
+}
+
+.row-fluid .offset11 {
+ margin-left: 95.74468085106382%;
+ *margin-left: 95.6382978723404%;
+}
+
+.row-fluid .offset11:first-child {
+ margin-left: 93.61702127659574%;
+ *margin-left: 93.51063829787232%;
+}
+
+.row-fluid .offset10 {
+ margin-left: 87.23404255319149%;
+ *margin-left: 87.12765957446807%;
+}
+
+.row-fluid .offset10:first-child {
+ margin-left: 85.1063829787234%;
+ *margin-left: 84.99999999999999%;
+}
+
+.row-fluid .offset9 {
+ margin-left: 78.72340425531914%;
+ *margin-left: 78.61702127659572%;
+}
+
+.row-fluid .offset9:first-child {
+ margin-left: 76.59574468085106%;
+ *margin-left: 76.48936170212764%;
+}
+
+.row-fluid .offset8 {
+ margin-left: 70.2127659574468%;
+ *margin-left: 70.10638297872339%;
+}
+
+.row-fluid .offset8:first-child {
+ margin-left: 68.08510638297872%;
+ *margin-left: 67.9787234042553%;
+}
+
+.row-fluid .offset7 {
+ margin-left: 61.70212765957446%;
+ *margin-left: 61.59574468085106%;
+}
+
+.row-fluid .offset7:first-child {
+ margin-left: 59.574468085106375%;
+ *margin-left: 59.46808510638297%;
+}
+
+.row-fluid .offset6 {
+ margin-left: 53.191489361702125%;
+ *margin-left: 53.085106382978715%;
+}
+
+.row-fluid .offset6:first-child {
+ margin-left: 51.063829787234035%;
+ *margin-left: 50.95744680851063%;
+}
+
+.row-fluid .offset5 {
+ margin-left: 44.68085106382979%;
+ *margin-left: 44.57446808510638%;
+}
+
+.row-fluid .offset5:first-child {
+ margin-left: 42.5531914893617%;
+ *margin-left: 42.4468085106383%;
+}
+
+.row-fluid .offset4 {
+ margin-left: 36.170212765957444%;
+ *margin-left: 36.06382978723405%;
+}
+
+.row-fluid .offset4:first-child {
+ margin-left: 34.04255319148936%;
+ *margin-left: 33.93617021276596%;
+}
+
+.row-fluid .offset3 {
+ margin-left: 27.659574468085104%;
+ *margin-left: 27.5531914893617%;
+}
+
+.row-fluid .offset3:first-child {
+ margin-left: 25.53191489361702%;
+ *margin-left: 25.425531914893618%;
+}
+
+.row-fluid .offset2 {
+ margin-left: 19.148936170212764%;
+ *margin-left: 19.04255319148936%;
+}
+
+.row-fluid .offset2:first-child {
+ margin-left: 17.02127659574468%;
+ *margin-left: 16.914893617021278%;
+}
+
+.row-fluid .offset1 {
+ margin-left: 10.638297872340425%;
+ *margin-left: 10.53191489361702%;
+}
+
+.row-fluid .offset1:first-child {
+ margin-left: 8.51063829787234%;
+ *margin-left: 8.404255319148938%;
+}
+
+[class*="span"].hide,
+.row-fluid [class*="span"].hide {
+ display: none;
+}
+
+[class*="span"].pull-right,
+.row-fluid [class*="span"].pull-right {
+ float: right;
+}
+
+.container {
+ margin-right: auto;
+ margin-left: auto;
+ *zoom: 1;
+}
+
+.container:before,
+.container:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.container:after {
+ clear: both;
+}
+
+.container-fluid {
+ padding-right: 20px;
+ padding-left: 20px;
+ *zoom: 1;
+}
+
+.container-fluid:before,
+.container-fluid:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.container-fluid:after {
+ clear: both;
+}
+
+p {
+ margin: 0 0 10px;
+}
+
+.lead {
+ margin-bottom: 20px;
+ font-size: 21px;
+ font-weight: 200;
+ line-height: 30px;
+}
+
+small {
+ font-size: 85%;
+}
+
+strong {
+ font-weight: bold;
+}
+
+em {
+ font-style: italic;
+}
+
+cite {
+ font-style: normal;
+}
+
+.muted {
+ color: #999999;
+}
+
+a.muted:hover,
+a.muted:focus {
+ color: #808080;
+}
+
+.text-warning {
+ color: #c09853;
+}
+
+a.text-warning:hover,
+a.text-warning:focus {
+ color: #a47e3c;
+}
+
+.text-error {
+ color: #b94a48;
+}
+
+a.text-error:hover,
+a.text-error:focus {
+ color: #953b39;
+}
+
+.text-info {
+ color: #3a87ad;
+}
+
+a.text-info:hover,
+a.text-info:focus {
+ color: #2d6987;
+}
+
+.text-success {
+ color: #468847;
+}
+
+a.text-success:hover,
+a.text-success:focus {
+ color: #356635;
+}
+
+.text-left {
+ text-align: left;
+}
+
+.text-right {
+ text-align: right;
+}
+
+.text-center {
+ text-align: center;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ margin: 10px 0;
+ font-family: inherit;
+ font-weight: bold;
+ line-height: 20px;
+ color: inherit;
+ text-rendering: optimizelegibility;
+}
+
+h1 small,
+h2 small,
+h3 small,
+h4 small,
+h5 small,
+h6 small {
+ font-weight: normal;
+ line-height: 1;
+ color: #999999;
+}
+
+h1,
+h2,
+h3 {
+ line-height: 40px;
+}
+
+h1 {
+ font-size: 38.5px;
+}
+
+h2 {
+ font-size: 31.5px;
+}
+
+h3 {
+ font-size: 24.5px;
+}
+
+h4 {
+ font-size: 17.5px;
+}
+
+h5 {
+ font-size: 14px;
+}
+
+h6 {
+ font-size: 11.9px;
+}
+
+h1 small {
+ font-size: 24.5px;
+}
+
+h2 small {
+ font-size: 17.5px;
+}
+
+h3 small {
+ font-size: 14px;
+}
+
+h4 small {
+ font-size: 14px;
+}
+
+.page-header {
+ padding-bottom: 9px;
+ margin: 20px 0 30px;
+ border-bottom: 1px solid #eeeeee;
+}
+
+ul,
+ol {
+ padding: 0;
+ margin: 0 0 10px 25px;
+}
+
+ul ul,
+ul ol,
+ol ol,
+ol ul {
+ margin-bottom: 0;
+}
+
+li {
+ line-height: 20px;
+}
+
+ul.unstyled,
+ol.unstyled {
+ margin-left: 0;
+ list-style: none;
+}
+
+ul.inline,
+ol.inline {
+ margin-left: 0;
+ list-style: none;
+}
+
+ul.inline > li,
+ol.inline > li {
+ display: inline-block;
+ *display: inline;
+ padding-right: 5px;
+ padding-left: 5px;
+ *zoom: 1;
+}
+
+dl {
+ margin-bottom: 20px;
+}
+
+dt,
+dd {
+ line-height: 20px;
+}
+
+dt {
+ font-weight: bold;
+}
+
+dd {
+ margin-left: 10px;
+}
+
+.dl-horizontal {
+ *zoom: 1;
+}
+
+.dl-horizontal:before,
+.dl-horizontal:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.dl-horizontal:after {
+ clear: both;
+}
+
+.dl-horizontal dt {
+ float: left;
+ width: 160px;
+ overflow: hidden;
+ clear: left;
+ text-align: right;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.dl-horizontal dd {
+ margin-left: 180px;
+}
+
+hr {
+ margin: 20px 0;
+ border: 0;
+ border-top: 1px solid #eeeeee;
+ border-bottom: 1px solid #ffffff;
+}
+
+abbr[title],
+abbr[data-original-title] {
+ cursor: help;
+ border-bottom: 1px dotted #999999;
+}
+
+abbr.initialism {
+ font-size: 90%;
+ text-transform: uppercase;
+}
+
+blockquote {
+ padding: 0 0 0 15px;
+ margin: 0 0 20px;
+ border-left: 5px solid #eeeeee;
+}
+
+blockquote p {
+ margin-bottom: 0;
+ font-size: 17.5px;
+ font-weight: 300;
+ line-height: 1.25;
+}
+
+blockquote small {
+ display: block;
+ line-height: 20px;
+ color: #999999;
+}
+
+blockquote small:before {
+ content: '\2014 \00A0';
+}
+
+blockquote.pull-right {
+ float: right;
+ padding-right: 15px;
+ padding-left: 0;
+ border-right: 5px solid #eeeeee;
+ border-left: 0;
+}
+
+blockquote.pull-right p,
+blockquote.pull-right small {
+ text-align: right;
+}
+
+blockquote.pull-right small:before {
+ content: '';
+}
+
+blockquote.pull-right small:after {
+ content: '\00A0 \2014';
+}
+
+q:before,
+q:after,
+blockquote:before,
+blockquote:after {
+ content: "";
+}
+
+address {
+ display: block;
+ margin-bottom: 20px;
+ font-style: normal;
+ line-height: 20px;
+}
+
+code,
+pre {
+ padding: 0 3px 2px;
+ font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
+ font-size: 12px;
+ color: #333333;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+
+code {
+ padding: 2px 4px;
+ color: #d14;
+ white-space: nowrap;
+ background-color: #f7f7f9;
+ border: 1px solid #e1e1e8;
+}
+
+pre {
+ display: block;
+ padding: 9.5px;
+ margin: 0 0 10px;
+ font-size: 13px;
+ line-height: 20px;
+ word-break: break-all;
+ word-wrap: break-word;
+ white-space: pre;
+ white-space: pre-wrap;
+ background-color: #f5f5f5;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+pre.prettyprint {
+ margin-bottom: 20px;
+}
+
+pre code {
+ padding: 0;
+ color: inherit;
+ white-space: pre;
+ white-space: pre-wrap;
+ background-color: transparent;
+ border: 0;
+}
+
+.pre-scrollable {
+ max-height: 340px;
+ overflow-y: scroll;
+}
+
+form {
+ margin: 0 0 20px;
+}
+
+fieldset {
+ padding: 0;
+ margin: 0;
+ border: 0;
+}
+
+legend {
+ display: block;
+ width: 100%;
+ padding: 0;
+ margin-bottom: 20px;
+ font-size: 21px;
+ line-height: 40px;
+ color: #333333;
+ border: 0;
+ border-bottom: 1px solid #e5e5e5;
+}
+
+legend small {
+ font-size: 15px;
+ color: #999999;
+}
+
+label,
+input,
+button,
+select,
+textarea {
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 20px;
+}
+
+input,
+button,
+select,
+textarea {
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+}
+
+label {
+ display: block;
+ margin-bottom: 5px;
+}
+
+select,
+textarea,
+input[type="text"],
+input[type="password"],
+input[type="datetime"],
+input[type="datetime-local"],
+input[type="date"],
+input[type="month"],
+input[type="time"],
+input[type="week"],
+input[type="number"],
+input[type="email"],
+input[type="url"],
+input[type="search"],
+input[type="tel"],
+input[type="color"],
+.uneditable-input {
+ display: inline-block;
+ height: 20px;
+ padding: 4px 6px;
+ margin-bottom: 10px;
+ font-size: 14px;
+ line-height: 20px;
+ color: #555555;
+ vertical-align: middle;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+input,
+textarea,
+.uneditable-input {
+ width: 206px;
+}
+
+textarea {
+ height: auto;
+}
+
+textarea,
+input[type="text"],
+input[type="password"],
+input[type="datetime"],
+input[type="datetime-local"],
+input[type="date"],
+input[type="month"],
+input[type="time"],
+input[type="week"],
+input[type="number"],
+input[type="email"],
+input[type="url"],
+input[type="search"],
+input[type="tel"],
+input[type="color"],
+.uneditable-input {
+ background-color: #ffffff;
+ border: 1px solid #cccccc;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
+ -moz-transition: border linear 0.2s, box-shadow linear 0.2s;
+ -o-transition: border linear 0.2s, box-shadow linear 0.2s;
+ transition: border linear 0.2s, box-shadow linear 0.2s;
+}
+
+textarea:focus,
+input[type="text"]:focus,
+input[type="password"]:focus,
+input[type="datetime"]:focus,
+input[type="datetime-local"]:focus,
+input[type="date"]:focus,
+input[type="month"]:focus,
+input[type="time"]:focus,
+input[type="week"]:focus,
+input[type="number"]:focus,
+input[type="email"]:focus,
+input[type="url"]:focus,
+input[type="search"]:focus,
+input[type="tel"]:focus,
+input[type="color"]:focus,
+.uneditable-input:focus {
+ border-color: rgba(82, 168, 236, 0.8);
+ outline: 0;
+ outline: thin dotted \9;
+ /* IE6-9 */
+
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+}
+
+input[type="radio"],
+input[type="checkbox"] {
+ margin: 4px 0 0;
+ margin-top: 1px \9;
+ *margin-top: 0;
+ line-height: normal;
+}
+
+input[type="file"],
+input[type="image"],
+input[type="submit"],
+input[type="reset"],
+input[type="button"],
+input[type="radio"],
+input[type="checkbox"] {
+ width: auto;
+}
+
+select,
+input[type="file"] {
+ height: 30px;
+ /* In IE7, the height of the select element cannot be changed by height, only font-size */
+
+ *margin-top: 4px;
+ /* For IE7, add top margin to align select with labels */
+
+ line-height: 30px;
+}
+
+select {
+ width: 220px;
+ background-color: #ffffff;
+ border: 1px solid #cccccc;
+}
+
+select[multiple],
+select[size] {
+ height: auto;
+}
+
+select:focus,
+input[type="file"]:focus,
+input[type="radio"]:focus,
+input[type="checkbox"]:focus {
+ outline: thin dotted #333;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+
+.uneditable-input,
+.uneditable-textarea {
+ color: #999999;
+ cursor: not-allowed;
+ background-color: #fcfcfc;
+ border-color: #cccccc;
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+ -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+}
+
+.uneditable-input {
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+.uneditable-textarea {
+ width: auto;
+ height: auto;
+}
+
+input:-moz-placeholder,
+textarea:-moz-placeholder {
+ color: #999999;
+}
+
+input:-ms-input-placeholder,
+textarea:-ms-input-placeholder {
+ color: #999999;
+}
+
+input::-webkit-input-placeholder,
+textarea::-webkit-input-placeholder {
+ color: #999999;
+}
+
+.radio,
+.checkbox {
+ min-height: 20px;
+ padding-left: 20px;
+}
+
+.radio input[type="radio"],
+.checkbox input[type="checkbox"] {
+ float: left;
+ margin-left: -20px;
+}
+
+.controls > .radio:first-child,
+.controls > .checkbox:first-child {
+ padding-top: 5px;
+}
+
+.radio.inline,
+.checkbox.inline {
+ display: inline-block;
+ padding-top: 5px;
+ margin-bottom: 0;
+ vertical-align: middle;
+}
+
+.radio.inline + .radio.inline,
+.checkbox.inline + .checkbox.inline {
+ margin-left: 10px;
+}
+
+.input-mini {
+ width: 60px;
+}
+
+.input-small {
+ width: 90px;
+}
+
+.input-medium {
+ width: 150px;
+}
+
+.input-large {
+ width: 210px;
+}
+
+.input-xlarge {
+ width: 270px;
+}
+
+.input-xxlarge {
+ width: 530px;
+}
+
+input[class*="span"],
+select[class*="span"],
+textarea[class*="span"],
+.uneditable-input[class*="span"],
+.row-fluid input[class*="span"],
+.row-fluid select[class*="span"],
+.row-fluid textarea[class*="span"],
+.row-fluid .uneditable-input[class*="span"] {
+ float: none;
+ margin-left: 0;
+}
+
+.input-append input[class*="span"],
+.input-append .uneditable-input[class*="span"],
+.input-prepend input[class*="span"],
+.input-prepend .uneditable-input[class*="span"],
+.row-fluid input[class*="span"],
+.row-fluid select[class*="span"],
+.row-fluid textarea[class*="span"],
+.row-fluid .uneditable-input[class*="span"],
+.row-fluid .input-prepend [class*="span"],
+.row-fluid .input-append [class*="span"] {
+ display: inline-block;
+}
+
+input,
+textarea,
+.uneditable-input {
+ margin-left: 0;
+}
+
+.controls-row [class*="span"] + [class*="span"] {
+ margin-left: 20px;
+}
+
+input.span12,
+textarea.span12,
+.uneditable-input.span12 {
+ width: 926px;
+}
+
+input.span11,
+textarea.span11,
+.uneditable-input.span11 {
+ width: 846px;
+}
+
+input.span10,
+textarea.span10,
+.uneditable-input.span10 {
+ width: 766px;
+}
+
+input.span9,
+textarea.span9,
+.uneditable-input.span9 {
+ width: 686px;
+}
+
+input.span8,
+textarea.span8,
+.uneditable-input.span8 {
+ width: 606px;
+}
+
+input.span7,
+textarea.span7,
+.uneditable-input.span7 {
+ width: 526px;
+}
+
+input.span6,
+textarea.span6,
+.uneditable-input.span6 {
+ width: 446px;
+}
+
+input.span5,
+textarea.span5,
+.uneditable-input.span5 {
+ width: 366px;
+}
+
+input.span4,
+textarea.span4,
+.uneditable-input.span4 {
+ width: 286px;
+}
+
+input.span3,
+textarea.span3,
+.uneditable-input.span3 {
+ width: 206px;
+}
+
+input.span2,
+textarea.span2,
+.uneditable-input.span2 {
+ width: 126px;
+}
+
+input.span1,
+textarea.span1,
+.uneditable-input.span1 {
+ width: 46px;
+}
+
+.controls-row {
+ *zoom: 1;
+}
+
+.controls-row:before,
+.controls-row:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.controls-row:after {
+ clear: both;
+}
+
+.controls-row [class*="span"],
+.row-fluid .controls-row [class*="span"] {
+ float: left;
+}
+
+.controls-row .checkbox[class*="span"],
+.controls-row .radio[class*="span"] {
+ padding-top: 5px;
+}
+
+input[disabled],
+select[disabled],
+textarea[disabled],
+input[readonly],
+select[readonly],
+textarea[readonly] {
+ cursor: not-allowed;
+ background-color: #eeeeee;
+}
+
+input[type="radio"][disabled],
+input[type="checkbox"][disabled],
+input[type="radio"][readonly],
+input[type="checkbox"][readonly] {
+ background-color: transparent;
+}
+
+.control-group.warning .control-label,
+.control-group.warning .help-block,
+.control-group.warning .help-inline {
+ color: #c09853;
+}
+
+.control-group.warning .checkbox,
+.control-group.warning .radio,
+.control-group.warning input,
+.control-group.warning select,
+.control-group.warning textarea {
+ color: #c09853;
+}
+
+.control-group.warning input,
+.control-group.warning select,
+.control-group.warning textarea {
+ border-color: #c09853;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.control-group.warning input:focus,
+.control-group.warning select:focus,
+.control-group.warning textarea:focus {
+ border-color: #a47e3c;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+}
+
+.control-group.warning .input-prepend .add-on,
+.control-group.warning .input-append .add-on {
+ color: #c09853;
+ background-color: #fcf8e3;
+ border-color: #c09853;
+}
+
+.control-group.error .control-label,
+.control-group.error .help-block,
+.control-group.error .help-inline {
+ color: #b94a48;
+}
+
+.control-group.error .checkbox,
+.control-group.error .radio,
+.control-group.error input,
+.control-group.error select,
+.control-group.error textarea {
+ color: #b94a48;
+}
+
+.control-group.error input,
+.control-group.error select,
+.control-group.error textarea {
+ border-color: #b94a48;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.control-group.error input:focus,
+.control-group.error select:focus,
+.control-group.error textarea:focus {
+ border-color: #953b39;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+}
+
+.control-group.error .input-prepend .add-on,
+.control-group.error .input-append .add-on {
+ color: #b94a48;
+ background-color: #f2dede;
+ border-color: #b94a48;
+}
+
+.control-group.success .control-label,
+.control-group.success .help-block,
+.control-group.success .help-inline {
+ color: #468847;
+}
+
+.control-group.success .checkbox,
+.control-group.success .radio,
+.control-group.success input,
+.control-group.success select,
+.control-group.success textarea {
+ color: #468847;
+}
+
+.control-group.success input,
+.control-group.success select,
+.control-group.success textarea {
+ border-color: #468847;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.control-group.success input:focus,
+.control-group.success select:focus,
+.control-group.success textarea:focus {
+ border-color: #356635;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+}
+
+.control-group.success .input-prepend .add-on,
+.control-group.success .input-append .add-on {
+ color: #468847;
+ background-color: #dff0d8;
+ border-color: #468847;
+}
+
+.control-group.info .control-label,
+.control-group.info .help-block,
+.control-group.info .help-inline {
+ color: #3a87ad;
+}
+
+.control-group.info .checkbox,
+.control-group.info .radio,
+.control-group.info input,
+.control-group.info select,
+.control-group.info textarea {
+ color: #3a87ad;
+}
+
+.control-group.info input,
+.control-group.info select,
+.control-group.info textarea {
+ border-color: #3a87ad;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.control-group.info input:focus,
+.control-group.info select:focus,
+.control-group.info textarea:focus {
+ border-color: #2d6987;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
+}
+
+.control-group.info .input-prepend .add-on,
+.control-group.info .input-append .add-on {
+ color: #3a87ad;
+ background-color: #d9edf7;
+ border-color: #3a87ad;
+}
+
+input:focus:invalid,
+textarea:focus:invalid,
+select:focus:invalid {
+ color: #b94a48;
+ border-color: #ee5f5b;
+}
+
+input:focus:invalid:focus,
+textarea:focus:invalid:focus,
+select:focus:invalid:focus {
+ border-color: #e9322d;
+ -webkit-box-shadow: 0 0 6px #f8b9b7;
+ -moz-box-shadow: 0 0 6px #f8b9b7;
+ box-shadow: 0 0 6px #f8b9b7;
+}
+
+.form-actions {
+ padding: 19px 20px 20px;
+ margin-top: 20px;
+ margin-bottom: 20px;
+ background-color: #f5f5f5;
+ border-top: 1px solid #e5e5e5;
+ *zoom: 1;
+}
+
+.form-actions:before,
+.form-actions:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.form-actions:after {
+ clear: both;
+}
+
+.help-block,
+.help-inline {
+ color: #595959;
+}
+
+.help-block {
+ display: block;
+ margin-bottom: 10px;
+}
+
+.help-inline {
+ display: inline-block;
+ *display: inline;
+ padding-left: 5px;
+ vertical-align: middle;
+ *zoom: 1;
+}
+
+.input-append,
+.input-prepend {
+ display: inline-block;
+ margin-bottom: 10px;
+ font-size: 0;
+ white-space: nowrap;
+ vertical-align: middle;
+}
+
+.input-append input,
+.input-prepend input,
+.input-append select,
+.input-prepend select,
+.input-append .uneditable-input,
+.input-prepend .uneditable-input,
+.input-append .dropdown-menu,
+.input-prepend .dropdown-menu,
+.input-append .popover,
+.input-prepend .popover {
+ font-size: 14px;
+}
+
+.input-append input,
+.input-prepend input,
+.input-append select,
+.input-prepend select,
+.input-append .uneditable-input,
+.input-prepend .uneditable-input {
+ position: relative;
+ margin-bottom: 0;
+ *margin-left: 0;
+ vertical-align: top;
+ -webkit-border-radius: 0 4px 4px 0;
+ -moz-border-radius: 0 4px 4px 0;
+ border-radius: 0 4px 4px 0;
+}
+
+.input-append input:focus,
+.input-prepend input:focus,
+.input-append select:focus,
+.input-prepend select:focus,
+.input-append .uneditable-input:focus,
+.input-prepend .uneditable-input:focus {
+ z-index: 2;
+}
+
+.input-append .add-on,
+.input-prepend .add-on {
+ display: inline-block;
+ width: auto;
+ height: 20px;
+ min-width: 16px;
+ padding: 4px 5px;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 20px;
+ text-align: center;
+ text-shadow: 0 1px 0 #ffffff;
+ background-color: #eeeeee;
+ border: 1px solid #ccc;
+}
+
+.input-append .add-on,
+.input-prepend .add-on,
+.input-append .btn,
+.input-prepend .btn,
+.input-append .btn-group > .dropdown-toggle,
+.input-prepend .btn-group > .dropdown-toggle {
+ vertical-align: top;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.input-append .active,
+.input-prepend .active {
+ background-color: #a9dba9;
+ border-color: #46a546;
+}
+
+.input-prepend .add-on,
+.input-prepend .btn {
+ margin-right: -1px;
+}
+
+.input-prepend .add-on:first-child,
+.input-prepend .btn:first-child {
+ -webkit-border-radius: 4px 0 0 4px;
+ -moz-border-radius: 4px 0 0 4px;
+ border-radius: 4px 0 0 4px;
+}
+
+.input-append input,
+.input-append select,
+.input-append .uneditable-input {
+ -webkit-border-radius: 4px 0 0 4px;
+ -moz-border-radius: 4px 0 0 4px;
+ border-radius: 4px 0 0 4px;
+}
+
+.input-append input + .btn-group .btn:last-child,
+.input-append select + .btn-group .btn:last-child,
+.input-append .uneditable-input + .btn-group .btn:last-child {
+ -webkit-border-radius: 0 4px 4px 0;
+ -moz-border-radius: 0 4px 4px 0;
+ border-radius: 0 4px 4px 0;
+}
+
+.input-append .add-on,
+.input-append .btn,
+.input-append .btn-group {
+ margin-left: -1px;
+}
+
+.input-append .add-on:last-child,
+.input-append .btn:last-child,
+.input-append .btn-group:last-child > .dropdown-toggle {
+ -webkit-border-radius: 0 4px 4px 0;
+ -moz-border-radius: 0 4px 4px 0;
+ border-radius: 0 4px 4px 0;
+}
+
+.input-prepend.input-append input,
+.input-prepend.input-append select,
+.input-prepend.input-append .uneditable-input {
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.input-prepend.input-append input + .btn-group .btn,
+.input-prepend.input-append select + .btn-group .btn,
+.input-prepend.input-append .uneditable-input + .btn-group .btn {
+ -webkit-border-radius: 0 4px 4px 0;
+ -moz-border-radius: 0 4px 4px 0;
+ border-radius: 0 4px 4px 0;
+}
+
+.input-prepend.input-append .add-on:first-child,
+.input-prepend.input-append .btn:first-child {
+ margin-right: -1px;
+ -webkit-border-radius: 4px 0 0 4px;
+ -moz-border-radius: 4px 0 0 4px;
+ border-radius: 4px 0 0 4px;
+}
+
+.input-prepend.input-append .add-on:last-child,
+.input-prepend.input-append .btn:last-child {
+ margin-left: -1px;
+ -webkit-border-radius: 0 4px 4px 0;
+ -moz-border-radius: 0 4px 4px 0;
+ border-radius: 0 4px 4px 0;
+}
+
+.input-prepend.input-append .btn-group:first-child {
+ margin-left: 0;
+}
+
+input.search-query {
+ padding-right: 14px;
+ padding-right: 4px \9;
+ padding-left: 14px;
+ padding-left: 4px \9;
+ /* IE7-8 doesn't have border-radius, so don't indent the padding */
+
+ margin-bottom: 0;
+ -webkit-border-radius: 15px;
+ -moz-border-radius: 15px;
+ border-radius: 15px;
+}
+
+/* Allow for input prepend/append in search forms */
+
+.form-search .input-append .search-query,
+.form-search .input-prepend .search-query {
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.form-search .input-append .search-query {
+ -webkit-border-radius: 14px 0 0 14px;
+ -moz-border-radius: 14px 0 0 14px;
+ border-radius: 14px 0 0 14px;
+}
+
+.form-search .input-append .btn {
+ -webkit-border-radius: 0 14px 14px 0;
+ -moz-border-radius: 0 14px 14px 0;
+ border-radius: 0 14px 14px 0;
+}
+
+.form-search .input-prepend .search-query {
+ -webkit-border-radius: 0 14px 14px 0;
+ -moz-border-radius: 0 14px 14px 0;
+ border-radius: 0 14px 14px 0;
+}
+
+.form-search .input-prepend .btn {
+ -webkit-border-radius: 14px 0 0 14px;
+ -moz-border-radius: 14px 0 0 14px;
+ border-radius: 14px 0 0 14px;
+}
+
+.form-search input,
+.form-inline input,
+.form-horizontal input,
+.form-search textarea,
+.form-inline textarea,
+.form-horizontal textarea,
+.form-search select,
+.form-inline select,
+.form-horizontal select,
+.form-search .help-inline,
+.form-inline .help-inline,
+.form-horizontal .help-inline,
+.form-search .uneditable-input,
+.form-inline .uneditable-input,
+.form-horizontal .uneditable-input,
+.form-search .input-prepend,
+.form-inline .input-prepend,
+.form-horizontal .input-prepend,
+.form-search .input-append,
+.form-inline .input-append,
+.form-horizontal .input-append {
+ display: inline-block;
+ *display: inline;
+ margin-bottom: 0;
+ vertical-align: middle;
+ *zoom: 1;
+}
+
+.form-search .hide,
+.form-inline .hide,
+.form-horizontal .hide {
+ display: none;
+}
+
+.form-search label,
+.form-inline label,
+.form-search .btn-group,
+.form-inline .btn-group {
+ display: inline-block;
+}
+
+.form-search .input-append,
+.form-inline .input-append,
+.form-search .input-prepend,
+.form-inline .input-prepend {
+ margin-bottom: 0;
+}
+
+.form-search .radio,
+.form-search .checkbox,
+.form-inline .radio,
+.form-inline .checkbox {
+ padding-left: 0;
+ margin-bottom: 0;
+ vertical-align: middle;
+}
+
+.form-search .radio input[type="radio"],
+.form-search .checkbox input[type="checkbox"],
+.form-inline .radio input[type="radio"],
+.form-inline .checkbox input[type="checkbox"] {
+ float: left;
+ margin-right: 3px;
+ margin-left: 0;
+}
+
+.control-group {
+ margin-bottom: 10px;
+}
+
+legend + .control-group {
+ margin-top: 20px;
+ -webkit-margin-top-collapse: separate;
+}
+
+.form-horizontal .control-group {
+ margin-bottom: 20px;
+ *zoom: 1;
+}
+
+.form-horizontal .control-group:before,
+.form-horizontal .control-group:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.form-horizontal .control-group:after {
+ clear: both;
+}
+
+.form-horizontal .control-label {
+ float: left;
+ width: 160px;
+ padding-top: 5px;
+ text-align: right;
+}
+
+.form-horizontal .controls {
+ *display: inline-block;
+ *padding-left: 20px;
+ margin-left: 180px;
+ *margin-left: 0;
+}
+
+.form-horizontal .controls:first-child {
+ *padding-left: 180px;
+}
+
+.form-horizontal .help-block {
+ margin-bottom: 0;
+}
+
+.form-horizontal input + .help-block,
+.form-horizontal select + .help-block,
+.form-horizontal textarea + .help-block,
+.form-horizontal .uneditable-input + .help-block,
+.form-horizontal .input-prepend + .help-block,
+.form-horizontal .input-append + .help-block {
+ margin-top: 10px;
+}
+
+.form-horizontal .form-actions {
+ padding-left: 180px;
+}
+
+table {
+ max-width: 100%;
+ background-color: transparent;
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+.table {
+ width: 100%;
+ margin-bottom: 20px;
+}
+
+.table th,
+.table td {
+ padding: 8px;
+ line-height: 20px;
+ text-align: left;
+ vertical-align: top;
+ border-top: 1px solid #dddddd;
+}
+
+.table th {
+ font-weight: bold;
+}
+
+.table thead th {
+ vertical-align: bottom;
+}
+
+.table caption + thead tr:first-child th,
+.table caption + thead tr:first-child td,
+.table colgroup + thead tr:first-child th,
+.table colgroup + thead tr:first-child td,
+.table thead:first-child tr:first-child th,
+.table thead:first-child tr:first-child td {
+ border-top: 0;
+}
+
+.table tbody + tbody {
+ border-top: 2px solid #dddddd;
+}
+
+.table .table {
+ background-color: #ffffff;
+}
+
+.table-condensed th,
+.table-condensed td {
+ padding: 4px 5px;
+}
+
+.table-bordered {
+ border: 1px solid #dddddd;
+ border-collapse: separate;
+ *border-collapse: collapse;
+ border-left: 0;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.table-bordered th,
+.table-bordered td {
+ border-left: 1px solid #dddddd;
+}
+
+.table-bordered caption + thead tr:first-child th,
+.table-bordered caption + tbody tr:first-child th,
+.table-bordered caption + tbody tr:first-child td,
+.table-bordered colgroup + thead tr:first-child th,
+.table-bordered colgroup + tbody tr:first-child th,
+.table-bordered colgroup + tbody tr:first-child td,
+.table-bordered thead:first-child tr:first-child th,
+.table-bordered tbody:first-child tr:first-child th,
+.table-bordered tbody:first-child tr:first-child td {
+ border-top: 0;
+}
+
+.table-bordered thead:first-child tr:first-child > th:first-child,
+.table-bordered tbody:first-child tr:first-child > td:first-child,
+.table-bordered tbody:first-child tr:first-child > th:first-child {
+ -webkit-border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
+ -moz-border-radius-topleft: 4px;
+}
+
+.table-bordered thead:first-child tr:first-child > th:last-child,
+.table-bordered tbody:first-child tr:first-child > td:last-child,
+.table-bordered tbody:first-child tr:first-child > th:last-child {
+ -webkit-border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
+ -moz-border-radius-topright: 4px;
+}
+
+.table-bordered thead:last-child tr:last-child > th:first-child,
+.table-bordered tbody:last-child tr:last-child > td:first-child,
+.table-bordered tbody:last-child tr:last-child > th:first-child,
+.table-bordered tfoot:last-child tr:last-child > td:first-child,
+.table-bordered tfoot:last-child tr:last-child > th:first-child {
+ -webkit-border-bottom-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ -moz-border-radius-bottomleft: 4px;
+}
+
+.table-bordered thead:last-child tr:last-child > th:last-child,
+.table-bordered tbody:last-child tr:last-child > td:last-child,
+.table-bordered tbody:last-child tr:last-child > th:last-child,
+.table-bordered tfoot:last-child tr:last-child > td:last-child,
+.table-bordered tfoot:last-child tr:last-child > th:last-child {
+ -webkit-border-bottom-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+ -moz-border-radius-bottomright: 4px;
+}
+
+.table-bordered tfoot + tbody:last-child tr:last-child td:first-child {
+ -webkit-border-bottom-left-radius: 0;
+ border-bottom-left-radius: 0;
+ -moz-border-radius-bottomleft: 0;
+}
+
+.table-bordered tfoot + tbody:last-child tr:last-child td:last-child {
+ -webkit-border-bottom-right-radius: 0;
+ border-bottom-right-radius: 0;
+ -moz-border-radius-bottomright: 0;
+}
+
+.table-bordered caption + thead tr:first-child th:first-child,
+.table-bordered caption + tbody tr:first-child td:first-child,
+.table-bordered colgroup + thead tr:first-child th:first-child,
+.table-bordered colgroup + tbody tr:first-child td:first-child {
+ -webkit-border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
+ -moz-border-radius-topleft: 4px;
+}
+
+.table-bordered caption + thead tr:first-child th:last-child,
+.table-bordered caption + tbody tr:first-child td:last-child,
+.table-bordered colgroup + thead tr:first-child th:last-child,
+.table-bordered colgroup + tbody tr:first-child td:last-child {
+ -webkit-border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
+ -moz-border-radius-topright: 4px;
+}
+
+.table-striped tbody > tr:nth-child(odd) > td,
+.table-striped tbody > tr:nth-child(odd) > th {
+ background-color: #f9f9f9;
+}
+
+.table-hover tbody tr:hover > td,
+.table-hover tbody tr:hover > th {
+ background-color: #f5f5f5;
+}
+
+table td[class*="span"],
+table th[class*="span"],
+.row-fluid table td[class*="span"],
+.row-fluid table th[class*="span"] {
+ display: table-cell;
+ float: none;
+ margin-left: 0;
+}
+
+.table td.span1,
+.table th.span1 {
+ float: none;
+ width: 44px;
+ margin-left: 0;
+}
+
+.table td.span2,
+.table th.span2 {
+ float: none;
+ width: 124px;
+ margin-left: 0;
+}
+
+.table td.span3,
+.table th.span3 {
+ float: none;
+ width: 204px;
+ margin-left: 0;
+}
+
+.table td.span4,
+.table th.span4 {
+ float: none;
+ width: 284px;
+ margin-left: 0;
+}
+
+.table td.span5,
+.table th.span5 {
+ float: none;
+ width: 364px;
+ margin-left: 0;
+}
+
+.table td.span6,
+.table th.span6 {
+ float: none;
+ width: 444px;
+ margin-left: 0;
+}
+
+.table td.span7,
+.table th.span7 {
+ float: none;
+ width: 524px;
+ margin-left: 0;
+}
+
+.table td.span8,
+.table th.span8 {
+ float: none;
+ width: 604px;
+ margin-left: 0;
+}
+
+.table td.span9,
+.table th.span9 {
+ float: none;
+ width: 684px;
+ margin-left: 0;
+}
+
+.table td.span10,
+.table th.span10 {
+ float: none;
+ width: 764px;
+ margin-left: 0;
+}
+
+.table td.span11,
+.table th.span11 {
+ float: none;
+ width: 844px;
+ margin-left: 0;
+}
+
+.table td.span12,
+.table th.span12 {
+ float: none;
+ width: 924px;
+ margin-left: 0;
+}
+
+.table tbody tr.success > td {
+ background-color: #dff0d8;
+}
+
+.table tbody tr.error > td {
+ background-color: #f2dede;
+}
+
+.table tbody tr.warning > td {
+ background-color: #fcf8e3;
+}
+
+.table tbody tr.info > td {
+ background-color: #d9edf7;
+}
+
+.table-hover tbody tr.success:hover > td {
+ background-color: #d0e9c6;
+}
+
+.table-hover tbody tr.error:hover > td {
+ background-color: #ebcccc;
+}
+
+.table-hover tbody tr.warning:hover > td {
+ background-color: #faf2cc;
+}
+
+.table-hover tbody tr.info:hover > td {
+ background-color: #c4e3f3;
+}
+
+[class^="icon-"],
+[class*=" icon-"] {
+ display: inline-block;
+ width: 14px;
+ height: 14px;
+ margin-top: 1px;
+ *margin-right: .3em;
+ line-height: 14px;
+ vertical-align: text-top;
+ background-image: url("../img/glyphicons-halflings.png");
+ background-position: 14px 14px;
+ background-repeat: no-repeat;
+}
+
+/* White icons with optional class, or on hover/focus/active states of certain elements */
+
+.icon-white,
+.nav-pills > .active > a > [class^="icon-"],
+.nav-pills > .active > a > [class*=" icon-"],
+.nav-list > .active > a > [class^="icon-"],
+.nav-list > .active > a > [class*=" icon-"],
+.navbar-inverse .nav > .active > a > [class^="icon-"],
+.navbar-inverse .nav > .active > a > [class*=" icon-"],
+.dropdown-menu > li > a:hover > [class^="icon-"],
+.dropdown-menu > li > a:focus > [class^="icon-"],
+.dropdown-menu > li > a:hover > [class*=" icon-"],
+.dropdown-menu > li > a:focus > [class*=" icon-"],
+.dropdown-menu > .active > a > [class^="icon-"],
+.dropdown-menu > .active > a > [class*=" icon-"],
+.dropdown-submenu:hover > a > [class^="icon-"],
+.dropdown-submenu:focus > a > [class^="icon-"],
+.dropdown-submenu:hover > a > [class*=" icon-"],
+.dropdown-submenu:focus > a > [class*=" icon-"] {
+ background-image: url("../img/glyphicons-halflings-white.png");
+}
+
+.icon-glass {
+ background-position: 0 0;
+}
+
+.icon-music {
+ background-position: -24px 0;
+}
+
+.icon-search {
+ background-position: -48px 0;
+}
+
+.icon-envelope {
+ background-position: -72px 0;
+}
+
+.icon-heart {
+ background-position: -96px 0;
+}
+
+.icon-star {
+ background-position: -120px 0;
+}
+
+.icon-star-empty {
+ background-position: -144px 0;
+}
+
+.icon-user {
+ background-position: -168px 0;
+}
+
+.icon-film {
+ background-position: -192px 0;
+}
+
+.icon-th-large {
+ background-position: -216px 0;
+}
+
+.icon-th {
+ background-position: -240px 0;
+}
+
+.icon-th-list {
+ background-position: -264px 0;
+}
+
+.icon-ok {
+ background-position: -288px 0;
+}
+
+.icon-remove {
+ background-position: -312px 0;
+}
+
+.icon-zoom-in {
+ background-position: -336px 0;
+}
+
+.icon-zoom-out {
+ background-position: -360px 0;
+}
+
+.icon-off {
+ background-position: -384px 0;
+}
+
+.icon-signal {
+ background-position: -408px 0;
+}
+
+.icon-cog {
+ background-position: -432px 0;
+}
+
+.icon-trash {
+ background-position: -456px 0;
+}
+
+.icon-home {
+ background-position: 0 -24px;
+}
+
+.icon-file {
+ background-position: -24px -24px;
+}
+
+.icon-time {
+ background-position: -48px -24px;
+}
+
+.icon-road {
+ background-position: -72px -24px;
+}
+
+.icon-download-alt {
+ background-position: -96px -24px;
+}
+
+.icon-download {
+ background-position: -120px -24px;
+}
+
+.icon-upload {
+ background-position: -144px -24px;
+}
+
+.icon-inbox {
+ background-position: -168px -24px;
+}
+
+.icon-play-circle {
+ background-position: -192px -24px;
+}
+
+.icon-repeat {
+ background-position: -216px -24px;
+}
+
+.icon-refresh {
+ background-position: -240px -24px;
+}
+
+.icon-list-alt {
+ background-position: -264px -24px;
+}
+
+.icon-lock {
+ background-position: -287px -24px;
+}
+
+.icon-flag {
+ background-position: -312px -24px;
+}
+
+.icon-headphones {
+ background-position: -336px -24px;
+}
+
+.icon-volume-off {
+ background-position: -360px -24px;
+}
+
+.icon-volume-down {
+ background-position: -384px -24px;
+}
+
+.icon-volume-up {
+ background-position: -408px -24px;
+}
+
+.icon-qrcode {
+ background-position: -432px -24px;
+}
+
+.icon-barcode {
+ background-position: -456px -24px;
+}
+
+.icon-tag {
+ background-position: 0 -48px;
+}
+
+.icon-tags {
+ background-position: -25px -48px;
+}
+
+.icon-book {
+ background-position: -48px -48px;
+}
+
+.icon-bookmark {
+ background-position: -72px -48px;
+}
+
+.icon-print {
+ background-position: -96px -48px;
+}
+
+.icon-camera {
+ background-position: -120px -48px;
+}
+
+.icon-font {
+ background-position: -144px -48px;
+}
+
+.icon-bold {
+ background-position: -167px -48px;
+}
+
+.icon-italic {
+ background-position: -192px -48px;
+}
+
+.icon-text-height {
+ background-position: -216px -48px;
+}
+
+.icon-text-width {
+ background-position: -240px -48px;
+}
+
+.icon-align-left {
+ background-position: -264px -48px;
+}
+
+.icon-align-center {
+ background-position: -288px -48px;
+}
+
+.icon-align-right {
+ background-position: -312px -48px;
+}
+
+.icon-align-justify {
+ background-position: -336px -48px;
+}
+
+.icon-list {
+ background-position: -360px -48px;
+}
+
+.icon-indent-left {
+ background-position: -384px -48px;
+}
+
+.icon-indent-right {
+ background-position: -408px -48px;
+}
+
+.icon-facetime-video {
+ background-position: -432px -48px;
+}
+
+.icon-picture {
+ background-position: -456px -48px;
+}
+
+.icon-pencil {
+ background-position: 0 -72px;
+}
+
+.icon-map-marker {
+ background-position: -24px -72px;
+}
+
+.icon-adjust {
+ background-position: -48px -72px;
+}
+
+.icon-tint {
+ background-position: -72px -72px;
+}
+
+.icon-edit {
+ background-position: -96px -72px;
+}
+
+.icon-share {
+ background-position: -120px -72px;
+}
+
+.icon-check {
+ background-position: -144px -72px;
+}
+
+.icon-move {
+ background-position: -168px -72px;
+}
+
+.icon-step-backward {
+ background-position: -192px -72px;
+}
+
+.icon-fast-backward {
+ background-position: -216px -72px;
+}
+
+.icon-backward {
+ background-position: -240px -72px;
+}
+
+.icon-play {
+ background-position: -264px -72px;
+}
+
+.icon-pause {
+ background-position: -288px -72px;
+}
+
+.icon-stop {
+ background-position: -312px -72px;
+}
+
+.icon-forward {
+ background-position: -336px -72px;
+}
+
+.icon-fast-forward {
+ background-position: -360px -72px;
+}
+
+.icon-step-forward {
+ background-position: -384px -72px;
+}
+
+.icon-eject {
+ background-position: -408px -72px;
+}
+
+.icon-chevron-left {
+ background-position: -432px -72px;
+}
+
+.icon-chevron-right {
+ background-position: -456px -72px;
+}
+
+.icon-plus-sign {
+ background-position: 0 -96px;
+}
+
+.icon-minus-sign {
+ background-position: -24px -96px;
+}
+
+.icon-remove-sign {
+ background-position: -48px -96px;
+}
+
+.icon-ok-sign {
+ background-position: -72px -96px;
+}
+
+.icon-question-sign {
+ background-position: -96px -96px;
+}
+
+.icon-info-sign {
+ background-position: -120px -96px;
+}
+
+.icon-screenshot {
+ background-position: -144px -96px;
+}
+
+.icon-remove-circle {
+ background-position: -168px -96px;
+}
+
+.icon-ok-circle {
+ background-position: -192px -96px;
+}
+
+.icon-ban-circle {
+ background-position: -216px -96px;
+}
+
+.icon-arrow-left {
+ background-position: -240px -96px;
+}
+
+.icon-arrow-right {
+ background-position: -264px -96px;
+}
+
+.icon-arrow-up {
+ background-position: -289px -96px;
+}
+
+.icon-arrow-down {
+ background-position: -312px -96px;
+}
+
+.icon-share-alt {
+ background-position: -336px -96px;
+}
+
+.icon-resize-full {
+ background-position: -360px -96px;
+}
+
+.icon-resize-small {
+ background-position: -384px -96px;
+}
+
+.icon-plus {
+ background-position: -408px -96px;
+}
+
+.icon-minus {
+ background-position: -433px -96px;
+}
+
+.icon-asterisk {
+ background-position: -456px -96px;
+}
+
+.icon-exclamation-sign {
+ background-position: 0 -120px;
+}
+
+.icon-gift {
+ background-position: -24px -120px;
+}
+
+.icon-leaf {
+ background-position: -48px -120px;
+}
+
+.icon-fire {
+ background-position: -72px -120px;
+}
+
+.icon-eye-open {
+ background-position: -96px -120px;
+}
+
+.icon-eye-close {
+ background-position: -120px -120px;
+}
+
+.icon-warning-sign {
+ background-position: -144px -120px;
+}
+
+.icon-plane {
+ background-position: -168px -120px;
+}
+
+.icon-calendar {
+ background-position: -192px -120px;
+}
+
+.icon-random {
+ width: 16px;
+ background-position: -216px -120px;
+}
+
+.icon-comment {
+ background-position: -240px -120px;
+}
+
+.icon-magnet {
+ background-position: -264px -120px;
+}
+
+.icon-chevron-up {
+ background-position: -288px -120px;
+}
+
+.icon-chevron-down {
+ background-position: -313px -119px;
+}
+
+.icon-retweet {
+ background-position: -336px -120px;
+}
+
+.icon-shopping-cart {
+ background-position: -360px -120px;
+}
+
+.icon-folder-close {
+ width: 16px;
+ background-position: -384px -120px;
+}
+
+.icon-folder-open {
+ width: 16px;
+ background-position: -408px -120px;
+}
+
+.icon-resize-vertical {
+ background-position: -432px -119px;
+}
+
+.icon-resize-horizontal {
+ background-position: -456px -118px;
+}
+
+.icon-hdd {
+ background-position: 0 -144px;
+}
+
+.icon-bullhorn {
+ background-position: -24px -144px;
+}
+
+.icon-bell {
+ background-position: -48px -144px;
+}
+
+.icon-certificate {
+ background-position: -72px -144px;
+}
+
+.icon-thumbs-up {
+ background-position: -96px -144px;
+}
+
+.icon-thumbs-down {
+ background-position: -120px -144px;
+}
+
+.icon-hand-right {
+ background-position: -144px -144px;
+}
+
+.icon-hand-left {
+ background-position: -168px -144px;
+}
+
+.icon-hand-up {
+ background-position: -192px -144px;
+}
+
+.icon-hand-down {
+ background-position: -216px -144px;
+}
+
+.icon-circle-arrow-right {
+ background-position: -240px -144px;
+}
+
+.icon-circle-arrow-left {
+ background-position: -264px -144px;
+}
+
+.icon-circle-arrow-up {
+ background-position: -288px -144px;
+}
+
+.icon-circle-arrow-down {
+ background-position: -312px -144px;
+}
+
+.icon-globe {
+ background-position: -336px -144px;
+}
+
+.icon-wrench {
+ background-position: -360px -144px;
+}
+
+.icon-tasks {
+ background-position: -384px -144px;
+}
+
+.icon-filter {
+ background-position: -408px -144px;
+}
+
+.icon-briefcase {
+ background-position: -432px -144px;
+}
+
+.icon-fullscreen {
+ background-position: -456px -144px;
+}
+
+.dropup,
+.dropdown {
+ position: relative;
+}
+
+.dropdown-toggle {
+ *margin-bottom: -3px;
+}
+
+.dropdown-toggle:active,
+.open .dropdown-toggle {
+ outline: 0;
+}
+
+.caret {
+ display: inline-block;
+ width: 0;
+ height: 0;
+ vertical-align: top;
+ border-top: 4px solid #000000;
+ border-right: 4px solid transparent;
+ border-left: 4px solid transparent;
+ content: "";
+}
+
+.dropdown .caret {
+ margin-top: 8px;
+ margin-left: 2px;
+}
+
+.dropdown-menu {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ z-index: 1000;
+ display: none;
+ float: left;
+ min-width: 160px;
+ padding: 5px 0;
+ margin: 2px 0 0;
+ list-style: none;
+ background-color: #ffffff;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ *border-right-width: 2px;
+ *border-bottom-width: 2px;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+ -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding;
+ background-clip: padding-box;
+}
+
+.dropdown-menu.pull-right {
+ right: 0;
+ left: auto;
+}
+
+.dropdown-menu .divider {
+ *width: 100%;
+ height: 1px;
+ margin: 9px 1px;
+ *margin: -5px 0 5px;
+ overflow: hidden;
+ background-color: #e5e5e5;
+ border-bottom: 1px solid #ffffff;
+}
+
+.dropdown-menu > li > a {
+ display: block;
+ padding: 3px 20px;
+ clear: both;
+ font-weight: normal;
+ line-height: 20px;
+ color: #333333;
+ white-space: nowrap;
+}
+
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus,
+.dropdown-submenu:hover > a,
+.dropdown-submenu:focus > a {
+ color: #ffffff;
+ text-decoration: none;
+ background-color: #0081c2;
+ background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
+ background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
+ background-image: -o-linear-gradient(top, #0088cc, #0077b3);
+ background-image: linear-gradient(to bottom, #0088cc, #0077b3);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);
+}
+
+.dropdown-menu > .active > a,
+.dropdown-menu > .active > a:hover,
+.dropdown-menu > .active > a:focus {
+ color: #ffffff;
+ text-decoration: none;
+ background-color: #0081c2;
+ background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
+ background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
+ background-image: -o-linear-gradient(top, #0088cc, #0077b3);
+ background-image: linear-gradient(to bottom, #0088cc, #0077b3);
+ background-repeat: repeat-x;
+ outline: 0;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);
+}
+
+.dropdown-menu > .disabled > a,
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+ color: #999999;
+}
+
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+ text-decoration: none;
+ cursor: default;
+ background-color: transparent;
+ background-image: none;
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.open {
+ *z-index: 1000;
+}
+
+.open > .dropdown-menu {
+ display: block;
+}
+
+.dropdown-backdrop {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 990;
+}
+
+.pull-right > .dropdown-menu {
+ right: 0;
+ left: auto;
+}
+
+.dropup .caret,
+.navbar-fixed-bottom .dropdown .caret {
+ border-top: 0;
+ border-bottom: 4px solid #000000;
+ content: "";
+}
+
+.dropup .dropdown-menu,
+.navbar-fixed-bottom .dropdown .dropdown-menu {
+ top: auto;
+ bottom: 100%;
+ margin-bottom: 1px;
+}
+
+.dropdown-submenu {
+ position: relative;
+}
+
+.dropdown-submenu > .dropdown-menu {
+ top: 0;
+ left: 100%;
+ margin-top: -6px;
+ margin-left: -1px;
+ -webkit-border-radius: 0 6px 6px 6px;
+ -moz-border-radius: 0 6px 6px 6px;
+ border-radius: 0 6px 6px 6px;
+}
+
+.dropdown-submenu:hover > .dropdown-menu {
+ display: block;
+}
+
+.dropup .dropdown-submenu > .dropdown-menu {
+ top: auto;
+ bottom: 0;
+ margin-top: 0;
+ margin-bottom: -2px;
+ -webkit-border-radius: 5px 5px 5px 0;
+ -moz-border-radius: 5px 5px 5px 0;
+ border-radius: 5px 5px 5px 0;
+}
+
+.dropdown-submenu > a:after {
+ display: block;
+ float: right;
+ width: 0;
+ height: 0;
+ margin-top: 5px;
+ margin-right: -10px;
+ border-color: transparent;
+ border-left-color: #cccccc;
+ border-style: solid;
+ border-width: 5px 0 5px 5px;
+ content: " ";
+}
+
+.dropdown-submenu:hover > a:after {
+ border-left-color: #ffffff;
+}
+
+.dropdown-submenu.pull-left {
+ float: none;
+}
+
+.dropdown-submenu.pull-left > .dropdown-menu {
+ left: -100%;
+ margin-left: 10px;
+ -webkit-border-radius: 6px 0 6px 6px;
+ -moz-border-radius: 6px 0 6px 6px;
+ border-radius: 6px 0 6px 6px;
+}
+
+.dropdown .dropdown-menu .nav-header {
+ padding-right: 20px;
+ padding-left: 20px;
+}
+
+.typeahead {
+ z-index: 1051;
+ margin-top: 2px;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.well {
+ min-height: 20px;
+ padding: 19px;
+ margin-bottom: 20px;
+ background-color: #f5f5f5;
+ border: 1px solid #e3e3e3;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+
+.well blockquote {
+ border-color: #ddd;
+ border-color: rgba(0, 0, 0, 0.15);
+}
+
+.well-large {
+ padding: 24px;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+}
+
+.well-small {
+ padding: 9px;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+
+.fade {
+ opacity: 0;
+ -webkit-transition: opacity 0.15s linear;
+ -moz-transition: opacity 0.15s linear;
+ -o-transition: opacity 0.15s linear;
+ transition: opacity 0.15s linear;
+}
+
+.fade.in {
+ opacity: 1;
+}
+
+.collapse {
+ position: relative;
+ height: 0;
+ overflow: hidden;
+ -webkit-transition: height 0.35s ease;
+ -moz-transition: height 0.35s ease;
+ -o-transition: height 0.35s ease;
+ transition: height 0.35s ease;
+}
+
+.collapse.in {
+ height: auto;
+}
+
+.close {
+ float: right;
+ font-size: 20px;
+ font-weight: bold;
+ line-height: 20px;
+ color: #000000;
+ text-shadow: 0 1px 0 #ffffff;
+ opacity: 0.2;
+ filter: alpha(opacity=20);
+}
+
+.close:hover,
+.close:focus {
+ color: #000000;
+ text-decoration: none;
+ cursor: pointer;
+ opacity: 0.4;
+ filter: alpha(opacity=40);
+}
+
+button.close {
+ padding: 0;
+ cursor: pointer;
+ background: transparent;
+ border: 0;
+ -webkit-appearance: none;
+}
+
+.btn {
+ display: inline-block;
+ *display: inline;
+ padding: 4px 12px;
+ margin-bottom: 0;
+ *margin-left: .3em;
+ font-size: 14px;
+ line-height: 20px;
+ color: #333333;
+ text-align: center;
+ text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
+ vertical-align: middle;
+ cursor: pointer;
+ background-color: #f5f5f5;
+ *background-color: #e6e6e6;
+ background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
+ background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
+ background-repeat: repeat-x;
+ border: 1px solid #cccccc;
+ *border: 0;
+ border-color: #e6e6e6 #e6e6e6 #bfbfbf;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ border-bottom-color: #b3b3b3;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+ *zoom: 1;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn:hover,
+.btn:focus,
+.btn:active,
+.btn.active,
+.btn.disabled,
+.btn[disabled] {
+ color: #333333;
+ background-color: #e6e6e6;
+ *background-color: #d9d9d9;
+}
+
+.btn:active,
+.btn.active {
+ background-color: #cccccc \9;
+}
+
+.btn:first-child {
+ *margin-left: 0;
+}
+
+.btn:hover,
+.btn:focus {
+ color: #333333;
+ text-decoration: none;
+ background-position: 0 -15px;
+ -webkit-transition: background-position 0.1s linear;
+ -moz-transition: background-position 0.1s linear;
+ -o-transition: background-position 0.1s linear;
+ transition: background-position 0.1s linear;
+}
+
+.btn:focus {
+ outline: thin dotted #333;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+
+.btn.active,
+.btn:active {
+ background-image: none;
+ outline: 0;
+ -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn.disabled,
+.btn[disabled] {
+ cursor: default;
+ background-image: none;
+ opacity: 0.65;
+ filter: alpha(opacity=65);
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+}
+
+.btn-large {
+ padding: 11px 19px;
+ font-size: 17.5px;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+}
+
+.btn-large [class^="icon-"],
+.btn-large [class*=" icon-"] {
+ margin-top: 4px;
+}
+
+.btn-small {
+ padding: 2px 10px;
+ font-size: 11.9px;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+
+.btn-small [class^="icon-"],
+.btn-small [class*=" icon-"] {
+ margin-top: 0;
+}
+
+.btn-mini [class^="icon-"],
+.btn-mini [class*=" icon-"] {
+ margin-top: -1px;
+}
+
+.btn-mini {
+ padding: 0 6px;
+ font-size: 10.5px;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+
+.btn-block {
+ display: block;
+ width: 100%;
+ padding-right: 0;
+ padding-left: 0;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+.btn-block + .btn-block {
+ margin-top: 5px;
+}
+
+input[type="submit"].btn-block,
+input[type="reset"].btn-block,
+input[type="button"].btn-block {
+ width: 100%;
+}
+
+.btn-primary.active,
+.btn-warning.active,
+.btn-danger.active,
+.btn-success.active,
+.btn-info.active,
+.btn-inverse.active {
+ color: rgba(255, 255, 255, 0.75);
+}
+
+.btn-primary {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #006dcc;
+ *background-color: #0044cc;
+ background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
+ background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
+ background-image: -o-linear-gradient(top, #0088cc, #0044cc);
+ background-image: linear-gradient(to bottom, #0088cc, #0044cc);
+ background-repeat: repeat-x;
+ border-color: #0044cc #0044cc #002a80;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-primary:hover,
+.btn-primary:focus,
+.btn-primary:active,
+.btn-primary.active,
+.btn-primary.disabled,
+.btn-primary[disabled] {
+ color: #ffffff;
+ background-color: #0044cc;
+ *background-color: #003bb3;
+}
+
+.btn-primary:active,
+.btn-primary.active {
+ background-color: #003399 \9;
+}
+
+.btn-warning {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #faa732;
+ *background-color: #f89406;
+ background-image: -moz-linear-gradient(top, #fbb450, #f89406);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));
+ background-image: -webkit-linear-gradient(top, #fbb450, #f89406);
+ background-image: -o-linear-gradient(top, #fbb450, #f89406);
+ background-image: linear-gradient(to bottom, #fbb450, #f89406);
+ background-repeat: repeat-x;
+ border-color: #f89406 #f89406 #ad6704;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-warning:hover,
+.btn-warning:focus,
+.btn-warning:active,
+.btn-warning.active,
+.btn-warning.disabled,
+.btn-warning[disabled] {
+ color: #ffffff;
+ background-color: #f89406;
+ *background-color: #df8505;
+}
+
+.btn-warning:active,
+.btn-warning.active {
+ background-color: #c67605 \9;
+}
+
+.btn-danger {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #da4f49;
+ *background-color: #bd362f;
+ background-image: -moz-linear-gradient(top, #ee5f5b, #bd362f);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));
+ background-image: -webkit-linear-gradient(top, #ee5f5b, #bd362f);
+ background-image: -o-linear-gradient(top, #ee5f5b, #bd362f);
+ background-image: linear-gradient(to bottom, #ee5f5b, #bd362f);
+ background-repeat: repeat-x;
+ border-color: #bd362f #bd362f #802420;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-danger:hover,
+.btn-danger:focus,
+.btn-danger:active,
+.btn-danger.active,
+.btn-danger.disabled,
+.btn-danger[disabled] {
+ color: #ffffff;
+ background-color: #bd362f;
+ *background-color: #a9302a;
+}
+
+.btn-danger:active,
+.btn-danger.active {
+ background-color: #942a25 \9;
+}
+
+.btn-success {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #5bb75b;
+ *background-color: #51a351;
+ background-image: -moz-linear-gradient(top, #62c462, #51a351);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));
+ background-image: -webkit-linear-gradient(top, #62c462, #51a351);
+ background-image: -o-linear-gradient(top, #62c462, #51a351);
+ background-image: linear-gradient(to bottom, #62c462, #51a351);
+ background-repeat: repeat-x;
+ border-color: #51a351 #51a351 #387038;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff51a351', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-success:hover,
+.btn-success:focus,
+.btn-success:active,
+.btn-success.active,
+.btn-success.disabled,
+.btn-success[disabled] {
+ color: #ffffff;
+ background-color: #51a351;
+ *background-color: #499249;
+}
+
+.btn-success:active,
+.btn-success.active {
+ background-color: #408140 \9;
+}
+
+.btn-info {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #49afcd;
+ *background-color: #2f96b4;
+ background-image: -moz-linear-gradient(top, #5bc0de, #2f96b4);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));
+ background-image: -webkit-linear-gradient(top, #5bc0de, #2f96b4);
+ background-image: -o-linear-gradient(top, #5bc0de, #2f96b4);
+ background-image: linear-gradient(to bottom, #5bc0de, #2f96b4);
+ background-repeat: repeat-x;
+ border-color: #2f96b4 #2f96b4 #1f6377;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2f96b4', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-info:hover,
+.btn-info:focus,
+.btn-info:active,
+.btn-info.active,
+.btn-info.disabled,
+.btn-info[disabled] {
+ color: #ffffff;
+ background-color: #2f96b4;
+ *background-color: #2a85a0;
+}
+
+.btn-info:active,
+.btn-info.active {
+ background-color: #24748c \9;
+}
+
+.btn-inverse {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #363636;
+ *background-color: #222222;
+ background-image: -moz-linear-gradient(top, #444444, #222222);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#444444), to(#222222));
+ background-image: -webkit-linear-gradient(top, #444444, #222222);
+ background-image: -o-linear-gradient(top, #444444, #222222);
+ background-image: linear-gradient(to bottom, #444444, #222222);
+ background-repeat: repeat-x;
+ border-color: #222222 #222222 #000000;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444', endColorstr='#ff222222', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-inverse:hover,
+.btn-inverse:focus,
+.btn-inverse:active,
+.btn-inverse.active,
+.btn-inverse.disabled,
+.btn-inverse[disabled] {
+ color: #ffffff;
+ background-color: #222222;
+ *background-color: #151515;
+}
+
+.btn-inverse:active,
+.btn-inverse.active {
+ background-color: #080808 \9;
+}
+
+button.btn,
+input[type="submit"].btn {
+ *padding-top: 3px;
+ *padding-bottom: 3px;
+}
+
+button.btn::-moz-focus-inner,
+input[type="submit"].btn::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+}
+
+button.btn.btn-large,
+input[type="submit"].btn.btn-large {
+ *padding-top: 7px;
+ *padding-bottom: 7px;
+}
+
+button.btn.btn-small,
+input[type="submit"].btn.btn-small {
+ *padding-top: 3px;
+ *padding-bottom: 3px;
+}
+
+button.btn.btn-mini,
+input[type="submit"].btn.btn-mini {
+ *padding-top: 1px;
+ *padding-bottom: 1px;
+}
+
+.btn-link,
+.btn-link:active,
+.btn-link[disabled] {
+ background-color: transparent;
+ background-image: none;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+}
+
+.btn-link {
+ color: #0088cc;
+ cursor: pointer;
+ border-color: transparent;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.btn-link:hover,
+.btn-link:focus {
+ color: #005580;
+ text-decoration: underline;
+ background-color: transparent;
+}
+
+.btn-link[disabled]:hover,
+.btn-link[disabled]:focus {
+ color: #333333;
+ text-decoration: none;
+}
+
+.btn-group {
+ position: relative;
+ display: inline-block;
+ *display: inline;
+ *margin-left: .3em;
+ font-size: 0;
+ white-space: nowrap;
+ vertical-align: middle;
+ *zoom: 1;
+}
+
+.btn-group:first-child {
+ *margin-left: 0;
+}
+
+.btn-group + .btn-group {
+ margin-left: 5px;
+}
+
+.btn-toolbar {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ font-size: 0;
+}
+
+.btn-toolbar > .btn + .btn,
+.btn-toolbar > .btn-group + .btn,
+.btn-toolbar > .btn + .btn-group {
+ margin-left: 5px;
+}
+
+.btn-group > .btn {
+ position: relative;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.btn-group > .btn + .btn {
+ margin-left: -1px;
+}
+
+.btn-group > .btn,
+.btn-group > .dropdown-menu,
+.btn-group > .popover {
+ font-size: 14px;
+}
+
+.btn-group > .btn-mini {
+ font-size: 10.5px;
+}
+
+.btn-group > .btn-small {
+ font-size: 11.9px;
+}
+
+.btn-group > .btn-large {
+ font-size: 17.5px;
+}
+
+.btn-group > .btn:first-child {
+ margin-left: 0;
+ -webkit-border-bottom-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ -webkit-border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
+ -moz-border-radius-bottomleft: 4px;
+ -moz-border-radius-topleft: 4px;
+}
+
+.btn-group > .btn:last-child,
+.btn-group > .dropdown-toggle {
+ -webkit-border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
+ -webkit-border-bottom-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+ -moz-border-radius-topright: 4px;
+ -moz-border-radius-bottomright: 4px;
+}
+
+.btn-group > .btn.large:first-child {
+ margin-left: 0;
+ -webkit-border-bottom-left-radius: 6px;
+ border-bottom-left-radius: 6px;
+ -webkit-border-top-left-radius: 6px;
+ border-top-left-radius: 6px;
+ -moz-border-radius-bottomleft: 6px;
+ -moz-border-radius-topleft: 6px;
+}
+
+.btn-group > .btn.large:last-child,
+.btn-group > .large.dropdown-toggle {
+ -webkit-border-top-right-radius: 6px;
+ border-top-right-radius: 6px;
+ -webkit-border-bottom-right-radius: 6px;
+ border-bottom-right-radius: 6px;
+ -moz-border-radius-topright: 6px;
+ -moz-border-radius-bottomright: 6px;
+}
+
+.btn-group > .btn:hover,
+.btn-group > .btn:focus,
+.btn-group > .btn:active,
+.btn-group > .btn.active {
+ z-index: 2;
+}
+
+.btn-group .dropdown-toggle:active,
+.btn-group.open .dropdown-toggle {
+ outline: 0;
+}
+
+.btn-group > .btn + .dropdown-toggle {
+ *padding-top: 5px;
+ padding-right: 8px;
+ *padding-bottom: 5px;
+ padding-left: 8px;
+ -webkit-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn-group > .btn-mini + .dropdown-toggle {
+ *padding-top: 2px;
+ padding-right: 5px;
+ *padding-bottom: 2px;
+ padding-left: 5px;
+}
+
+.btn-group > .btn-small + .dropdown-toggle {
+ *padding-top: 5px;
+ *padding-bottom: 4px;
+}
+
+.btn-group > .btn-large + .dropdown-toggle {
+ *padding-top: 7px;
+ padding-right: 12px;
+ *padding-bottom: 7px;
+ padding-left: 12px;
+}
+
+.btn-group.open .dropdown-toggle {
+ background-image: none;
+ -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn-group.open .btn.dropdown-toggle {
+ background-color: #e6e6e6;
+}
+
+.btn-group.open .btn-primary.dropdown-toggle {
+ background-color: #0044cc;
+}
+
+.btn-group.open .btn-warning.dropdown-toggle {
+ background-color: #f89406;
+}
+
+.btn-group.open .btn-danger.dropdown-toggle {
+ background-color: #bd362f;
+}
+
+.btn-group.open .btn-success.dropdown-toggle {
+ background-color: #51a351;
+}
+
+.btn-group.open .btn-info.dropdown-toggle {
+ background-color: #2f96b4;
+}
+
+.btn-group.open .btn-inverse.dropdown-toggle {
+ background-color: #222222;
+}
+
+.btn .caret {
+ margin-top: 8px;
+ margin-left: 0;
+}
+
+.btn-large .caret {
+ margin-top: 6px;
+}
+
+.btn-large .caret {
+ border-top-width: 5px;
+ border-right-width: 5px;
+ border-left-width: 5px;
+}
+
+.btn-mini .caret,
+.btn-small .caret {
+ margin-top: 8px;
+}
+
+.dropup .btn-large .caret {
+ border-bottom-width: 5px;
+}
+
+.btn-primary .caret,
+.btn-warning .caret,
+.btn-danger .caret,
+.btn-info .caret,
+.btn-success .caret,
+.btn-inverse .caret {
+ border-top-color: #ffffff;
+ border-bottom-color: #ffffff;
+}
+
+.btn-group-vertical {
+ display: inline-block;
+ *display: inline;
+ /* IE7 inline-block hack */
+
+ *zoom: 1;
+}
+
+.btn-group-vertical > .btn {
+ display: block;
+ float: none;
+ max-width: 100%;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.btn-group-vertical > .btn + .btn {
+ margin-top: -1px;
+ margin-left: 0;
+}
+
+.btn-group-vertical > .btn:first-child {
+ -webkit-border-radius: 4px 4px 0 0;
+ -moz-border-radius: 4px 4px 0 0;
+ border-radius: 4px 4px 0 0;
+}
+
+.btn-group-vertical > .btn:last-child {
+ -webkit-border-radius: 0 0 4px 4px;
+ -moz-border-radius: 0 0 4px 4px;
+ border-radius: 0 0 4px 4px;
+}
+
+.btn-group-vertical > .btn-large:first-child {
+ -webkit-border-radius: 6px 6px 0 0;
+ -moz-border-radius: 6px 6px 0 0;
+ border-radius: 6px 6px 0 0;
+}
+
+.btn-group-vertical > .btn-large:last-child {
+ -webkit-border-radius: 0 0 6px 6px;
+ -moz-border-radius: 0 0 6px 6px;
+ border-radius: 0 0 6px 6px;
+}
+
+.alert {
+ padding: 8px 35px 8px 14px;
+ margin-bottom: 20px;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+ background-color: #fcf8e3;
+ border: 1px solid #fbeed5;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.alert,
+.alert h4 {
+ color: #c09853;
+}
+
+.alert h4 {
+ margin: 0;
+}
+
+.alert .close {
+ position: relative;
+ top: -2px;
+ right: -21px;
+ line-height: 20px;
+}
+
+.alert-success {
+ color: #468847;
+ background-color: #dff0d8;
+ border-color: #d6e9c6;
+}
+
+.alert-success h4 {
+ color: #468847;
+}
+
+.alert-danger,
+.alert-error {
+ color: #b94a48;
+ background-color: #f2dede;
+ border-color: #eed3d7;
+}
+
+.alert-danger h4,
+.alert-error h4 {
+ color: #b94a48;
+}
+
+.alert-info {
+ color: #3a87ad;
+ background-color: #d9edf7;
+ border-color: #bce8f1;
+}
+
+.alert-info h4 {
+ color: #3a87ad;
+}
+
+.alert-block {
+ padding-top: 14px;
+ padding-bottom: 14px;
+}
+
+.alert-block > p,
+.alert-block > ul {
+ margin-bottom: 0;
+}
+
+.alert-block p + p {
+ margin-top: 5px;
+}
+
+.nav {
+ margin-bottom: 20px;
+ margin-left: 0;
+ list-style: none;
+}
+
+.nav > li > a {
+ display: block;
+}
+
+.nav > li > a:hover,
+.nav > li > a:focus {
+ text-decoration: none;
+ background-color: #eeeeee;
+}
+
+.nav > li > a > img {
+ max-width: none;
+}
+
+.nav > .pull-right {
+ float: right;
+}
+
+.nav-header {
+ display: block;
+ padding: 3px 15px;
+ font-size: 11px;
+ font-weight: bold;
+ line-height: 20px;
+ color: #999999;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+ text-transform: uppercase;
+}
+
+.nav li + .nav-header {
+ margin-top: 9px;
+}
+
+.nav-list {
+ padding-right: 15px;
+ padding-left: 15px;
+ margin-bottom: 0;
+}
+
+.nav-list > li > a,
+.nav-list .nav-header {
+ margin-right: -15px;
+ margin-left: -15px;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+}
+
+.nav-list > li > a {
+ padding: 3px 15px;
+}
+
+.nav-list > .active > a,
+.nav-list > .active > a:hover,
+.nav-list > .active > a:focus {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
+ background-color: #0088cc;
+}
+
+.nav-list [class^="icon-"],
+.nav-list [class*=" icon-"] {
+ margin-right: 2px;
+}
+
+.nav-list .divider {
+ *width: 100%;
+ height: 1px;
+ margin: 9px 1px;
+ *margin: -5px 0 5px;
+ overflow: hidden;
+ background-color: #e5e5e5;
+ border-bottom: 1px solid #ffffff;
+}
+
+.nav-tabs,
+.nav-pills {
+ *zoom: 1;
+}
+
+.nav-tabs:before,
+.nav-pills:before,
+.nav-tabs:after,
+.nav-pills:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.nav-tabs:after,
+.nav-pills:after {
+ clear: both;
+}
+
+.nav-tabs > li,
+.nav-pills > li {
+ float: left;
+}
+
+.nav-tabs > li > a,
+.nav-pills > li > a {
+ padding-right: 12px;
+ padding-left: 12px;
+ margin-right: 2px;
+ line-height: 14px;
+}
+
+.nav-tabs {
+ border-bottom: 1px solid #ddd;
+}
+
+.nav-tabs > li {
+ margin-bottom: -1px;
+}
+
+.nav-tabs > li > a {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ line-height: 20px;
+ border: 1px solid transparent;
+ -webkit-border-radius: 4px 4px 0 0;
+ -moz-border-radius: 4px 4px 0 0;
+ border-radius: 4px 4px 0 0;
+}
+
+.nav-tabs > li > a:hover,
+.nav-tabs > li > a:focus {
+ border-color: #eeeeee #eeeeee #dddddd;
+}
+
+.nav-tabs > .active > a,
+.nav-tabs > .active > a:hover,
+.nav-tabs > .active > a:focus {
+ color: #555555;
+ cursor: default;
+ background-color: #ffffff;
+ border: 1px solid #ddd;
+ border-bottom-color: transparent;
+}
+
+.nav-pills > li > a {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ margin-top: 2px;
+ margin-bottom: 2px;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+}
+
+.nav-pills > .active > a,
+.nav-pills > .active > a:hover,
+.nav-pills > .active > a:focus {
+ color: #ffffff;
+ background-color: #0088cc;
+}
+
+.nav-stacked > li {
+ float: none;
+}
+
+.nav-stacked > li > a {
+ margin-right: 0;
+}
+
+.nav-tabs.nav-stacked {
+ border-bottom: 0;
+}
+
+.nav-tabs.nav-stacked > li > a {
+ border: 1px solid #ddd;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.nav-tabs.nav-stacked > li:first-child > a {
+ -webkit-border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
+ -webkit-border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
+ -moz-border-radius-topright: 4px;
+ -moz-border-radius-topleft: 4px;
+}
+
+.nav-tabs.nav-stacked > li:last-child > a {
+ -webkit-border-bottom-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+ -webkit-border-bottom-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ -moz-border-radius-bottomright: 4px;
+ -moz-border-radius-bottomleft: 4px;
+}
+
+.nav-tabs.nav-stacked > li > a:hover,
+.nav-tabs.nav-stacked > li > a:focus {
+ z-index: 2;
+ border-color: #ddd;
+}
+
+.nav-pills.nav-stacked > li > a {
+ margin-bottom: 3px;
+}
+
+.nav-pills.nav-stacked > li:last-child > a {
+ margin-bottom: 1px;
+}
+
+.nav-tabs .dropdown-menu {
+ -webkit-border-radius: 0 0 6px 6px;
+ -moz-border-radius: 0 0 6px 6px;
+ border-radius: 0 0 6px 6px;
+}
+
+.nav-pills .dropdown-menu {
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+}
+
+.nav .dropdown-toggle .caret {
+ margin-top: 6px;
+ border-top-color: #0088cc;
+ border-bottom-color: #0088cc;
+}
+
+.nav .dropdown-toggle:hover .caret,
+.nav .dropdown-toggle:focus .caret {
+ border-top-color: #005580;
+ border-bottom-color: #005580;
+}
+
+/* move down carets for tabs */
+
+.nav-tabs .dropdown-toggle .caret {
+ margin-top: 8px;
+}
+
+.nav .active .dropdown-toggle .caret {
+ border-top-color: #fff;
+ border-bottom-color: #fff;
+}
+
+.nav-tabs .active .dropdown-toggle .caret {
+ border-top-color: #555555;
+ border-bottom-color: #555555;
+}
+
+.nav > .dropdown.active > a:hover,
+.nav > .dropdown.active > a:focus {
+ cursor: pointer;
+}
+
+.nav-tabs .open .dropdown-toggle,
+.nav-pills .open .dropdown-toggle,
+.nav > li.dropdown.open.active > a:hover,
+.nav > li.dropdown.open.active > a:focus {
+ color: #ffffff;
+ background-color: #999999;
+ border-color: #999999;
+}
+
+.nav li.dropdown.open .caret,
+.nav li.dropdown.open.active .caret,
+.nav li.dropdown.open a:hover .caret,
+.nav li.dropdown.open a:focus .caret {
+ border-top-color: #ffffff;
+ border-bottom-color: #ffffff;
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+
+.tabs-stacked .open > a:hover,
+.tabs-stacked .open > a:focus {
+ border-color: #999999;
+}
+
+.tabbable {
+ *zoom: 1;
+}
+
+.tabbable:before,
+.tabbable:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.tabbable:after {
+ clear: both;
+}
+
+.tab-content {
+ overflow: auto;
+}
+
+.tabs-below > .nav-tabs,
+.tabs-right > .nav-tabs,
+.tabs-left > .nav-tabs {
+ border-bottom: 0;
+}
+
+.tab-content > .tab-pane,
+.pill-content > .pill-pane {
+ display: none;
+}
+
+.tab-content > .active,
+.pill-content > .active {
+ display: block;
+}
+
+.tabs-below > .nav-tabs {
+ border-top: 1px solid #ddd;
+}
+
+.tabs-below > .nav-tabs > li {
+ margin-top: -1px;
+ margin-bottom: 0;
+}
+
+.tabs-below > .nav-tabs > li > a {
+ -webkit-border-radius: 0 0 4px 4px;
+ -moz-border-radius: 0 0 4px 4px;
+ border-radius: 0 0 4px 4px;
+}
+
+.tabs-below > .nav-tabs > li > a:hover,
+.tabs-below > .nav-tabs > li > a:focus {
+ border-top-color: #ddd;
+ border-bottom-color: transparent;
+}
+
+.tabs-below > .nav-tabs > .active > a,
+.tabs-below > .nav-tabs > .active > a:hover,
+.tabs-below > .nav-tabs > .active > a:focus {
+ border-color: transparent #ddd #ddd #ddd;
+}
+
+.tabs-left > .nav-tabs > li,
+.tabs-right > .nav-tabs > li {
+ float: none;
+}
+
+.tabs-left > .nav-tabs > li > a,
+.tabs-right > .nav-tabs > li > a {
+ min-width: 74px;
+ margin-right: 0;
+ margin-bottom: 3px;
+}
+
+.tabs-left > .nav-tabs {
+ float: left;
+ margin-right: 19px;
+ border-right: 1px solid #ddd;
+}
+
+.tabs-left > .nav-tabs > li > a {
+ margin-right: -1px;
+ -webkit-border-radius: 4px 0 0 4px;
+ -moz-border-radius: 4px 0 0 4px;
+ border-radius: 4px 0 0 4px;
+}
+
+.tabs-left > .nav-tabs > li > a:hover,
+.tabs-left > .nav-tabs > li > a:focus {
+ border-color: #eeeeee #dddddd #eeeeee #eeeeee;
+}
+
+.tabs-left > .nav-tabs .active > a,
+.tabs-left > .nav-tabs .active > a:hover,
+.tabs-left > .nav-tabs .active > a:focus {
+ border-color: #ddd transparent #ddd #ddd;
+ *border-right-color: #ffffff;
+}
+
+.tabs-right > .nav-tabs {
+ float: right;
+ margin-left: 19px;
+ border-left: 1px solid #ddd;
+}
+
+.tabs-right > .nav-tabs > li > a {
+ margin-left: -1px;
+ -webkit-border-radius: 0 4px 4px 0;
+ -moz-border-radius: 0 4px 4px 0;
+ border-radius: 0 4px 4px 0;
+}
+
+.tabs-right > .nav-tabs > li > a:hover,
+.tabs-right > .nav-tabs > li > a:focus {
+ border-color: #eeeeee #eeeeee #eeeeee #dddddd;
+}
+
+.tabs-right > .nav-tabs .active > a,
+.tabs-right > .nav-tabs .active > a:hover,
+.tabs-right > .nav-tabs .active > a:focus {
+ border-color: #ddd #ddd #ddd transparent;
+ *border-left-color: #ffffff;
+}
+
+.nav > .disabled > a {
+ color: #999999;
+}
+
+.nav > .disabled > a:hover,
+.nav > .disabled > a:focus {
+ text-decoration: none;
+ cursor: default;
+ background-color: transparent;
+}
+
+.navbar {
+ *position: relative;
+ *z-index: 2;
+ margin-bottom: 20px;
+ overflow: visible;
+}
+
+.navbar-inner {
+ min-height: 40px;
+ padding-right: 20px;
+ padding-left: 20px;
+ background-color: #fafafa;
+ background-image: -moz-linear-gradient(top, #ffffff, #f2f2f2);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f2f2f2));
+ background-image: -webkit-linear-gradient(top, #ffffff, #f2f2f2);
+ background-image: -o-linear-gradient(top, #ffffff, #f2f2f2);
+ background-image: linear-gradient(to bottom, #ffffff, #f2f2f2);
+ background-repeat: repeat-x;
+ border: 1px solid #d4d4d4;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0);
+ *zoom: 1;
+ -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
+ -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
+}
+
+.navbar-inner:before,
+.navbar-inner:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.navbar-inner:after {
+ clear: both;
+}
+
+.navbar .container {
+ width: auto;
+}
+
+.nav-collapse.collapse {
+ height: auto;
+ overflow: visible;
+}
+
+.navbar .brand {
+ display: block;
+ float: left;
+ padding: 10px 20px 10px;
+ margin-left: -20px;
+ font-size: 20px;
+ font-weight: 200;
+ color: #777777;
+ text-shadow: 0 1px 0 #ffffff;
+}
+
+.navbar .brand:hover,
+.navbar .brand:focus {
+ text-decoration: none;
+}
+
+.navbar-text {
+ margin-bottom: 0;
+ line-height: 40px;
+ color: #777777;
+}
+
+.navbar-link {
+ color: #777777;
+}
+
+.navbar-link:hover,
+.navbar-link:focus {
+ color: #333333;
+}
+
+.navbar .divider-vertical {
+ height: 40px;
+ margin: 0 9px;
+ border-right: 1px solid #ffffff;
+ border-left: 1px solid #f2f2f2;
+}
+
+.navbar .btn,
+.navbar .btn-group {
+ margin-top: 5px;
+}
+
+.navbar .btn-group .btn,
+.navbar .input-prepend .btn,
+.navbar .input-append .btn,
+.navbar .input-prepend .btn-group,
+.navbar .input-append .btn-group {
+ margin-top: 0;
+}
+
+.navbar-form {
+ margin-bottom: 0;
+ *zoom: 1;
+}
+
+.navbar-form:before,
+.navbar-form:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.navbar-form:after {
+ clear: both;
+}
+
+.navbar-form input,
+.navbar-form select,
+.navbar-form .radio,
+.navbar-form .checkbox {
+ margin-top: 5px;
+}
+
+.navbar-form input,
+.navbar-form select,
+.navbar-form .btn {
+ display: inline-block;
+ margin-bottom: 0;
+}
+
+.navbar-form input[type="image"],
+.navbar-form input[type="checkbox"],
+.navbar-form input[type="radio"] {
+ margin-top: 3px;
+}
+
+.navbar-form .input-append,
+.navbar-form .input-prepend {
+ margin-top: 5px;
+ white-space: nowrap;
+}
+
+.navbar-form .input-append input,
+.navbar-form .input-prepend input {
+ margin-top: 0;
+}
+
+.navbar-search {
+ position: relative;
+ float: left;
+ margin-top: 5px;
+ margin-bottom: 0;
+}
+
+.navbar-search .search-query {
+ padding: 4px 14px;
+ margin-bottom: 0;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 13px;
+ font-weight: normal;
+ line-height: 1;
+ -webkit-border-radius: 15px;
+ -moz-border-radius: 15px;
+ border-radius: 15px;
+}
+
+.navbar-static-top {
+ position: static;
+ margin-bottom: 0;
+}
+
+.navbar-static-top .navbar-inner {
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+ position: fixed;
+ right: 0;
+ left: 0;
+ z-index: 1030;
+ margin-bottom: 0;
+}
+
+.navbar-fixed-top .navbar-inner,
+.navbar-static-top .navbar-inner {
+ border-width: 0 0 1px;
+}
+
+.navbar-fixed-bottom .navbar-inner {
+ border-width: 1px 0 0;
+}
+
+.navbar-fixed-top .navbar-inner,
+.navbar-fixed-bottom .navbar-inner {
+ padding-right: 0;
+ padding-left: 0;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.navbar-static-top .container,
+.navbar-fixed-top .container,
+.navbar-fixed-bottom .container {
+ width: 940px;
+}
+
+.navbar-fixed-top {
+ top: 0;
+}
+
+.navbar-fixed-top .navbar-inner,
+.navbar-static-top .navbar-inner {
+ -webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
+ box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
+}
+
+.navbar-fixed-bottom {
+ bottom: 0;
+}
+
+.navbar-fixed-bottom .navbar-inner {
+ -webkit-box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1);
+ box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1);
+}
+
+.navbar .nav {
+ position: relative;
+ left: 0;
+ display: block;
+ float: left;
+ margin: 0 10px 0 0;
+}
+
+.navbar .nav.pull-right {
+ float: right;
+ margin-right: 0;
+}
+
+.navbar .nav > li {
+ float: left;
+}
+
+.navbar .nav > li > a {
+ float: none;
+ padding: 10px 15px 10px;
+ color: #777777;
+ text-decoration: none;
+ text-shadow: 0 1px 0 #ffffff;
+}
+
+.navbar .nav .dropdown-toggle .caret {
+ margin-top: 8px;
+}
+
+.navbar .nav > li > a:focus,
+.navbar .nav > li > a:hover {
+ color: #333333;
+ text-decoration: none;
+ background-color: transparent;
+}
+
+.navbar .nav > .active > a,
+.navbar .nav > .active > a:hover,
+.navbar .nav > .active > a:focus {
+ color: #555555;
+ text-decoration: none;
+ background-color: #e5e5e5;
+ -webkit-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
+ -moz-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
+ box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
+}
+
+.navbar .btn-navbar {
+ display: none;
+ float: right;
+ padding: 7px 10px;
+ margin-right: 5px;
+ margin-left: 5px;
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #ededed;
+ *background-color: #e5e5e5;
+ background-image: -moz-linear-gradient(top, #f2f2f2, #e5e5e5);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5));
+ background-image: -webkit-linear-gradient(top, #f2f2f2, #e5e5e5);
+ background-image: -o-linear-gradient(top, #f2f2f2, #e5e5e5);
+ background-image: linear-gradient(to bottom, #f2f2f2, #e5e5e5);
+ background-repeat: repeat-x;
+ border-color: #e5e5e5 #e5e5e5 #bfbfbf;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffe5e5e5', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+ -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+}
+
+.navbar .btn-navbar:hover,
+.navbar .btn-navbar:focus,
+.navbar .btn-navbar:active,
+.navbar .btn-navbar.active,
+.navbar .btn-navbar.disabled,
+.navbar .btn-navbar[disabled] {
+ color: #ffffff;
+ background-color: #e5e5e5;
+ *background-color: #d9d9d9;
+}
+
+.navbar .btn-navbar:active,
+.navbar .btn-navbar.active {
+ background-color: #cccccc \9;
+}
+
+.navbar .btn-navbar .icon-bar {
+ display: block;
+ width: 18px;
+ height: 2px;
+ background-color: #f5f5f5;
+ -webkit-border-radius: 1px;
+ -moz-border-radius: 1px;
+ border-radius: 1px;
+ -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+ -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+}
+
+.btn-navbar .icon-bar + .icon-bar {
+ margin-top: 3px;
+}
+
+.navbar .nav > li > .dropdown-menu:before {
+ position: absolute;
+ top: -7px;
+ left: 9px;
+ display: inline-block;
+ border-right: 7px solid transparent;
+ border-bottom: 7px solid #ccc;
+ border-left: 7px solid transparent;
+ border-bottom-color: rgba(0, 0, 0, 0.2);
+ content: '';
+}
+
+.navbar .nav > li > .dropdown-menu:after {
+ position: absolute;
+ top: -6px;
+ left: 10px;
+ display: inline-block;
+ border-right: 6px solid transparent;
+ border-bottom: 6px solid #ffffff;
+ border-left: 6px solid transparent;
+ content: '';
+}
+
+.navbar-fixed-bottom .nav > li > .dropdown-menu:before {
+ top: auto;
+ bottom: -7px;
+ border-top: 7px solid #ccc;
+ border-bottom: 0;
+ border-top-color: rgba(0, 0, 0, 0.2);
+}
+
+.navbar-fixed-bottom .nav > li > .dropdown-menu:after {
+ top: auto;
+ bottom: -6px;
+ border-top: 6px solid #ffffff;
+ border-bottom: 0;
+}
+
+.navbar .nav li.dropdown > a:hover .caret,
+.navbar .nav li.dropdown > a:focus .caret {
+ border-top-color: #333333;
+ border-bottom-color: #333333;
+}
+
+.navbar .nav li.dropdown.open > .dropdown-toggle,
+.navbar .nav li.dropdown.active > .dropdown-toggle,
+.navbar .nav li.dropdown.open.active > .dropdown-toggle {
+ color: #555555;
+ background-color: #e5e5e5;
+}
+
+.navbar .nav li.dropdown > .dropdown-toggle .caret {
+ border-top-color: #777777;
+ border-bottom-color: #777777;
+}
+
+.navbar .nav li.dropdown.open > .dropdown-toggle .caret,
+.navbar .nav li.dropdown.active > .dropdown-toggle .caret,
+.navbar .nav li.dropdown.open.active > .dropdown-toggle .caret {
+ border-top-color: #555555;
+ border-bottom-color: #555555;
+}
+
+.navbar .pull-right > li > .dropdown-menu,
+.navbar .nav > li > .dropdown-menu.pull-right {
+ right: 0;
+ left: auto;
+}
+
+.navbar .pull-right > li > .dropdown-menu:before,
+.navbar .nav > li > .dropdown-menu.pull-right:before {
+ right: 12px;
+ left: auto;
+}
+
+.navbar .pull-right > li > .dropdown-menu:after,
+.navbar .nav > li > .dropdown-menu.pull-right:after {
+ right: 13px;
+ left: auto;
+}
+
+.navbar .pull-right > li > .dropdown-menu .dropdown-menu,
+.navbar .nav > li > .dropdown-menu.pull-right .dropdown-menu {
+ right: 100%;
+ left: auto;
+ margin-right: -1px;
+ margin-left: 0;
+ -webkit-border-radius: 6px 0 6px 6px;
+ -moz-border-radius: 6px 0 6px 6px;
+ border-radius: 6px 0 6px 6px;
+}
+
+.navbar-inverse .navbar-inner {
+ background-color: #1b1b1b;
+ background-image: -moz-linear-gradient(top, #222222, #111111);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111));
+ background-image: -webkit-linear-gradient(top, #222222, #111111);
+ background-image: -o-linear-gradient(top, #222222, #111111);
+ background-image: linear-gradient(to bottom, #222222, #111111);
+ background-repeat: repeat-x;
+ border-color: #252525;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0);
+}
+
+.navbar-inverse .brand,
+.navbar-inverse .nav > li > a {
+ color: #999999;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+
+.navbar-inverse .brand:hover,
+.navbar-inverse .nav > li > a:hover,
+.navbar-inverse .brand:focus,
+.navbar-inverse .nav > li > a:focus {
+ color: #ffffff;
+}
+
+.navbar-inverse .brand {
+ color: #999999;
+}
+
+.navbar-inverse .navbar-text {
+ color: #999999;
+}
+
+.navbar-inverse .nav > li > a:focus,
+.navbar-inverse .nav > li > a:hover {
+ color: #ffffff;
+ background-color: transparent;
+}
+
+.navbar-inverse .nav .active > a,
+.navbar-inverse .nav .active > a:hover,
+.navbar-inverse .nav .active > a:focus {
+ color: #ffffff;
+ background-color: #111111;
+}
+
+.navbar-inverse .navbar-link {
+ color: #999999;
+}
+
+.navbar-inverse .navbar-link:hover,
+.navbar-inverse .navbar-link:focus {
+ color: #ffffff;
+}
+
+.navbar-inverse .divider-vertical {
+ border-right-color: #222222;
+ border-left-color: #111111;
+}
+
+.navbar-inverse .nav li.dropdown.open > .dropdown-toggle,
+.navbar-inverse .nav li.dropdown.active > .dropdown-toggle,
+.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle {
+ color: #ffffff;
+ background-color: #111111;
+}
+
+.navbar-inverse .nav li.dropdown > a:hover .caret,
+.navbar-inverse .nav li.dropdown > a:focus .caret {
+ border-top-color: #ffffff;
+ border-bottom-color: #ffffff;
+}
+
+.navbar-inverse .nav li.dropdown > .dropdown-toggle .caret {
+ border-top-color: #999999;
+ border-bottom-color: #999999;
+}
+
+.navbar-inverse .nav li.dropdown.open > .dropdown-toggle .caret,
+.navbar-inverse .nav li.dropdown.active > .dropdown-toggle .caret,
+.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle .caret {
+ border-top-color: #ffffff;
+ border-bottom-color: #ffffff;
+}
+
+.navbar-inverse .navbar-search .search-query {
+ color: #ffffff;
+ background-color: #515151;
+ border-color: #111111;
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
+ -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
+ -webkit-transition: none;
+ -moz-transition: none;
+ -o-transition: none;
+ transition: none;
+}
+
+.navbar-inverse .navbar-search .search-query:-moz-placeholder {
+ color: #cccccc;
+}
+
+.navbar-inverse .navbar-search .search-query:-ms-input-placeholder {
+ color: #cccccc;
+}
+
+.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder {
+ color: #cccccc;
+}
+
+.navbar-inverse .navbar-search .search-query:focus,
+.navbar-inverse .navbar-search .search-query.focused {
+ padding: 5px 15px;
+ color: #333333;
+ text-shadow: 0 1px 0 #ffffff;
+ background-color: #ffffff;
+ border: 0;
+ outline: 0;
+ -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+ -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+ box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+}
+
+.navbar-inverse .btn-navbar {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #0e0e0e;
+ *background-color: #040404;
+ background-image: -moz-linear-gradient(top, #151515, #040404);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#151515), to(#040404));
+ background-image: -webkit-linear-gradient(top, #151515, #040404);
+ background-image: -o-linear-gradient(top, #151515, #040404);
+ background-image: linear-gradient(to bottom, #151515, #040404);
+ background-repeat: repeat-x;
+ border-color: #040404 #040404 #000000;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515', endColorstr='#ff040404', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.navbar-inverse .btn-navbar:hover,
+.navbar-inverse .btn-navbar:focus,
+.navbar-inverse .btn-navbar:active,
+.navbar-inverse .btn-navbar.active,
+.navbar-inverse .btn-navbar.disabled,
+.navbar-inverse .btn-navbar[disabled] {
+ color: #ffffff;
+ background-color: #040404;
+ *background-color: #000000;
+}
+
+.navbar-inverse .btn-navbar:active,
+.navbar-inverse .btn-navbar.active {
+ background-color: #000000 \9;
+}
+
+.breadcrumb {
+ padding: 8px 15px;
+ margin: 0 0 20px;
+ list-style: none;
+ background-color: #f5f5f5;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.breadcrumb > li {
+ display: inline-block;
+ *display: inline;
+ text-shadow: 0 1px 0 #ffffff;
+ *zoom: 1;
+}
+
+.breadcrumb > li > .divider {
+ padding: 0 5px;
+ color: #ccc;
+}
+
+.breadcrumb > .active {
+ color: #999999;
+}
+
+.pagination {
+ margin: 20px 0;
+}
+
+.pagination ul {
+ display: inline-block;
+ *display: inline;
+ margin-bottom: 0;
+ margin-left: 0;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ *zoom: 1;
+ -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.pagination ul > li {
+ display: inline;
+}
+
+.pagination ul > li > a,
+.pagination ul > li > span {
+ float: left;
+ padding: 4px 12px;
+ line-height: 20px;
+ text-decoration: none;
+ background-color: #ffffff;
+ border: 1px solid #dddddd;
+ border-left-width: 0;
+}
+
+.pagination ul > li > a:hover,
+.pagination ul > li > a:focus,
+.pagination ul > .active > a,
+.pagination ul > .active > span {
+ background-color: #f5f5f5;
+}
+
+.pagination ul > .active > a,
+.pagination ul > .active > span {
+ color: #999999;
+ cursor: default;
+}
+
+.pagination ul > .disabled > span,
+.pagination ul > .disabled > a,
+.pagination ul > .disabled > a:hover,
+.pagination ul > .disabled > a:focus {
+ color: #999999;
+ cursor: default;
+ background-color: transparent;
+}
+
+.pagination ul > li:first-child > a,
+.pagination ul > li:first-child > span {
+ border-left-width: 1px;
+ -webkit-border-bottom-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ -webkit-border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
+ -moz-border-radius-bottomleft: 4px;
+ -moz-border-radius-topleft: 4px;
+}
+
+.pagination ul > li:last-child > a,
+.pagination ul > li:last-child > span {
+ -webkit-border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
+ -webkit-border-bottom-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+ -moz-border-radius-topright: 4px;
+ -moz-border-radius-bottomright: 4px;
+}
+
+.pagination-centered {
+ text-align: center;
+}
+
+.pagination-right {
+ text-align: right;
+}
+
+.pagination-large ul > li > a,
+.pagination-large ul > li > span {
+ padding: 11px 19px;
+ font-size: 17.5px;
+}
+
+.pagination-large ul > li:first-child > a,
+.pagination-large ul > li:first-child > span {
+ -webkit-border-bottom-left-radius: 6px;
+ border-bottom-left-radius: 6px;
+ -webkit-border-top-left-radius: 6px;
+ border-top-left-radius: 6px;
+ -moz-border-radius-bottomleft: 6px;
+ -moz-border-radius-topleft: 6px;
+}
+
+.pagination-large ul > li:last-child > a,
+.pagination-large ul > li:last-child > span {
+ -webkit-border-top-right-radius: 6px;
+ border-top-right-radius: 6px;
+ -webkit-border-bottom-right-radius: 6px;
+ border-bottom-right-radius: 6px;
+ -moz-border-radius-topright: 6px;
+ -moz-border-radius-bottomright: 6px;
+}
+
+.pagination-mini ul > li:first-child > a,
+.pagination-small ul > li:first-child > a,
+.pagination-mini ul > li:first-child > span,
+.pagination-small ul > li:first-child > span {
+ -webkit-border-bottom-left-radius: 3px;
+ border-bottom-left-radius: 3px;
+ -webkit-border-top-left-radius: 3px;
+ border-top-left-radius: 3px;
+ -moz-border-radius-bottomleft: 3px;
+ -moz-border-radius-topleft: 3px;
+}
+
+.pagination-mini ul > li:last-child > a,
+.pagination-small ul > li:last-child > a,
+.pagination-mini ul > li:last-child > span,
+.pagination-small ul > li:last-child > span {
+ -webkit-border-top-right-radius: 3px;
+ border-top-right-radius: 3px;
+ -webkit-border-bottom-right-radius: 3px;
+ border-bottom-right-radius: 3px;
+ -moz-border-radius-topright: 3px;
+ -moz-border-radius-bottomright: 3px;
+}
+
+.pagination-small ul > li > a,
+.pagination-small ul > li > span {
+ padding: 2px 10px;
+ font-size: 11.9px;
+}
+
+.pagination-mini ul > li > a,
+.pagination-mini ul > li > span {
+ padding: 0 6px;
+ font-size: 10.5px;
+}
+
+.pager {
+ margin: 20px 0;
+ text-align: center;
+ list-style: none;
+ *zoom: 1;
+}
+
+.pager:before,
+.pager:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.pager:after {
+ clear: both;
+}
+
+.pager li {
+ display: inline;
+}
+
+.pager li > a,
+.pager li > span {
+ display: inline-block;
+ padding: 5px 14px;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ -webkit-border-radius: 15px;
+ -moz-border-radius: 15px;
+ border-radius: 15px;
+}
+
+.pager li > a:hover,
+.pager li > a:focus {
+ text-decoration: none;
+ background-color: #f5f5f5;
+}
+
+.pager .next > a,
+.pager .next > span {
+ float: right;
+}
+
+.pager .previous > a,
+.pager .previous > span {
+ float: left;
+}
+
+.pager .disabled > a,
+.pager .disabled > a:hover,
+.pager .disabled > a:focus,
+.pager .disabled > span {
+ color: #999999;
+ cursor: default;
+ background-color: #fff;
+}
+
+.modal-backdrop {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 1040;
+ background-color: #000000;
+}
+
+.modal-backdrop.fade {
+ opacity: 0;
+}
+
+.modal-backdrop,
+.modal-backdrop.fade.in {
+ opacity: 0.8;
+ filter: alpha(opacity=80);
+}
+
+.modal {
+ position: fixed;
+ top: 10%;
+ left: 50%;
+ z-index: 1050;
+ width: 560px;
+ margin-left: -280px;
+ background-color: #ffffff;
+ border: 1px solid #999;
+ border: 1px solid rgba(0, 0, 0, 0.3);
+ *border: 1px solid #999;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+ outline: none;
+ -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+ -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+ box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding-box;
+ background-clip: padding-box;
+}
+
+.modal.fade {
+ top: -25%;
+ -webkit-transition: opacity 0.3s linear, top 0.3s ease-out;
+ -moz-transition: opacity 0.3s linear, top 0.3s ease-out;
+ -o-transition: opacity 0.3s linear, top 0.3s ease-out;
+ transition: opacity 0.3s linear, top 0.3s ease-out;
+}
+
+.modal.fade.in {
+ top: 10%;
+}
+
+.modal-header {
+ padding: 9px 15px;
+ border-bottom: 1px solid #eee;
+}
+
+.modal-header .close {
+ margin-top: 2px;
+}
+
+.modal-header h3 {
+ margin: 0;
+ line-height: 30px;
+}
+
+.modal-body {
+ position: relative;
+ max-height: 400px;
+ padding: 15px;
+ overflow-y: auto;
+}
+
+.modal-form {
+ margin-bottom: 0;
+}
+
+.modal-footer {
+ padding: 14px 15px 15px;
+ margin-bottom: 0;
+ text-align: right;
+ background-color: #f5f5f5;
+ border-top: 1px solid #ddd;
+ -webkit-border-radius: 0 0 6px 6px;
+ -moz-border-radius: 0 0 6px 6px;
+ border-radius: 0 0 6px 6px;
+ *zoom: 1;
+ -webkit-box-shadow: inset 0 1px 0 #ffffff;
+ -moz-box-shadow: inset 0 1px 0 #ffffff;
+ box-shadow: inset 0 1px 0 #ffffff;
+}
+
+.modal-footer:before,
+.modal-footer:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.modal-footer:after {
+ clear: both;
+}
+
+.modal-footer .btn + .btn {
+ margin-bottom: 0;
+ margin-left: 5px;
+}
+
+.modal-footer .btn-group .btn + .btn {
+ margin-left: -1px;
+}
+
+.modal-footer .btn-block + .btn-block {
+ margin-left: 0;
+}
+
+.tooltip {
+ position: absolute;
+ z-index: 1030;
+ display: block;
+ font-size: 11px;
+ line-height: 1.4;
+ opacity: 0;
+ filter: alpha(opacity=0);
+ visibility: visible;
+}
+
+.tooltip.in {
+ opacity: 0.8;
+ filter: alpha(opacity=80);
+}
+
+.tooltip.top {
+ padding: 5px 0;
+ margin-top: -3px;
+}
+
+.tooltip.right {
+ padding: 0 5px;
+ margin-left: 3px;
+}
+
+.tooltip.bottom {
+ padding: 5px 0;
+ margin-top: 3px;
+}
+
+.tooltip.left {
+ padding: 0 5px;
+ margin-left: -3px;
+}
+
+.tooltip-inner {
+ max-width: 200px;
+ padding: 8px;
+ color: #ffffff;
+ text-align: center;
+ text-decoration: none;
+ background-color: #000000;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.tooltip-arrow {
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+}
+
+.tooltip.top .tooltip-arrow {
+ bottom: 0;
+ left: 50%;
+ margin-left: -5px;
+ border-top-color: #000000;
+ border-width: 5px 5px 0;
+}
+
+.tooltip.right .tooltip-arrow {
+ top: 50%;
+ left: 0;
+ margin-top: -5px;
+ border-right-color: #000000;
+ border-width: 5px 5px 5px 0;
+}
+
+.tooltip.left .tooltip-arrow {
+ top: 50%;
+ right: 0;
+ margin-top: -5px;
+ border-left-color: #000000;
+ border-width: 5px 0 5px 5px;
+}
+
+.tooltip.bottom .tooltip-arrow {
+ top: 0;
+ left: 50%;
+ margin-left: -5px;
+ border-bottom-color: #000000;
+ border-width: 0 5px 5px;
+}
+
+.popover {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 1010;
+ display: none;
+ max-width: 276px;
+ padding: 1px;
+ text-align: left;
+ white-space: normal;
+ background-color: #ffffff;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+ -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding;
+ background-clip: padding-box;
+}
+
+.popover.top {
+ margin-top: -10px;
+}
+
+.popover.right {
+ margin-left: 10px;
+}
+
+.popover.bottom {
+ margin-top: 10px;
+}
+
+.popover.left {
+ margin-left: -10px;
+}
+
+.popover-title {
+ padding: 8px 14px;
+ margin: 0;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 18px;
+ background-color: #f7f7f7;
+ border-bottom: 1px solid #ebebeb;
+ -webkit-border-radius: 5px 5px 0 0;
+ -moz-border-radius: 5px 5px 0 0;
+ border-radius: 5px 5px 0 0;
+}
+
+.popover-title:empty {
+ display: none;
+}
+
+.popover-content {
+ padding: 9px 14px;
+}
+
+.popover .arrow,
+.popover .arrow:after {
+ position: absolute;
+ display: block;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+}
+
+.popover .arrow {
+ border-width: 11px;
+}
+
+.popover .arrow:after {
+ border-width: 10px;
+ content: "";
+}
+
+.popover.top .arrow {
+ bottom: -11px;
+ left: 50%;
+ margin-left: -11px;
+ border-top-color: #999;
+ border-top-color: rgba(0, 0, 0, 0.25);
+ border-bottom-width: 0;
+}
+
+.popover.top .arrow:after {
+ bottom: 1px;
+ margin-left: -10px;
+ border-top-color: #ffffff;
+ border-bottom-width: 0;
+}
+
+.popover.right .arrow {
+ top: 50%;
+ left: -11px;
+ margin-top: -11px;
+ border-right-color: #999;
+ border-right-color: rgba(0, 0, 0, 0.25);
+ border-left-width: 0;
+}
+
+.popover.right .arrow:after {
+ bottom: -10px;
+ left: 1px;
+ border-right-color: #ffffff;
+ border-left-width: 0;
+}
+
+.popover.bottom .arrow {
+ top: -11px;
+ left: 50%;
+ margin-left: -11px;
+ border-bottom-color: #999;
+ border-bottom-color: rgba(0, 0, 0, 0.25);
+ border-top-width: 0;
+}
+
+.popover.bottom .arrow:after {
+ top: 1px;
+ margin-left: -10px;
+ border-bottom-color: #ffffff;
+ border-top-width: 0;
+}
+
+.popover.left .arrow {
+ top: 50%;
+ right: -11px;
+ margin-top: -11px;
+ border-left-color: #999;
+ border-left-color: rgba(0, 0, 0, 0.25);
+ border-right-width: 0;
+}
+
+.popover.left .arrow:after {
+ right: 1px;
+ bottom: -10px;
+ border-left-color: #ffffff;
+ border-right-width: 0;
+}
+
+.thumbnails {
+ margin-left: -20px;
+ list-style: none;
+ *zoom: 1;
+}
+
+.thumbnails:before,
+.thumbnails:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.thumbnails:after {
+ clear: both;
+}
+
+.row-fluid .thumbnails {
+ margin-left: 0;
+}
+
+.thumbnails > li {
+ float: left;
+ margin-bottom: 20px;
+ margin-left: 20px;
+}
+
+.thumbnail {
+ display: block;
+ padding: 4px;
+ line-height: 20px;
+ border: 1px solid #ddd;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
+ -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
+ -webkit-transition: all 0.2s ease-in-out;
+ -moz-transition: all 0.2s ease-in-out;
+ -o-transition: all 0.2s ease-in-out;
+ transition: all 0.2s ease-in-out;
+}
+
+a.thumbnail:hover,
+a.thumbnail:focus {
+ border-color: #0088cc;
+ -webkit-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+ -moz-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+ box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+}
+
+.thumbnail > img {
+ display: block;
+ max-width: 100%;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.thumbnail .caption {
+ padding: 9px;
+ color: #555555;
+}
+
+.media,
+.media-body {
+ overflow: hidden;
+ *overflow: visible;
+ zoom: 1;
+}
+
+.media,
+.media .media {
+ margin-top: 15px;
+}
+
+.media:first-child {
+ margin-top: 0;
+}
+
+.media-object {
+ display: block;
+}
+
+.media-heading {
+ margin: 0 0 5px;
+}
+
+.media > .pull-left {
+ margin-right: 10px;
+}
+
+.media > .pull-right {
+ margin-left: 10px;
+}
+
+.media-list {
+ margin-left: 0;
+ list-style: none;
+}
+
+.label,
+.badge {
+ display: inline-block;
+ padding: 2px 4px;
+ font-size: 11.844px;
+ font-weight: bold;
+ line-height: 14px;
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ white-space: nowrap;
+ vertical-align: baseline;
+ background-color: #999999;
+}
+
+.label {
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+
+.badge {
+ padding-right: 9px;
+ padding-left: 9px;
+ -webkit-border-radius: 9px;
+ -moz-border-radius: 9px;
+ border-radius: 9px;
+}
+
+.label:empty,
+.badge:empty {
+ display: none;
+}
+
+a.label:hover,
+a.label:focus,
+a.badge:hover,
+a.badge:focus {
+ color: #ffffff;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+.label-important,
+.badge-important {
+ background-color: #b94a48;
+}
+
+.label-important[href],
+.badge-important[href] {
+ background-color: #953b39;
+}
+
+.label-warning,
+.badge-warning {
+ background-color: #f89406;
+}
+
+.label-warning[href],
+.badge-warning[href] {
+ background-color: #c67605;
+}
+
+.label-success,
+.badge-success {
+ background-color: #468847;
+}
+
+.label-success[href],
+.badge-success[href] {
+ background-color: #356635;
+}
+
+.label-info,
+.badge-info {
+ background-color: #3a87ad;
+}
+
+.label-info[href],
+.badge-info[href] {
+ background-color: #2d6987;
+}
+
+.label-inverse,
+.badge-inverse {
+ background-color: #333333;
+}
+
+.label-inverse[href],
+.badge-inverse[href] {
+ background-color: #1a1a1a;
+}
+
+.btn .label,
+.btn .badge {
+ position: relative;
+ top: -1px;
+}
+
+.btn-mini .label,
+.btn-mini .badge {
+ top: 0;
+}
+
+@-webkit-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+
+@-moz-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+
+@-ms-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+
+@-o-keyframes progress-bar-stripes {
+ from {
+ background-position: 0 0;
+ }
+ to {
+ background-position: 40px 0;
+ }
+}
+
+@keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+
+.progress {
+ height: 20px;
+ margin-bottom: 20px;
+ overflow: hidden;
+ background-color: #f7f7f7;
+ background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));
+ background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9);
+ background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9);
+ background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9);
+ background-repeat: repeat-x;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+}
+
+.progress .bar {
+ float: left;
+ width: 0;
+ height: 100%;
+ font-size: 12px;
+ color: #ffffff;
+ text-align: center;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #0e90d2;
+ background-image: -moz-linear-gradient(top, #149bdf, #0480be);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));
+ background-image: -webkit-linear-gradient(top, #149bdf, #0480be);
+ background-image: -o-linear-gradient(top, #149bdf, #0480be);
+ background-image: linear-gradient(to bottom, #149bdf, #0480be);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);
+ -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ -webkit-transition: width 0.6s ease;
+ -moz-transition: width 0.6s ease;
+ -o-transition: width 0.6s ease;
+ transition: width 0.6s ease;
+}
+
+.progress .bar + .bar {
+ -webkit-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ -moz-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+}
+
+.progress-striped .bar {
+ background-color: #149bdf;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ -webkit-background-size: 40px 40px;
+ -moz-background-size: 40px 40px;
+ -o-background-size: 40px 40px;
+ background-size: 40px 40px;
+}
+
+.progress.active .bar {
+ -webkit-animation: progress-bar-stripes 2s linear infinite;
+ -moz-animation: progress-bar-stripes 2s linear infinite;
+ -ms-animation: progress-bar-stripes 2s linear infinite;
+ -o-animation: progress-bar-stripes 2s linear infinite;
+ animation: progress-bar-stripes 2s linear infinite;
+}
+
+.progress-danger .bar,
+.progress .bar-danger {
+ background-color: #dd514c;
+ background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));
+ background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
+ background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
+ background-image: linear-gradient(to bottom, #ee5f5b, #c43c35);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0);
+}
+
+.progress-danger.progress-striped .bar,
+.progress-striped .bar-danger {
+ background-color: #ee5f5b;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-success .bar,
+.progress .bar-success {
+ background-color: #5eb95e;
+ background-image: -moz-linear-gradient(top, #62c462, #57a957);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));
+ background-image: -webkit-linear-gradient(top, #62c462, #57a957);
+ background-image: -o-linear-gradient(top, #62c462, #57a957);
+ background-image: linear-gradient(to bottom, #62c462, #57a957);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0);
+}
+
+.progress-success.progress-striped .bar,
+.progress-striped .bar-success {
+ background-color: #62c462;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-info .bar,
+.progress .bar-info {
+ background-color: #4bb1cf;
+ background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));
+ background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
+ background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
+ background-image: linear-gradient(to bottom, #5bc0de, #339bb9);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0);
+}
+
+.progress-info.progress-striped .bar,
+.progress-striped .bar-info {
+ background-color: #5bc0de;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-warning .bar,
+.progress .bar-warning {
+ background-color: #faa732;
+ background-image: -moz-linear-gradient(top, #fbb450, #f89406);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));
+ background-image: -webkit-linear-gradient(top, #fbb450, #f89406);
+ background-image: -o-linear-gradient(top, #fbb450, #f89406);
+ background-image: linear-gradient(to bottom, #fbb450, #f89406);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);
+}
+
+.progress-warning.progress-striped .bar,
+.progress-striped .bar-warning {
+ background-color: #fbb450;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.accordion {
+ margin-bottom: 20px;
+}
+
+.accordion-group {
+ margin-bottom: 2px;
+ border: 1px solid #e5e5e5;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.accordion-heading {
+ border-bottom: 0;
+}
+
+.accordion-heading .accordion-toggle {
+ display: block;
+ padding: 8px 15px;
+}
+
+.accordion-toggle {
+ cursor: pointer;
+}
+
+.accordion-inner {
+ padding: 9px 15px;
+ border-top: 1px solid #e5e5e5;
+}
+
+.carousel {
+ position: relative;
+ margin-bottom: 20px;
+ line-height: 1;
+}
+
+.carousel-inner {
+ position: relative;
+ width: 100%;
+ overflow: hidden;
+}
+
+.carousel-inner > .item {
+ position: relative;
+ display: none;
+ -webkit-transition: 0.6s ease-in-out left;
+ -moz-transition: 0.6s ease-in-out left;
+ -o-transition: 0.6s ease-in-out left;
+ transition: 0.6s ease-in-out left;
+}
+
+.carousel-inner > .item > img,
+.carousel-inner > .item > a > img {
+ display: block;
+ line-height: 1;
+}
+
+.carousel-inner > .active,
+.carousel-inner > .next,
+.carousel-inner > .prev {
+ display: block;
+}
+
+.carousel-inner > .active {
+ left: 0;
+}
+
+.carousel-inner > .next,
+.carousel-inner > .prev {
+ position: absolute;
+ top: 0;
+ width: 100%;
+}
+
+.carousel-inner > .next {
+ left: 100%;
+}
+
+.carousel-inner > .prev {
+ left: -100%;
+}
+
+.carousel-inner > .next.left,
+.carousel-inner > .prev.right {
+ left: 0;
+}
+
+.carousel-inner > .active.left {
+ left: -100%;
+}
+
+.carousel-inner > .active.right {
+ left: 100%;
+}
+
+.carousel-control {
+ position: absolute;
+ top: 40%;
+ left: 15px;
+ width: 40px;
+ height: 40px;
+ margin-top: -20px;
+ font-size: 60px;
+ font-weight: 100;
+ line-height: 30px;
+ color: #ffffff;
+ text-align: center;
+ background: #222222;
+ border: 3px solid #ffffff;
+ -webkit-border-radius: 23px;
+ -moz-border-radius: 23px;
+ border-radius: 23px;
+ opacity: 0.5;
+ filter: alpha(opacity=50);
+}
+
+.carousel-control.right {
+ right: 15px;
+ left: auto;
+}
+
+.carousel-control:hover,
+.carousel-control:focus {
+ color: #ffffff;
+ text-decoration: none;
+ opacity: 0.9;
+ filter: alpha(opacity=90);
+}
+
+.carousel-indicators {
+ position: absolute;
+ top: 15px;
+ right: 15px;
+ z-index: 5;
+ margin: 0;
+ list-style: none;
+}
+
+.carousel-indicators li {
+ display: block;
+ float: left;
+ width: 10px;
+ height: 10px;
+ margin-left: 5px;
+ text-indent: -999px;
+ background-color: #ccc;
+ background-color: rgba(255, 255, 255, 0.25);
+ border-radius: 5px;
+}
+
+.carousel-indicators .active {
+ background-color: #fff;
+}
+
+.carousel-caption {
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ padding: 15px;
+ background: #333333;
+ background: rgba(0, 0, 0, 0.75);
+}
+
+.carousel-caption h4,
+.carousel-caption p {
+ line-height: 20px;
+ color: #ffffff;
+}
+
+.carousel-caption h4 {
+ margin: 0 0 5px;
+}
+
+.carousel-caption p {
+ margin-bottom: 0;
+}
+
+.hero-unit {
+ padding: 60px;
+ margin-bottom: 30px;
+ font-size: 18px;
+ font-weight: 200;
+ line-height: 30px;
+ color: inherit;
+ background-color: #eeeeee;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+}
+
+.hero-unit h1 {
+ margin-bottom: 0;
+ font-size: 60px;
+ line-height: 1;
+ letter-spacing: -1px;
+ color: inherit;
+}
+
+.hero-unit li {
+ line-height: 30px;
+}
+
+.pull-right {
+ float: right;
+}
+
+.pull-left {
+ float: left;
+}
+
+.hide {
+ display: none;
+}
+
+.show {
+ display: block;
+}
+
+.invisible {
+ visibility: hidden;
+}
+
+.affix {
+ position: fixed;
+}
diff --git a/dashboard/app/styles/etcd-widgets.css b/dashboard/app/styles/etcd-widgets.css
new file mode 100644
index 000000000..1cccf39f3
--- /dev/null
+++ b/dashboard/app/styles/etcd-widgets.css
@@ -0,0 +1,694 @@
+body {
+ margin: 0px;
+}
+.etcd-container {
+ background-color: #fff;
+ border: 1px solid #ddd;
+ border-radius: 5px;
+ box-shadow: rgba(0, 0, 0, 0.14902) 0px 1px 3px;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ overflow: hidden;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ position: relative;
+}
+
+ a {
+ color: #2176AC;
+ text-decoration: none;
+ }
+
+ a:hover, a:active {
+ text-decoration: underline;
+ }
+
+ input[type=text] {
+ box-shadow: inset 0 1px 2px rgba(0,0,0,.5);
+ border: none;
+ border-radius: 3px;
+ font-size: 13px;
+ padding-left: 5px;
+ padding-right: 5px;
+ height: 25px;
+ }
+
+ input[type=text]:focus {
+
+ }
+
+ h2 {
+ font-size: 22px;
+ font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 500;
+ margin: 0 0 20px 0;
+ padding: 0;
+ }
+
+ .etcd-button {
+ display:inline-block;
+ padding:6px 12px;
+ margin-bottom:0;
+ font-size:14px;
+ font-weight:normal;
+ line-height:1.428571429;
+ text-align:center;
+ white-space:nowrap;
+ vertical-align:middle;
+ cursor:pointer;
+ border:1px solid transparent;
+ border-radius:4px;
+ -webkit-user-select:none;
+ -moz-user-select:none;
+ -ms-user-select:none;
+ -o-user-select:none;
+ user-select:none;
+ margin: 0px;
+ border: none;
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.25);
+ }
+
+ .etcd-button.etcd-button-small {
+ height: 25px;
+ padding: 0 10px;
+ font-size: 13px;
+ }
+
+ .etcd-button-primary {
+ background-color: #428BCA;
+ color: #fff;
+ text-shadow: 0 0 3px rgba(0,0,0,0.25);
+ }
+
+ .etcd-button-primary:active {
+ background-color: #2276ad;
+ }
+
+ .etcd-popover {
+ background: #333;
+ border-radius: 3px;
+ padding: 15px;
+ position: absolute;
+ top: 39px;
+ z-index: 9999;
+ color: #fff;
+ font-size: 13px;
+ box-shadow: 0px 2px 10px rgba(0,0,0,.5);
+ display: none;
+ }
+
+ .etcd-popover-error .etcd-popover-content {
+ color: #FF3C43;
+ font-weight: bold;
+ }
+
+ .etcd-popover-notch {
+ width: 14px;
+ height: 14px;
+ -webkit-transform: rotate(45deg);
+ -moz-transform: rotate(45deg);
+ -ms-transform: rotate(45deg);
+ position: absolute;
+ margin-top: -5px;
+ margin-left: 3px;
+ background: #333;
+ top: 0px;
+ right: 15px;
+ }
+
+ .etcd-popover.etcd-popover-right {
+ left: 77px;
+ }
+
+ .etcd-popover-right .etcd-popover-notch {
+ left: 15px;
+ }
+
+ .etcd-popover.etcd-popover-left {
+ right: 10px;
+ }
+
+ .etcd-popover-left .etcd-popover-notch {
+ right: 15px;
+ }
+
+ .etcd-popover-confirm {
+ margin-top: 10px;
+ }
+
+ .etcd-popover-confirm button {
+
+ }
+
+ .etcd-header {
+ width: 100%;
+ position: relative;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ }
+ .etcd-header.solid {
+ background: #eeeeee;
+ background: -moz-linear-gradient(top, #eeeeee 0%, #dddddd 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#eeeeee), color-stop(100%,#dddddd));
+ background: -webkit-linear-gradient(top, #eeeeee 0%,#dddddd 100%);
+ background: -o-linear-gradient(top, #eeeeee 0%,#dddddd 100%);
+ background: -ms-linear-gradient(top, #eeeeee 0%,#dddddd 100%);
+ background: linear-gradient(to bottom, #eeeeee 0%,#dddddd 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#dddddd',GradientType=0 );
+ }
+
+ .etcd-body {
+ top: 0px;
+ left: 0px;
+ position: relative;
+ overflow-y: auto;
+ overflow-x: hidden;
+ height: 100%;
+ width: 100%;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ }
+
+ .etcd-body table {
+ width: 100%;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ }
+
+ .etcd-body table thead td {
+ text-transform: uppercase;
+ font-size: 11px;
+ line-height: 20px;
+ border-bottom: 1px solid #ddd;
+ padding-top: 0px;
+ padding-right: 10px;
+ padding-bottom: 0px;
+ padding-left: 0px;
+ color: #666;
+ }
+
+ .etcd-body table tbody td {
+ line-height: 18px;
+ border-bottom: 1px solid #ddd;
+ padding-top: 6px;
+ padding-right: 10px;
+ padding-bottom: 6px;
+ padding-left: 0px;
+ vertical-align: text-top;
+ }
+
+ .etcd-body table .etcd-ttl-header {
+ width: 33%;
+ }
+
+ .etcd-body table tbody .etcd-ttl {
+ font-size: 13px;
+ }
+
+ .etcd-body table tbody .etcd-ttl .etcd-ttl-none {
+ color: #999;
+ font-weight: 100;
+ }
+
+ .etcd-body table .etcd-actions-header {
+ width: 30px;
+ }
+
+ .etcd-body table thead td:first-child, .etcd-body table tbody td:first-child {
+ padding-left: 10px;
+ }
+
+ .etcd-body table thead td:last-child, .etcd-body table tbody td:last-child {
+ padding-right: 10px;
+ }
+
+ .etcd-container .etcd-preview .etcd-dialog {
+ background: #333;
+ position: absolute;
+ right: 0px;
+ left: 0px;
+ padding: 20px;
+ color: #fff;
+ font-size: 14px;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ bottom: 0px;
+ opacity: 0;
+ min-height: 110px; /* REMOVE ME! */
+ transition-property: all;
+ transition-duration: 150ms;
+ transition-timing-function: ease-in-out;
+ }
+
+ .etcd-container .etcd-preview .etcd-dialog .etcd-dialog-message {
+ margin-bottom: 20px;
+ }
+
+ .etcd-container .etcd-preview .etcd-dialog .etcd-dialog-buttons a {
+ line-height: 34px;
+ color: #fff;
+ vertical-align: middle;
+ margin-left: 10px;
+ }
+
+ /*.etcd-container .etcd-preview .etcd-dialog.etcd-reveal {
+ opacity: 1;
+ }
+
+ .etcd-container .etcd-preview .etcd-dialog.etcd-hide {
+ opacity: 0;
+ }*/
+
+ .etcd-body .etcd-list {
+ padding: 20px;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ overflow: auto;
+ height: 100%;
+ position: absolute;
+ }
+
+ .etcd-body .etcd-list .etcd-selected {
+ background-color: #EAF3FF;
+ }
+
+ .etcd-body .etcd-list a.directory {
+ font-weight: bold;
+ }
+
+ .etcd-body .etcd-list tr:hover .etcd-delete svg {
+ 1visibility: visible;
+ fill: #ff0000;
+ }
+
+ .etcd-body .etcd-list .etcd-delete {
+ height: 20px;
+ width: 25px;
+ vertical-align: middle;
+ margin: 0px;
+ display: inline-block;
+ }
+
+ .etcd-body .etcd-list .etcd-delete svg {
+ height: 20px;
+ fill: #eee;
+ }
+
+ .etcd-body .etcd-list .etcd-selected .etcd-delete svg {
+ height: 20px;
+ fill: #ddd;
+ }
+
+ .etcd-body .etcd-list .etcd-delete:hover svg {
+ cursor: pointer;
+ fill: #ff0000;
+ }
+
+
+.etcd-container.etcd-browser {
+
+}
+
+ .etcd-container.etcd-browser .etcd-header {
+ height: 37px;
+ }
+
+ .etcd-back {
+ height: 37px;
+ width: 37px;
+ vertical-align: middle;
+ margin: 0px;
+ position: absolute;
+ top: 0px;
+ left: 3px;
+ display: none;
+ }
+
+ .etcd-container.etcd-browser.etcd-preview-reveal .etcd-back {
+ display: block;
+ }
+
+ .etcd-container.etcd-browser.etcd-preview-hide .etcd-back {
+ display: block;
+ }
+
+ .etcd-back svg {
+ height: 20px;
+ padding: 8px 6px;
+ }
+
+ .etcd-back:hover svg {
+ cursor: pointer;
+ fill: #428bca;
+ }
+
+ .etcd-back.etcd-disabled svg {
+ fill: #bbb;
+ }
+
+ .etcd-add {
+ height: 37px;
+ width: 37px;
+ vertical-align: middle;
+ margin: 0px;
+ position: absolute;
+ top: 0px;
+ left: 36px;
+ }
+
+ .etcd-container.etcd-browser.etcd-preview-reveal .etcd-add {
+
+ }
+
+ .etcd-container.etcd-browser.etcd-preview-hide .etcd-add {
+
+ }
+
+ .etcd-add svg {
+ height: 22px;
+ padding: 7px 6px;
+ }
+
+ .etcd-add:hover svg {
+ cursor: pointer;
+ fill: #428bca;
+ }
+
+ .etcd-add.etcd-disabled svg {
+ fill: #bbb;
+ }
+
+ .etcd-container.etcd-browser .etcd-header .etcd-browser-path {
+ position: absolute;
+ left: 72px;
+ right: 0px;
+ top: 0;
+ margin: 6px 5px 6px 5px;
+ }
+
+ .etcd-container.etcd-browser .etcd-header .etcd-browser-path input {
+ width: 100%;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ }
+
+ .etcd-container.etcd-browser .etcd-header .etcd-save {
+ position: absolute;
+ width: 54px;
+ right: -55px;
+ margin: 6px 0;
+ }
+
+ .etcd-container.etcd-browser.etcd-save-reveal .etcd-header .etcd-save {
+ right: 7px;
+ }
+
+ .etcd-container.etcd-browser.etcd-save-reveal .etcd-header .etcd-browser-path {
+ right: 62px;
+ }
+
+ .etcd-container.etcd-browser.etcd-save-hide .etcd-header .etcd-save {
+ right: -55px;
+ }
+
+ .etcd-container.etcd-browser.etcd-save-hide .etcd-header .etcd-browser-path {
+ right: 0px;
+ }
+
+ .etcd-container.etcd-browser .etcd-preview {
+ position: absolute;
+ left: 100%;
+ min-height: 100%;
+ overflow-y: auto;
+ overflow-x: hidden;
+ top: 0px;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ background-color: #fff;
+ width: 100%;
+ border-left: 1px solid #ddd;
+ }
+
+ .etcd-container.etcd-browser .etcd-preview pre, .etcd-container.etcd-browser .etcd-preview textarea {
+ padding: 20px 20px 20px 20px;
+ margin: 0px;
+ font-family: Consolas, "Liberation Mono", Courier, monospace;
+ height: 100%;
+ width: 100%;
+ white-space: pre-wrap;
+ position: absolute;
+ font-size: 13px;
+ border: 1px;
+ outline: none;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ }
+
+ .etcd-container.etcd-browser.etcd-preview-reveal .etcd-preview pre, .etcd-container.etcd-browser.etcd-preview-reveal .etcd-preview textarea {
+ display: block;
+ }
+
+ .etcd-container.etcd-browser .etcd-preview .etcd-empty {
+ top: 0px;
+ bottom: 0px;
+ width: 100%;
+ text-align: center;
+ position: absolute;
+ }
+
+ .etcd-container.etcd-browser.etcd-preview-reveal .etcd-empty {
+ display: none;
+ }
+
+ .etcd-container.etcd-browser .etcd-preview .etcd-empty-message {
+ margin-top: 25%;
+ color: #999;
+ }
+
+ /* Single Column Positioning */
+ @media (max-width: 700px) {
+ .etcd-container.etcd-browser .etcd-list {
+ width: 100%;
+ }
+
+ .etcd-container.etcd-browser.etcd-preview-reveal .etcd-list {
+ left: -100%;
+ transition-property: all;
+ transition-duration: 250ms;
+ transition-timing-function: ease-in-out;
+ }
+
+ .etcd-container.etcd-browser.etcd-preview-hide .etcd-list {
+ left: 0%;
+ transition-property: all;
+ transition-duration: 250ms;
+ transition-timing-function: ease-in-out;
+ }
+
+ .etcd-container.etcd-browser .etcd-preview {
+ left: 100%;
+ }
+
+ .etcd-container.etcd-browser.etcd-preview-reveal .etcd-preview {
+ left: -1px;
+ transition-property: all;
+ transition-duration: 250ms;
+ transition-timing-function: ease-in-out;
+ }
+
+ .etcd-container.etcd-browser.etcd-preview-hide .etcd-preview {
+ left: 100%;
+ transition-property: all;
+ transition-duration: 250ms;
+ transition-timing-function: ease-in-out;
+ }
+ }
+
+
+ /* Double Column Positioning */
+ @media (min-width: 700px) {
+ .etcd-container.etcd-browser .etcd-list {
+ width: 50%;
+ }
+
+ .etcd-container.etcd-browser .etcd-preview {
+ left: 50%;
+ width: 50%;
+ }
+
+ .etcd-container.etcd-browser.etcd-preview-reveal .etcd-preview {
+ left: 50%; /* does nothing */
+ }
+
+ .etcd-container.etcd-browser.etcd-preview-reveal .etcd-preview .etcd-empty {
+ display: none;
+ }
+
+ .etcd-container.etcd-browser.etcd-preview-hide .etcd-preview {
+ left: 50%; /* does nothing */
+ }
+
+ .etcd-container.etcd-browser.etcd-preview-hide .etcd-preview .etcd-empty {
+ display: block;
+ }
+
+ .etcd-container.etcd-browser.etcd-preview-hide .etcd-preview pre, .etcd-container.etcd-browser.etcd-preview-hide .etcd-preview textarea {
+ display: none;
+ }
+ }
+
+.etcd-container.etcd-stats {
+
+}
+
+ .etcd-container.etcd-stats h2 {
+ margin-top: -7px;
+ }
+
+ .etcd-format-selector {
+ position: absolute;
+ top: 12px;
+ right: 16px;
+ z-index: 999;
+ }
+
+ .etcd-format-selector .etcd-selector-item {
+ display: inline-block;
+ height: 12px;
+ width: 12px;
+ padding: 8px 4px;
+ }
+
+ .etcd-format-selector .etcd-selector-item:hover {
+ cursor: pointer;
+ }
+
+ .etcd-format-selector .etcd-selector-item svg {
+ fill: #333;
+ }
+
+ .etcd-container.etcd-stats .etcd-graph {
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ position: absolute;
+ top: 0px;
+ bottom: 0px;
+ left: 0px;
+ right: 0px;
+ padding: 20px;
+ }
+
+ .etcd-container.etcd-stats .etcd-graph .etcd-graph-container {
+ position: absolute;
+ top: 60px;
+ bottom: 20px;
+ left: 20px;
+ right: 20px;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ }
+
+ .etcd-container.etcd-stats table .etcd-latency {
+ width: 50%;
+ }
+
+ .etcd-container.etcd-stats .etcd-list {
+ position: absolute;
+ left: 100%;
+ min-height: 100%;
+ overflow-y: auto;
+ overflow-x: hidden;
+ top: 0px;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ background-color: #fff;
+ width: 100%;
+ border-left: 1px solid #ddd;
+ }
+
+ .etcd-container.etcd-stats .etcd-list .etcd-square {
+ height: 10px;
+ width: 10px;
+ display: inline-block;
+ margin-right: 5px;
+ }
+
+ .etcd-container.etcd-stats .etcd-list .etcd-square-red {
+ background-color: #c40022;
+ }
+
+ .etcd-container.etcd-stats .etcd-list .etcd-square-orange {
+ background-color: #FFC000;
+ }
+
+ .etcd-container.etcd-stats .etcd-list .etcd-square-green {
+ background-color: #00DB24;
+ }
+
+ .etcd-container.etcd-stats .etcd-list .etcd-latency-value {
+ display: inline-block;
+ }
+
+ /* Single Column Positioning */
+ @media (max-width: 700px) {
+ .etcd-container.etcd-stats .etcd-list {
+ width: 100%;
+ left: 100%;
+ }
+
+ .etcd-container.etcd-stats .etcd-graph {
+ left: 0%;
+ }
+
+ .etcd-container.etcd-stats.etcd-table-reveal .etcd-graph {
+ left: -100%;
+ transition-property: all;
+ transition-duration: 250ms;
+ transition-timing-function: ease-in-out;
+ }
+ .etcd-container.etcd-stats.etcd-table-hide .etcd-graph {
+ left: 0%;
+ transition-property: all;
+ transition-duration: 250ms;
+ transition-timing-function: ease-in-out;
+
+ }
+ .etcd-container.etcd-stats.etcd-table-hide .etcd-format-selector .etcd-selector-graph svg * {
+ fill: #428bca;
+ }
+
+ .etcd-container.etcd-stats.etcd-table-hide .etcd-list {
+ left: 100%;
+ transition-property: all;
+ transition-duration: 250ms;
+ transition-timing-function: ease-in-out;
+ }
+ .etcd-container.etcd-stats.etcd-table-reveal .etcd-list {
+ left: 0%;
+ transition-property: all;
+ transition-duration: 250ms;
+ transition-timing-function: ease-in-out;
+ }
+ .etcd-container.etcd-stats.etcd-table-reveal .etcd-format-selector .etcd-selector-table svg * {
+ fill: #428bca;
+ }
+
+ }
+
+
+ /* Double Column Positioning */
+ @media (min-width: 700px) {
+ .etcd-container.etcd-stats .etcd-list {
+ width: 50%;
+ left: 50%;
+ }
+
+ .etcd-container.etcd-stats .etcd-graph {
+ left: 0%;
+ width: 50%;
+ }
+
+ .etcd-container.etcd-stats .etcd-format-selector {
+ display: none;
+ }
+
+ }
diff --git a/dashboard/app/styles/main.css b/dashboard/app/styles/main.css
new file mode 100644
index 000000000..c754fddc3
--- /dev/null
+++ b/dashboard/app/styles/main.css
@@ -0,0 +1,22 @@
+body {
+ background: #fafafa;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ color: #333;
+}
+
+.hero-unit {
+ margin: 50px auto 0 auto;
+ width: 300px;
+ font-size: 18px;
+ font-weight: 200;
+ line-height: 30px;
+ background-color: #eee;
+ border-radius: 6px;
+ padding: 60px;
+}
+
+.hero-unit h1 {
+ font-size: 60px;
+ line-height: 1;
+ letter-spacing: -1px;
+}
diff --git a/dashboard/app/views/browser.html b/dashboard/app/views/browser.html
new file mode 100644
index 000000000..8a2504cf8
--- /dev/null
+++ b/dashboard/app/views/browser.html
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Save and replicate this change?
+
+
+
+
+
+
diff --git a/dashboard/app/views/stats.html b/dashboard/app/views/stats.html
new file mode 100644
index 000000000..eb1a61547
--- /dev/null
+++ b/dashboard/app/views/stats.html
@@ -0,0 +1,46 @@
+
+
+
+
+
+
Follower List
+
+
+
+ Latency
+
+
+
+ {{follower.name}}
+
+
+ {{follower.latency.current | number:1 }} ms
+
+
+
+
+
+
+
diff --git a/dashboard/bower.json b/dashboard/bower.json
new file mode 100644
index 000000000..51ed4e5ad
--- /dev/null
+++ b/dashboard/bower.json
@@ -0,0 +1,20 @@
+{
+ "name": "etcdDashboard",
+ "version": "0.0.0",
+ "dependencies": {
+ "angular": "~1.2.0-rc.2",
+ "json3": "~3.2.4",
+ "jquery": "~1.9.1",
+ "bootstrap-sass": "~2.3.1",
+ "es5-shim": "~2.0.8",
+ "angular-route": "~1.2.0-rc.2",
+ "angular-resource": "~1.2.0-rc.2",
+ "angular-cookies": "~1.2.0-rc.2",
+ "angular-sanitize": "~1.2.0-rc.2"
+ },
+ "devDependencies": {
+ "angular-mocks": "~1.2.0-rc.2",
+ "angular-scenario": "~1.2.0-rc.2",
+ "underscore": "~1.5.2"
+ }
+}
diff --git a/dashboard/build b/dashboard/build
new file mode 100755
index 000000000..60d6fcdcb
--- /dev/null
+++ b/dashboard/build
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+grunt build
+
+git clean -x -f dashboard/dist
+
+for i in `find dashboard/dist -type f`; do
+ go build github.com/jteeuwen/go-bindata
+ ./go-bindata -pkg "dist" -toc -prefix dashboard/dist $i
+done
diff --git a/dashboard/karma-e2e.conf.js b/dashboard/karma-e2e.conf.js
new file mode 100644
index 000000000..fa01484a0
--- /dev/null
+++ b/dashboard/karma-e2e.conf.js
@@ -0,0 +1,54 @@
+// Karma configuration
+// http://karma-runner.github.io/0.10/config/configuration-file.html
+
+module.exports = function(config) {
+ config.set({
+ // base path, that will be used to resolve files and exclude
+ basePath: '',
+
+ // testing framework to use (jasmine/mocha/qunit/...)
+ frameworks: ['ng-scenario'],
+
+ // list of files / patterns to load in the browser
+ files: [
+ 'test/e2e/**/*.js'
+ ],
+
+ // list of files / patterns to exclude
+ exclude: [],
+
+ // web server port
+ port: 8080,
+
+ // level of logging
+ // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: false,
+
+
+ // Start these browsers, currently available:
+ // - Chrome
+ // - ChromeCanary
+ // - Firefox
+ // - Opera
+ // - Safari (only Mac)
+ // - PhantomJS
+ // - IE (only Windows)
+ browsers: ['Chrome'],
+
+
+ // Continuous Integration mode
+ // if true, it capture browsers, run tests and exit
+ singleRun: false
+
+ // Uncomment the following lines if you are using grunt's server to run the tests
+ // proxies: {
+ // '/': 'http://localhost:9000/'
+ // },
+ // URL root prevent conflicts with the site root
+ // urlRoot: '_karma_'
+ });
+};
diff --git a/dashboard/karma.conf.js b/dashboard/karma.conf.js
new file mode 100644
index 000000000..fae04e3aa
--- /dev/null
+++ b/dashboard/karma.conf.js
@@ -0,0 +1,52 @@
+// Karma configuration
+// http://karma-runner.github.io/0.10/config/configuration-file.html
+
+module.exports = function(config) {
+ config.set({
+ // base path, that will be used to resolve files and exclude
+ basePath: '',
+
+ // testing framework to use (jasmine/mocha/qunit/...)
+ frameworks: ['jasmine'],
+
+ // list of files / patterns to load in the browser
+ files: [
+ 'app/bower_components/angular/angular.js',
+ 'app/bower_components/angular-mocks/angular-mocks.js',
+ 'app/scripts/*.js',
+ 'app/scripts/**/*.js',
+ 'test/mock/**/*.js',
+ 'test/spec/**/*.js'
+ ],
+
+ // list of files / patterns to exclude
+ exclude: [],
+
+ // web server port
+ port: 8080,
+
+ // level of logging
+ // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: false,
+
+
+ // Start these browsers, currently available:
+ // - Chrome
+ // - ChromeCanary
+ // - Firefox
+ // - Opera
+ // - Safari (only Mac)
+ // - PhantomJS
+ // - IE (only Windows)
+ browsers: ['Chrome'],
+
+
+ // Continuous Integration mode
+ // if true, it capture browsers, run tests and exit
+ singleRun: false
+ });
+};
diff --git a/dashboard/package.json b/dashboard/package.json
new file mode 100644
index 000000000..84d591766
--- /dev/null
+++ b/dashboard/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "etcd-dashboard",
+ "version": "0.0.0",
+ "dependencies": {},
+ "devDependencies": {
+ "grunt": "~0.4.1",
+ "grunt-contrib-copy": "~0.4.1",
+ "grunt-contrib-concat": "~0.3.0",
+ "grunt-contrib-coffee": "~0.7.0",
+ "grunt-contrib-uglify": "~0.2.0",
+ "grunt-contrib-compass": "~0.5.0",
+ "grunt-contrib-jshint": "~0.6.0",
+ "grunt-contrib-cssmin": "~0.6.0",
+ "grunt-contrib-connect": "~0.3.0",
+ "grunt-contrib-clean": "~0.5.0",
+ "grunt-contrib-htmlmin": "~0.1.3",
+ "grunt-contrib-imagemin": "~0.2.0",
+ "grunt-contrib-watch": "~0.5.2",
+ "grunt-autoprefixer": "~0.2.0",
+ "grunt-usemin": "~0.1.11",
+ "grunt-svgmin": "~0.2.0",
+ "grunt-rev": "~0.1.0",
+ "grunt-open": "~0.2.0",
+ "grunt-concurrent": "~0.3.0",
+ "load-grunt-tasks": "~0.1.0",
+ "connect-livereload": "~0.2.0",
+ "grunt-google-cdn": "~0.2.0",
+ "grunt-ngmin": "~0.0.2",
+ "time-grunt": "~0.1.0",
+ "grunt-karma": "~0.6.2"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ },
+ "scripts": {
+ "test": "grunt test"
+ }
+}
diff --git a/dashboard/test/.jshintrc b/dashboard/test/.jshintrc
new file mode 100644
index 000000000..aa37e7a4d
--- /dev/null
+++ b/dashboard/test/.jshintrc
@@ -0,0 +1,35 @@
+{
+ "node": true,
+ "browser": true,
+ "esnext": true,
+ "bitwise": true,
+ "camelcase": true,
+ "curly": true,
+ "eqeqeq": true,
+ "immed": true,
+ "indent": 2,
+ "latedef": true,
+ "newcap": true,
+ "noarg": true,
+ "quotmark": "single",
+ "regexp": true,
+ "undef": true,
+ "unused": true,
+ "strict": true,
+ "trailing": true,
+ "smarttabs": true,
+ "globals": {
+ "after": false,
+ "afterEach": false,
+ "angular": false,
+ "before": false,
+ "beforeEach": false,
+ "browser": false,
+ "describe": false,
+ "expect": false,
+ "inject": false,
+ "it": false,
+ "spyOn": false
+ }
+}
+
diff --git a/dashboard/test/runner.html b/dashboard/test/runner.html
new file mode 100644
index 000000000..f4a00a12b
--- /dev/null
+++ b/dashboard/test/runner.html
@@ -0,0 +1,10 @@
+
+
+
+ End2end Test Runner
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dashboard/test/spec/controllers/main.js b/dashboard/test/spec/controllers/main.js
new file mode 100644
index 000000000..ef4dea5e5
--- /dev/null
+++ b/dashboard/test/spec/controllers/main.js
@@ -0,0 +1,22 @@
+'use strict';
+
+describe('Controller: MainCtrl', function () {
+
+ // load the controller's module
+ beforeEach(module('etcdDashboardApp'));
+
+ var MainCtrl,
+ scope;
+
+ // Initialize the controller and a mock scope
+ beforeEach(inject(function ($controller, $rootScope) {
+ scope = $rootScope.$new();
+ MainCtrl = $controller('MainCtrl', {
+ $scope: scope
+ });
+ }));
+
+ it('should attach a list of awesomeThings to the scope', function () {
+ expect(scope.awesomeThings.length).toBe(3);
+ });
+});