├── LICENSE ├── README.md ├── _site ├── Gruntfile.js ├── LICENSE ├── README.md ├── css │ ├── freeboard.css │ └── freeboard.min.css ├── dashboards │ └── es_health_monitor.json ├── docs │ ├── build-docs.sh │ ├── docco-fb.css │ ├── docco.css │ ├── plugin_example.html │ └── public │ │ ├── fonts │ │ ├── aller-bold.eot │ │ ├── aller-bold.ttf │ │ ├── aller-bold.woff │ │ ├── aller-light.eot │ │ ├── aller-light.ttf │ │ ├── aller-light.woff │ │ ├── fleurons.eot │ │ ├── fleurons.ttf │ │ ├── fleurons.woff │ │ ├── novecento-bold.eot │ │ ├── novecento-bold.ttf │ │ └── novecento-bold.woff │ │ ├── images │ │ └── gray.png │ │ └── stylesheets │ │ └── normalize.css ├── es-plugin.properties ├── examples │ ├── altGuage.js │ ├── plugin_example.js │ ├── rl78.json │ └── weather.json ├── img │ ├── dropdown-arrow.png │ ├── glyphicons-halflings-white.png │ └── glyphicons-halflings.png ├── index.html ├── index.min.html ├── js │ ├── freeboard+plugins.js │ ├── freeboard+plugins.min.js │ ├── freeboard+plugins.min.js.map │ ├── freeboard.creator.js │ ├── freeboard.js │ ├── freeboard.min.js │ ├── freeboard.min.js.map │ ├── freeboard.plugins.js │ ├── freeboard.plugins.min.js │ ├── freeboard.plugins.min.js.map │ ├── freeboard.thirdparty.js │ └── freeboard.thirdparty.min.js ├── lib │ ├── css │ │ ├── freeboard │ │ │ └── styles.css │ │ └── thirdparty │ │ │ ├── codemirror-ambiance.css │ │ │ ├── codemirror.css │ │ │ └── jquery.gridster.min.css │ └── js │ │ ├── freeboard │ │ ├── DatasourceModel.js │ │ ├── DeveloperConsole.js │ │ ├── DialogBox.js │ │ ├── FreeboardModel.js │ │ ├── FreeboardUI.js │ │ ├── JSEditor.js │ │ ├── PaneModel.js │ │ ├── PluginEditor.js │ │ ├── ValueEditor.js │ │ ├── WidgetModel.js │ │ └── freeboard.js │ │ └── thirdparty │ │ ├── codemirror.js │ │ ├── head.js │ │ ├── jquery-ui.js │ │ ├── jquery.caret.js │ │ ├── jquery.gridster.js │ │ ├── jquery.js │ │ ├── jquery.xdomainrequest.js │ │ ├── knockout.js │ │ └── underscore.js ├── package.json ├── plugins │ ├── freeboard │ │ ├── freeboard.datasources.js │ │ └── freeboard.widgets.js │ └── thirdparty │ │ ├── jquery.sparkline.min.js │ │ ├── justgage.1.0.1.js │ │ ├── raphael.2.1.0.min.js │ │ └── widget.tables.js └── test │ ├── casper │ ├── tests │ │ └── smoke_test.js │ └── util │ │ └── test_util.js │ ├── fixtures │ └── input.json │ └── run_browser_tests.sh └── plugin-descriptor.properties /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 QXIP BV 4 | Copyright (c) 2013 Jim Heising and Bug Labs, Inc. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # freeboard-elasticsearch 2 | Simple [Freeboard](https://github.com/Freeboard/freeboard) dashboards plugin for [Elasticsearch](https://github.com/elastic/elasticsearch) DYI monitoring on embedded devices _(experimental)_ 3 | 4 | 5 | ## Installing on an Elasticsearch instance: 6 |
 7 | ./elasticsearch/bin/plugin install elasticfence/freeboard-elasticsearch/
 8 | 
9 | 10 | ## Usage: 11 |
12 | http://127.0.0.1:9200/_plugin/freeboard-elasticsearch/
13 | 
14 | 15 | ![](http://i.imgur.com/GhgKOVW.png?1) 16 | 17 | 18 | ## Todo 19 | 20 | * implement save/load to elasticsearch index 21 | * implement dynamic node stats display 22 | * create meaningful dashboards ;) 23 | 24 | ## Interested? 25 | Join by contributing ideas, dashboards json or bug reports! 26 | -------------------------------------------------------------------------------- /_site/Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON('package.json'), 4 | concat: { 5 | css: { 6 | src: [ 7 | 'lib/css/thirdparty/*.css', 8 | 'lib/css/freeboard/styles.css' 9 | ], 10 | dest: 'css/freeboard.css' 11 | }, 12 | thirdparty : { 13 | src : [ 14 | [ 15 | 'lib/js/thirdparty/head.js', 16 | 'lib/js/thirdparty/jquery.js', 17 | 'lib/js/thirdparty/jquery-ui.js', 18 | 'lib/js/thirdparty/knockout.js', 19 | 'lib/js/thirdparty/underscore.js', 20 | 'lib/js/thirdparty/jquery.gridster.js', 21 | 'lib/js/thirdparty/jquery.caret.js', 22 | 'lib/js/thirdparty/jquery.xdomainrequest.js', 23 | 'lib/js/thirdparty/codemirror.js', 24 | ] 25 | ], 26 | dest : 'js/freeboard.thirdparty.js' 27 | }, 28 | fb : { 29 | src : [ 30 | 'lib/js/freeboard/DatasourceModel.js', 31 | 'lib/js/freeboard/DeveloperConsole.js', 32 | 'lib/js/freeboard/DialogBox.js', 33 | 'lib/js/freeboard/FreeboardModel.js', 34 | 'lib/js/freeboard/FreeboardUI.js', 35 | 'lib/js/freeboard/JSEditor.js', 36 | 'lib/js/freeboard/PaneModel.js', 37 | 'lib/js/freeboard/PluginEditor.js', 38 | 'lib/js/freeboard/ValueEditor.js', 39 | 'lib/js/freeboard/WidgetModel.js', 40 | 'lib/js/freeboard/freeboard.js', 41 | ], 42 | dest : 'js/freeboard.js' 43 | }, 44 | plugins : { 45 | src : [ 46 | 'plugins/freeboard/*.js' 47 | ], 48 | dest : 'js/freeboard.plugins.js' 49 | }, 50 | 'fb+plugins' : { 51 | src : [ 52 | 'js/freeboard.js', 53 | 'js/freeboard.plugins.js' 54 | ], 55 | dest : 'js/freeboard+plugins.js' 56 | } 57 | }, 58 | cssmin : { 59 | css:{ 60 | src: 'css/freeboard.css', 61 | dest: 'css/freeboard.min.css' 62 | } 63 | }, 64 | uglify : { 65 | fb: { 66 | files: { 67 | 'js/freeboard.min.js' : [ 'js/freeboard.js' ] 68 | } 69 | }, 70 | plugins: { 71 | files: { 72 | 'js/freeboard.plugins.min.js' : [ 'js/freeboard.plugins.js' ] 73 | } 74 | }, 75 | thirdparty :{ 76 | options: { 77 | mangle : false, 78 | beautify : false, 79 | compress: true 80 | }, 81 | files: { 82 | 'js/freeboard.thirdparty.min.js' : [ 'js/freeboard.thirdparty.js' ] 83 | } 84 | }, 85 | 'fb+plugins': { 86 | files: { 87 | 'js/freeboard+plugins.min.js' : [ 'js/freeboard+plugins.js' ] 88 | } 89 | } 90 | }, 91 | 'string-replace': { 92 | css: { 93 | files: { 94 | 'css/': 'css/*.css' 95 | }, 96 | options: { 97 | replacements: [{ 98 | pattern: /..\/..\/..\/img/ig, 99 | replacement: '../img' 100 | }] 101 | } 102 | } 103 | } 104 | }); 105 | 106 | grunt.loadNpmTasks('grunt-contrib-concat'); 107 | grunt.loadNpmTasks('grunt-contrib-uglify'); 108 | grunt.loadNpmTasks('grunt-contrib-watch'); 109 | grunt.loadNpmTasks('grunt-contrib-cssmin'); 110 | grunt.loadNpmTasks('grunt-string-replace'); 111 | grunt.registerTask('default', [ 'concat:css', 'cssmin:css', 'concat:fb', 'concat:thirdparty', 'concat:plugins', 'concat:fb+plugins', 'uglify:fb', 'uglify:plugins', 'uglify:fb+plugins', 'uglify:thirdparty', 'string-replace:css' ]); 112 | }; 113 | -------------------------------------------------------------------------------- /_site/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Jim Heising and Bug Labs, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /_site/README.md: -------------------------------------------------------------------------------- 1 | freeboard 2 | ========== 3 | 4 | **free·board** (noun) *\ˈfrē-ˌbȯrd\* 5 | 6 | 1. the distance between the waterline and the main deck or weather deck of a ship or between the level of the water and the upper edge of the side of a small boat. 7 | 2. the act of freeing data from below the "waterline" and exposing it to the world. 8 | 3. a damn-sexy, open source real-time dashboard builder/viewer for IOT and other web mashups. 9 | 10 | ### Demo 11 | http://freeboard.github.io/freeboard 12 | 13 | https://freeboard.io 14 | 15 | ### Screenshots 16 | ![Weather](https://raw.github.com/Freeboard/branding/master/screenshots/freeboard-screenshot-1.jpg) 17 | 18 | ### What is It? 19 | 20 | Freeboard is a turn-key HTML-based "engine" for dashboards. Besides a nice looking layout engine, it provides a plugin architecture for creating datasources (which fetch data) and widgets (which display data)— freeboard then does all the work to connect the two together. Another feature of freeboard is its ability to run entirely in the browser as a single-page static web app without the need for a server. The feature makes it extremely attractive as a front-end for embedded devices which may have limited ability to serve complex and dynamic web pages. 21 | 22 | The code here is the client-side portion of what you see when you visit a freeboard at http://freeboard.io. It does not include any of the server-side code for user management, saving to a database or public/private functionality— this is left up to you to implement should you want to use freeboard as an online service. 23 | 24 | ### How to Use 25 | 26 | Freeboard can be run entirely from a local hard drive. Simply download/clone the repository and open index.html. When using Chrome, you may run into issues with CORS when accessing JSON based APIs if you load from your local hard-drive— in this case you can switch to using JSONP or load index.html and run from a local or remote web server. 27 | 28 | ### API 29 | 30 | While freeboard runs as a stand-alone app out of the box, you can augment and control it from javascript with a simple API. All API calls are made on the `freeboard` singleton object. 31 | 32 | ------- 33 | 34 | **freeboard.initialize(allowEdit, [callback])** 35 | 36 | Must be called first to initialize freeboard. 37 | 38 | > **allowEdit** (boolean) - Sets the initial state of freeboard to allow or disallow editing. 39 | 40 | > **callback** (function) - Function that will be called back when freeboard has finished initializing. 41 | 42 | ------- 43 | 44 | **freeboard.newDashboard()** 45 | 46 | Clear the contents of the freeboard and initialize a new dashboard. 47 | 48 | ------- 49 | 50 | **freeboard.serialize()** 51 | 52 | Serializes the current dashboard and returns a javascript object. 53 | 54 | ------- 55 | 56 | **freeboard.loadDashboard(configuration, [callback])** 57 | 58 | Load the dashboard from a serialized dashboard object. 59 | 60 | > **configuration** (object) - A javascript object containing the configuration of a dashboard. Normally this will be an object that has been created and saved via the `freeboard.serialize()` function. 61 | 62 | > **callback** (function) - Function that will be called back when the dashboard has finished loading. 63 | 64 | ------- 65 | 66 | **freeboard.setEditing(editing, animate)** 67 | 68 | Programatically control the editing state of the of dashboard. 69 | 70 | > **editing** (bool) - Set to true or false to modify the view-only or editing state of the board. 71 | 72 | > **animate** (function) - Set to true or false to animate the modification of the editing state. This animates the top-tab dropdown (the part where you can edit datasources and such). 73 | 74 | ------- 75 | 76 | **freeboard.isEditing()** 77 | 78 | Returns boolean depending on whether the dashboard is in in the view-only or edit state. 79 | 80 | ------- 81 | 82 | **freeboard.loadDatasourcePlugin(plugin)** 83 | 84 | Register a datasource plugin. See http://freeboard.github.io/freeboard/docs/plugin_example.html for information on creating plugins. 85 | 86 | > **plugin** (object) - A plugin definition object as defined at http://freeboard.github.io/freeboard/docs/plugin_example.html 87 | 88 | ------- 89 | 90 | **freeboard.loadWidgetPlugin(plugin)** 91 | 92 | Register a widget plugin. See http://freeboard.github.io/freeboard/docs/plugin_example.html for information on creating plugins. 93 | 94 | > **plugin** (object) - A plugin definition object as defined at http://freeboard.github.io/freeboard/docs/plugin_example.html 95 | 96 | ------- 97 | 98 | **freeboard.showLoadingIndicator(show)** 99 | 100 | Show/hide the loading indicator. The loading indicator will display an indicator over the entire board that can be useful when you have some code that takes a while and you want to give a visual indication and to prevent the user from modifying the board. 101 | 102 | > **show** (boolean) - Set to true or false to show or hide the loading indicator. 103 | 104 | ------- 105 | 106 | **freeboard.showDialog(contentElement, title, okButtonTitle, cancelButtonTitle, okCallback)** 107 | 108 | Show a styled dialog box with custom content. 109 | 110 | > **contentElement** (DOM or jquery element) - The DOM or jquery element to display within the content of the dialog box. 111 | 112 | > **title** (string) - The title of the dialog box displayed on the top left. 113 | 114 | > **okButtonTitle** (string) - The string to display in the button that will be used as the OK button. A null or undefined value will result in no button being displayed. 115 | 116 | > **cancelButtonTitle** (string) - The string to display in the button that will be used as the Cancel button. A null or undefined value will result in no button being displayed. 117 | 118 | > **okCallback** (function) - A function that will be called if the user presses the OK button. 119 | 120 | ------- 121 | 122 | **freeboard.getDatasourceSettings(datasourceName)** 123 | 124 | Returns an object with the current settings for a datasource or null if no datasource with the given name is found. 125 | 126 | > **datasourceName** (string) - The name of a datasource in the dashboard. 127 | 128 | ------- 129 | 130 | **freeboard.setDatasourceSettings(datasourceName, settings)** 131 | 132 | Updates settings on a datasource. 133 | 134 | > **datasourceName** (string) - The name of a datasource in the dashboard. 135 | 136 | > **settings** (object) - An object of key-value pairs for the settings of the datasource. The values specified here will be combined with the current settings, so you do not need specify every setting if you only want to update one. To get a list of possible settings for a datasource, consult the datasource documentation or code, or call the freeboard.getDatasourceSettings function. 137 | 138 | ------- 139 | 140 | **freeboard.on(eventName, callback)** 141 | 142 | Attach to a global freeboard event. 143 | 144 | > **eventName** (string) - The name of a global event. The following events are supported: 145 | 146 | > **"dashboard_loaded"** - Occurs after a dashboard has been loaded. 147 | 148 | > **"initialized"** - Occurs after freeboard has first been initialized. 149 | 150 | > **callback** (function) - The callback function to be called when the event occurs. 151 | 152 | ------- 153 | 154 | ### Building Plugins 155 | 156 | See http://freeboard.github.io/freeboard/docs/plugin_example.html for info on how to build plugins for freeboard. 157 | 158 | ### Testing Plugins 159 | 160 | Just edit index.html and add a link to your javascript file near the end of the head.js script loader, like: 161 | 162 | ```javascript 163 | ... 164 | "path/to/my/plugin/file.js", 165 | $(function() 166 | { //DOM Ready 167 | freeboard.initialize(true); 168 | }); 169 | ``` 170 | 171 | ### Copyright 172 | 173 | Copyright © 2013 Jim Heising (https://github.com/jheising)
Copyright © 2013 Bug Labs, Inc. (http://buglabs.net)
Licensed under the **MIT** license. 174 | 175 | --- 176 | -------------------------------------------------------------------------------- /_site/dashboards/es_health_monitor.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "allow_edit": true, 4 | "header_image" : "http://i.imgur.com/P9b3wCm.png", 5 | "plugins": [], 6 | "panes": [ 7 | { 8 | "title": "CLUSTER STATS", 9 | "width": 1, 10 | "row": { 11 | "3": 1, 12 | "4": 1 13 | }, 14 | "col": { 15 | "3": 1, 16 | "4": 1 17 | }, 18 | "col_width": 1, 19 | "widgets": [ 20 | { 21 | "type": "list", 22 | "settings": { 23 | "title": "", 24 | "show_header": true, 25 | "replace_value": "-", 26 | "value": "datasources[\"Cluster\"][\"indices\"][\"shards\"]" 27 | } 28 | }, 29 | { 30 | "type": "list", 31 | "settings": { 32 | "show_header": true, 33 | "value": "datasources[\"Cluster\"][\"indices\"][\"docs\"]" 34 | } 35 | }, 36 | { 37 | "type": "list", 38 | "settings": { 39 | "show_header": true, 40 | "value": "datasources[\"Cluster\"][\"indices\"][\"store\"]" 41 | } 42 | }, 43 | { 44 | "type": "list", 45 | "settings": { 46 | "title": "", 47 | "show_header": true, 48 | "value": "datasources[\"Cluster\"][\"nodes\"][\"fs\"]" 49 | } 50 | }, 51 | { 52 | "type": "list", 53 | "settings": { 54 | "title": "", 55 | "show_header": true, 56 | "value": "datasources[\"Cluster\"][\"indices\"][\"query_cache\"]" 57 | } 58 | } 59 | ] 60 | }, 61 | { 62 | "title": "Node 1", 63 | "width": 1, 64 | "row": { 65 | "3": 1, 66 | "4": 7, 67 | "5": 1 68 | }, 69 | "col": { 70 | "3": 3, 71 | "4": 2, 72 | "5": 2 73 | }, 74 | "col_width": 1, 75 | "widgets": [ 76 | { 77 | "type": "text_widget", 78 | "settings": { 79 | "title": "Name", 80 | "size": "regular", 81 | "value": "var set = datasources[\"Nodes\"][\"nodes\"];return set[Object.keys(set)[0]][\"name\"]", 82 | "animate": true 83 | } 84 | }, 85 | { 86 | "type": "text_widget", 87 | "settings": { 88 | "title": "Storage", 89 | "size": "regular", 90 | "value": "var set = datasources[\"Nodes\"][\"nodes\"];var data = set[Object.keys(set)[0]][\"fs\"][\"data\"][0];\nvar free = parseInt(data[\"free_in_bytes\"] * 100 / data[\"total_in_bytes\"]);\nvar i = Math.floor( Math.log(data[\"total_in_bytes\"]) / Math.log(1024) );\nvar human = ( data[\"total_in_bytes\"] / Math.pow(1024, i) ).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];\nreturn human +\" (\"+free+\"% free)\";", 91 | "sparkline": false, 92 | "animate": false 93 | } 94 | }, 95 | { 96 | "type": "indicator", 97 | "settings": { 98 | "title": "Swap Usage", 99 | "value": "var set = datasources[\"Nodes\"][\"nodes\"];return set[Object.keys(set)[0]][\"os\"][\"swap\"][\"used_in_bytes\"];", 100 | "on_text": "var set = datasources[\"Nodes\"][\"nodes\"];\nvar i = Math.floor( Math.log(set[Object.keys(set)[0]][\"os\"][\"swap\"][\"used_in_bytes\"]) / Math.log(1024) );\nvar human = ( (set[Object.keys(set)[0]][\"os\"][\"swap\"][\"used_in_bytes\"]) / Math.pow(1024, i) ).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];\nreturn \"SWAP ON! (\"+human+\")\";", 101 | "off_text": "No Swap" 102 | } 103 | }, 104 | { 105 | "type": "text_widget", 106 | "settings": { 107 | "title": "CPU Usage", 108 | "size": "regular", 109 | "value": "var set =  datasources[\"Nodes\"][\"nodes\"];return set[Object.keys(set)[0]][\"process\"][\"cpu\"][\"percent\"]", 110 | "animate": false, 111 | "units": "%" 112 | } 113 | }, 114 | { 115 | "type": "text_widget", 116 | "settings": { 117 | "title": "Doc Count", 118 | "size": "regular", 119 | "value": "var set = datasources[\"Nodes\"][\"nodes\"];return set[Object.keys(set)[0]][\"indices\"][\"docs\"].count;", 120 | "sparkline": true, 121 | "animate": false 122 | } 123 | }, 124 | { 125 | "type": "text_widget", 126 | "settings": { 127 | "title": "Heap Usage", 128 | "size": "regular", 129 | "value": "var set = datasources[\"Nodes\"][\"nodes\"];return set[Object.keys(set)[0]][\"jvm\"][\"mem\"][\"heap_used_percent\"];", 130 | "animate": true, 131 | "units": "%" 132 | } 133 | }, 134 | { 135 | "type": "text_widget", 136 | "settings": { 137 | "title": "JVM Threads", 138 | "size": "regular", 139 | "value": "var set = datasources[\"Nodes\"][\"nodes\"];\nreturn set[Object.keys(set)[0]][\"jvm\"][\"threads\"].count+\" (\"+set[Object.keys(set)[0]][\"jvm\"][\"threads\"].count+\" peak)\";", 140 | "animate": true 141 | } 142 | } 143 | ] 144 | }, 145 | { 146 | "title": "Cluster Details", 147 | "width": 1, 148 | "row": { 149 | "3": 1, 150 | "4": 7, 151 | "5": 1 152 | }, 153 | "col": { 154 | "3": 2, 155 | "4": 1, 156 | "5": 1 157 | }, 158 | "col_width": 1, 159 | "widgets": [ 160 | { 161 | "type": "text_widget", 162 | "settings": { 163 | "title": "Name", 164 | "size": "regular", 165 | "value": "datasources[\"Cluster\"][\"cluster_name\"]", 166 | "animate": false 167 | } 168 | }, 169 | { 170 | "type": "indicator", 171 | "settings": { 172 | "title": "Status", 173 | "value": "var status = datasources[\"Cluster\"][\"status\"];\n\nif (status === \"green\") {\n return 0;\n} else {\n return 1;\n}", 174 | "on_text": "var status = datasources[\"Cluster\"][\"status\"];\n\nreturn \"Alarmed! (\"+status+\")\";", 175 | "off_text": "var status = datasources[\"Cluster\"][\"status\"];\n\nreturn \"No Alarms (\"+status+\")\";" 176 | } 177 | }, 178 | { 179 | "type": "sparkline", 180 | "settings": { 181 | "title": "Query Time", 182 | "value": "datasources[\"Search Ping\"][\"took\"]" 183 | } 184 | }, 185 | { 186 | "type": "sparkline", 187 | "settings": { 188 | "title": "Stats", 189 | "value": [ 190 | "datasources[\"Cluster\"][\"indices\"][\"shards\"][\"index\"][\"shards\"][\"max\"]", 191 | "datasources[\"Cluster\"][\"indices\"][\"shards\"][\"index\"][\"primaries\"][\"max\"]", 192 | "datasources[\"Cluster\"][\"indices\"][\"shards\"][\"index\"][\"replication\"][\"max\"]" 193 | ], 194 | "include_legend": true, 195 | "legend": "total,primaries,replication" 196 | } 197 | }, 198 | { 199 | "type": "sparkline", 200 | "settings": { 201 | "title": "Active Shards", 202 | "value": [ 203 | "datasources[\"Cluster\"][\"indices\"][\"shards\"][\"total\"]", 204 | "datasources[\"Cluster\"][\"nodes\"][\"count\"][\"total\"]" 205 | ], 206 | "include_legend": true, 207 | "legend": "shards, nodes" 208 | } 209 | } 210 | ] 211 | }, 212 | { 213 | "title": "Node List", 214 | "width": 1, 215 | "row": { 216 | "3": 13 217 | }, 218 | "col": { 219 | "3": 1 220 | }, 221 | "col_width": 1, 222 | "widgets": [ 223 | { 224 | "type": "list", 225 | "settings": { 226 | "show_header": true, 227 | "value": "datasources[\"Nodes\"][\"nodes\"]" 228 | } 229 | } 230 | ] 231 | } 232 | ], 233 | "datasources": [ 234 | { 235 | "name": "Cluster", 236 | "type": "JSON", 237 | "settings": { 238 | "url": "/_cluster/stats", 239 | "use_thingproxy": false, 240 | "refresh": 30, 241 | "method": "GET", 242 | "name": "Cluster" 243 | } 244 | }, 245 | { 246 | "name": "Nodes", 247 | "type": "JSON", 248 | "settings": { 249 | "url": "/_nodes/stats/", 250 | "use_thingproxy": false, 251 | "refresh": 30, 252 | "method": "GET", 253 | "name": "Nodes" 254 | } 255 | }, 256 | { 257 | "name": "Search Ping", 258 | "type": "JSON", 259 | "settings": { 260 | "url": "/_search?size=0", 261 | "use_thingproxy": false, 262 | "refresh": 30, 263 | "method": "GET" 264 | } 265 | }, 266 | { 267 | "name": "Indices", 268 | "type": "JSON", 269 | "settings": { 270 | "url": "/_nodes/stats/indices/docs,get,search", 271 | "use_thingproxy": false, 272 | "refresh": 30, 273 | "method": "GET" 274 | } 275 | } 276 | ], 277 | "columns": 3 278 | } 279 | -------------------------------------------------------------------------------- /_site/docs/build-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docco --css docco-fb.css ./../examples/plugin_example.js --output ./ -------------------------------------------------------------------------------- /_site/docs/docco-fb.css: -------------------------------------------------------------------------------- 1 | /*--------------------- Typography ----------------------------*/ 2 | 3 | @import url(http://fonts.googleapis.com/css?family=Montserrat); 4 | 5 | @font-face 6 | { 7 | font-family : 'aller-light'; 8 | src : url('public/fonts/aller-light.eot'); 9 | src : url('public/fonts/aller-light.eot?#iefix') format('embedded-opentype'), url('public/fonts/aller-light.woff') format('woff'), url('public/fonts/aller-light.ttf') format('truetype'); 10 | font-weight : normal; 11 | font-style : normal; 12 | } 13 | 14 | @font-face 15 | { 16 | font-family : 'aller-bold'; 17 | src : url('public/fonts/aller-bold.eot'); 18 | src : url('public/fonts/aller-bold.eot?#iefix') format('embedded-opentype'), url('public/fonts/aller-bold.woff') format('woff'), url('public/fonts/aller-bold.ttf') format('truetype'); 19 | font-weight : normal; 20 | font-style : normal; 21 | } 22 | 23 | @font-face 24 | { 25 | font-family : 'novecento-bold'; 26 | src : url('public/fonts/novecento-bold.eot'); 27 | src : url('public/fonts/novecento-bold.eot?#iefix') format('embedded-opentype'), url('public/fonts/novecento-bold.woff') format('woff'), url('public/fonts/novecento-bold.ttf') format('truetype'); 28 | font-weight : normal; 29 | font-style : normal; 30 | } 31 | 32 | /*--------------------- Layout ----------------------------*/ 33 | html 34 | { 35 | height : 100%; 36 | } 37 | 38 | body 39 | { 40 | font-family : "Helvetica Neue", Helvetica, Arial, sans-serif; 41 | font-size : 14px; 42 | line-height : 18px; 43 | color : #30404f; 44 | margin : 0; 45 | padding : 0; 46 | height : 100%; 47 | } 48 | 49 | #container 50 | { 51 | min-height : 100%; 52 | } 53 | 54 | a 55 | { 56 | color : #000; 57 | } 58 | 59 | b, strong 60 | { 61 | font-weight : 400; 62 | color : #fff; 63 | } 64 | 65 | p, ul, ol 66 | { 67 | margin : 15px 0 0px; 68 | } 69 | 70 | h1, h2, h3, h4, h5, h6 71 | { 72 | font-family : montserrat, sans-serif; 73 | color : #fff; 74 | line-height : 1em; 75 | font-weight : normal; 76 | text-transform : uppercase; 77 | margin : 30px 0 15px 0; 78 | } 79 | 80 | h1 81 | { 82 | margin-top : 40px; 83 | } 84 | 85 | hr 86 | { 87 | border : 0; 88 | background : 1px solid #ddd; 89 | height : 1px; 90 | margin : 20px 0; 91 | } 92 | 93 | pre, tt, code 94 | { 95 | font-size : 12px; 96 | line-height : 16px; 97 | font-family : Menlo, Monaco, Consolas, "Lucida Console", monospace; 98 | margin : 0; 99 | padding : 0; 100 | } 101 | 102 | .annotation 103 | { 104 | color: #A5A5A5; 105 | font-weight : 200; 106 | font-size: 14px; 107 | } 108 | 109 | .annotation pre 110 | { 111 | display : block; 112 | margin : 0; 113 | padding : 7px 10px; 114 | background : #fcfcfc; 115 | -moz-box-shadow : inset 0 0 10px rgba(0, 0, 0, 0.1); 116 | -webkit-box-shadow : inset 0 0 10px rgba(0, 0, 0, 0.1); 117 | box-shadow : inset 0 0 10px rgba(0, 0, 0, 0.1); 118 | overflow-x : auto; 119 | } 120 | 121 | .annotation pre code 122 | { 123 | border : 0; 124 | padding : 0; 125 | background : transparent; 126 | } 127 | 128 | blockquote 129 | { 130 | border-left : 5px solid #ccc; 131 | margin : 0; 132 | padding : 1px 0 1px 1em; 133 | } 134 | 135 | .sections blockquote p 136 | { 137 | font-family : Menlo, Consolas, Monaco, monospace; 138 | font-size : 12px; 139 | line-height : 16px; 140 | color : #999; 141 | margin : 10px 0 0; 142 | white-space : pre-wrap; 143 | } 144 | 145 | ul.sections 146 | { 147 | list-style : none; 148 | padding : 0 0 5px 0;; 149 | margin : 0; 150 | } 151 | 152 | /* 153 | Force border-box so that % widths fit the parent 154 | container without overlap because of margin/padding. 155 | 156 | More Info : http://www.quirksmode.org/css/box.html 157 | */ 158 | ul.sections > li > div 159 | { 160 | -moz-box-sizing : border-box; /* firefox */ 161 | -ms-box-sizing : border-box; /* ie */ 162 | -webkit-box-sizing : border-box; /* webkit */ 163 | -khtml-box-sizing : border-box; /* konqueror */ 164 | box-sizing : border-box; /* css3 */ 165 | } 166 | 167 | /*---------------------- Jump Page -----------------------------*/ 168 | #jump_to, #jump_page 169 | { 170 | margin : 0; 171 | background : white; 172 | -webkit-box-shadow : 0 0 25px #777; 173 | -moz-box-shadow : 0 0 25px #777; 174 | -webkit-border-bottom-left-radius : 5px; 175 | -moz-border-radius-bottomleft : 5px; 176 | font : 16px Arial; 177 | cursor : pointer; 178 | text-align : right; 179 | list-style : none; 180 | } 181 | 182 | #jump_to a 183 | { 184 | text-decoration : none; 185 | } 186 | 187 | #jump_to a.large 188 | { 189 | display : none; 190 | } 191 | 192 | #jump_to a.small 193 | { 194 | font-size : 22px; 195 | font-weight : bold; 196 | color : #676767; 197 | } 198 | 199 | #jump_to, #jump_wrapper 200 | { 201 | position : fixed; 202 | right : 0; 203 | top : 0; 204 | padding : 10px 15px; 205 | margin : 0; 206 | } 207 | 208 | #jump_wrapper 209 | { 210 | display : none; 211 | padding : 0; 212 | } 213 | 214 | #jump_to:hover #jump_wrapper 215 | { 216 | display : block; 217 | } 218 | 219 | #jump_page 220 | { 221 | padding : 5px 0 3px; 222 | margin : 0 0 25px 25px; 223 | } 224 | 225 | #jump_page .source 226 | { 227 | display : block; 228 | padding : 15px; 229 | text-decoration : none; 230 | border-top : 1px solid #eee; 231 | } 232 | 233 | #jump_page .source:hover 234 | { 235 | background : #f5f5ff; 236 | } 237 | 238 | #jump_page .source:first-child 239 | { 240 | } 241 | 242 | /*---------------------- Low resolutions (> 320px) ---------------------*/ 243 | @media only screen and (min-width: 320px) 244 | { 245 | .pilwrap 246 | { 247 | display : none; 248 | } 249 | 250 | ul.sections > li > div 251 | { 252 | display : block; 253 | padding : 5px 10px 0 10px; 254 | } 255 | 256 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol 257 | { 258 | padding-left : 30px; 259 | } 260 | 261 | ul.sections > li > div.content 262 | { 263 | background : #f5f5ff; 264 | overflow-x : auto; 265 | -webkit-box-shadow : inset 0 0 5px #e5e5ee; 266 | box-shadow : inset 0 0 5px #e5e5ee; 267 | border : 1px solid #dedede; 268 | margin : 5px 10px 5px 10px; 269 | padding-bottom : 5px; 270 | } 271 | 272 | ul.sections > li > div.annotation pre 273 | { 274 | margin : 7px 0 7px; 275 | padding-left : 15px; 276 | } 277 | 278 | ul.sections > li > div.annotation p tt, .annotation code 279 | { 280 | background : #f8f8ff; 281 | border : 1px solid #dedede; 282 | font-size : 12px; 283 | padding : 0 0.2em; 284 | } 285 | } 286 | 287 | /*---------------------- (> 481px) ---------------------*/ 288 | @media only screen and (min-width: 481px) 289 | { 290 | #container 291 | { 292 | position : relative; 293 | } 294 | 295 | body 296 | { 297 | background-color : #f9f9f9; 298 | font-size : 15px; 299 | line-height : 21px; 300 | } 301 | 302 | pre, tt, code 303 | { 304 | line-height : 18px; 305 | } 306 | 307 | p, ul, ol 308 | { 309 | margin : 0 0 15px; 310 | } 311 | 312 | #jump_to 313 | { 314 | padding : 5px 10px; 315 | } 316 | 317 | #jump_wrapper 318 | { 319 | padding : 0; 320 | } 321 | 322 | #jump_to, #jump_page 323 | { 324 | font : 10px Arial; 325 | text-transform : uppercase; 326 | } 327 | 328 | #jump_page .source 329 | { 330 | padding : 5px 10px; 331 | } 332 | 333 | #jump_to a.large 334 | { 335 | display : inline-block; 336 | } 337 | 338 | #jump_to a.small 339 | { 340 | display : none; 341 | } 342 | 343 | #background 344 | { 345 | position : absolute; 346 | top : 0; 347 | bottom : 0; 348 | width : 350px; 349 | background : #313131; 350 | border-right : 1px solid #e5e5ee; 351 | z-index : -1; 352 | } 353 | 354 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol 355 | { 356 | padding-left : 40px; 357 | } 358 | 359 | ul.sections > li 360 | { 361 | white-space : nowrap; 362 | } 363 | 364 | ul.sections > li > div 365 | { 366 | display : inline-block; 367 | } 368 | 369 | ul.sections > li > div.annotation 370 | { 371 | max-width : 350px; 372 | min-width : 350px; 373 | min-height : 5px; 374 | padding : 13px; 375 | overflow-x : hidden; 376 | white-space : normal; 377 | vertical-align : top; 378 | text-align : left; 379 | } 380 | 381 | ul.sections > li > div.annotation pre 382 | { 383 | margin : 15px 0 15px; 384 | padding-left : 15px; 385 | } 386 | 387 | ul.sections > li > div.content 388 | { 389 | padding : 13px; 390 | vertical-align : top; 391 | background : #f9f9f9; 392 | border : none; 393 | -webkit-box-shadow : none; 394 | box-shadow : none; 395 | } 396 | 397 | .pilwrap 398 | { 399 | position : relative; 400 | display : inline; 401 | } 402 | 403 | .pilcrow 404 | { 405 | font : 12px Arial; 406 | text-decoration : none; 407 | color : #454545; 408 | position : absolute; 409 | top : 3px; 410 | left : -20px; 411 | padding : 1px 2px; 412 | opacity : 0; 413 | -webkit-transition : opacity 0.2s linear; 414 | } 415 | 416 | .for-h1 .pilcrow 417 | { 418 | top : 47px; 419 | } 420 | 421 | .for-h2 .pilcrow, .for-h3 .pilcrow, .for-h4 .pilcrow 422 | { 423 | top : 35px; 424 | } 425 | 426 | ul.sections > li > div.annotation:hover .pilcrow 427 | { 428 | opacity : 1; 429 | } 430 | } 431 | 432 | /*---------------------- (> 1025px) ---------------------*/ 433 | @media only screen and (min-width: 1025px) 434 | { 435 | 436 | body 437 | { 438 | font-size : 16px; 439 | line-height : 24px; 440 | } 441 | 442 | #background 443 | { 444 | width : 525px; 445 | } 446 | 447 | ul.sections > li > div.annotation 448 | { 449 | max-width : 525px; 450 | min-width : 525px; 451 | padding : 10px 25px 1px 50px; 452 | } 453 | 454 | ul.sections > li > div.content 455 | { 456 | padding : 9px 15px 16px 25px; 457 | } 458 | } 459 | 460 | /*---------------------- Syntax Highlighting -----------------------------*/ 461 | 462 | td.linenos 463 | { 464 | background-color : #f0f0f0; 465 | padding-right : 10px; 466 | } 467 | 468 | span.lineno 469 | { 470 | background-color : #f0f0f0; 471 | padding : 0 5px 0 5px; 472 | } 473 | 474 | /* 475 | 476 | github.com style (c) Vasily Polovnyov 477 | 478 | */ 479 | 480 | pre code 481 | { 482 | display : block; 483 | padding : 0.5em; 484 | color : #000; 485 | background : #f8f8ff 486 | } 487 | 488 | pre .comment, 489 | pre .template_comment, 490 | pre .diff .header, 491 | pre .javadoc 492 | { 493 | color : #408080; 494 | font-style : italic 495 | } 496 | 497 | pre .keyword, 498 | pre .assignment, 499 | pre .literal, 500 | pre .css .rule .keyword, 501 | pre .winutils, 502 | pre .javascript .title, 503 | pre .lisp .title, 504 | pre .subst 505 | { 506 | color : #954121; 507 | /*font-weight: bold*/ 508 | } 509 | 510 | pre .number, 511 | pre .hexcolor 512 | { 513 | color : #40a070 514 | } 515 | 516 | pre .string, 517 | pre .tag .value, 518 | pre .phpdoc, 519 | pre .tex .formula 520 | { 521 | color : #219161; 522 | } 523 | 524 | pre .title, 525 | pre .id 526 | { 527 | color : #19469D; 528 | } 529 | 530 | pre .params 531 | { 532 | color : #00F; 533 | } 534 | 535 | pre .javascript .title, 536 | pre .lisp .title, 537 | pre .subst 538 | { 539 | font-weight : normal 540 | } 541 | 542 | pre .class .title, 543 | pre .haskell .label, 544 | pre .tex .command 545 | { 546 | color : #458; 547 | font-weight : bold 548 | } 549 | 550 | pre .tag, 551 | pre .tag .title, 552 | pre .rules .property, 553 | pre .django .tag .keyword 554 | { 555 | color : #000080; 556 | font-weight : normal 557 | } 558 | 559 | pre .attribute, 560 | pre .variable, 561 | pre .instancevar, 562 | pre .lisp .body 563 | { 564 | color : #008080 565 | } 566 | 567 | pre .regexp 568 | { 569 | color : #B68 570 | } 571 | 572 | pre .class 573 | { 574 | color : #458; 575 | font-weight : bold 576 | } 577 | 578 | pre .symbol, 579 | pre .ruby .symbol .string, 580 | pre .ruby .symbol .keyword, 581 | pre .ruby .symbol .keymethods, 582 | pre .lisp .keyword, 583 | pre .tex .special, 584 | pre .input_number 585 | { 586 | color : #990073 587 | } 588 | 589 | pre .builtin, 590 | pre .constructor, 591 | pre .built_in, 592 | pre .lisp .title 593 | { 594 | color : #0086b3 595 | } 596 | 597 | pre .preprocessor, 598 | pre .pi, 599 | pre .doctype, 600 | pre .shebang, 601 | pre .cdata 602 | { 603 | color : #999; 604 | font-weight : bold 605 | } 606 | 607 | pre .deletion 608 | { 609 | background : #fdd 610 | } 611 | 612 | pre .addition 613 | { 614 | background : #dfd 615 | } 616 | 617 | pre .diff .change 618 | { 619 | background : #0086b3 620 | } 621 | 622 | pre .chunk 623 | { 624 | color : #aaa 625 | } 626 | 627 | pre .tex .formula 628 | { 629 | opacity : 0.5; 630 | } 631 | -------------------------------------------------------------------------------- /_site/docs/docco.css: -------------------------------------------------------------------------------- 1 | /*--------------------- Typography ----------------------------*/ 2 | 3 | @font-face { 4 | font-family: 'aller-light'; 5 | src: url('public/fonts/aller-light.eot'); 6 | src: url('public/fonts/aller-light.eot?#iefix') format('embedded-opentype'), 7 | url('public/fonts/aller-light.woff') format('woff'), 8 | url('public/fonts/aller-light.ttf') format('truetype'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | @font-face { 14 | font-family: 'aller-bold'; 15 | src: url('public/fonts/aller-bold.eot'); 16 | src: url('public/fonts/aller-bold.eot?#iefix') format('embedded-opentype'), 17 | url('public/fonts/aller-bold.woff') format('woff'), 18 | url('public/fonts/aller-bold.ttf') format('truetype'); 19 | font-weight: normal; 20 | font-style: normal; 21 | } 22 | 23 | @font-face { 24 | font-family: 'novecento-bold'; 25 | src: url('public/fonts/novecento-bold.eot'); 26 | src: url('public/fonts/novecento-bold.eot?#iefix') format('embedded-opentype'), 27 | url('public/fonts/novecento-bold.woff') format('woff'), 28 | url('public/fonts/novecento-bold.ttf') format('truetype'); 29 | font-weight: normal; 30 | font-style: normal; 31 | } 32 | 33 | /*--------------------- Layout ----------------------------*/ 34 | html { height: 100%; } 35 | body { 36 | font-family: "aller-light"; 37 | font-size: 14px; 38 | line-height: 18px; 39 | color: #30404f; 40 | margin: 0; padding: 0; 41 | height:100%; 42 | } 43 | #container { min-height: 100%; } 44 | 45 | a { 46 | color: #000; 47 | } 48 | 49 | b, strong { 50 | font-weight: normal; 51 | font-family: "aller-bold"; 52 | } 53 | 54 | p, ul, ol { 55 | margin: 15px 0 0px; 56 | } 57 | 58 | h1, h2, h3, h4, h5, h6 { 59 | color: #112233; 60 | line-height: 1em; 61 | font-weight: normal; 62 | font-family: "novecento-bold"; 63 | text-transform: uppercase; 64 | margin: 30px 0 15px 0; 65 | } 66 | 67 | h1 { 68 | margin-top: 40px; 69 | } 70 | 71 | hr { 72 | border: 0; 73 | background: 1px solid #ddd; 74 | height: 1px; 75 | margin: 20px 0; 76 | } 77 | 78 | pre, tt, code { 79 | font-size: 12px; line-height: 16px; 80 | font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; 81 | margin: 0; padding: 0; 82 | } 83 | .annotation pre { 84 | display: block; 85 | margin: 0; 86 | padding: 7px 10px; 87 | background: #fcfcfc; 88 | -moz-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 89 | -webkit-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 90 | box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 91 | overflow-x: auto; 92 | } 93 | .annotation pre code { 94 | border: 0; 95 | padding: 0; 96 | background: transparent; 97 | } 98 | 99 | 100 | blockquote { 101 | border-left: 5px solid #ccc; 102 | margin: 0; 103 | padding: 1px 0 1px 1em; 104 | } 105 | .sections blockquote p { 106 | font-family: Menlo, Consolas, Monaco, monospace; 107 | font-size: 12px; line-height: 16px; 108 | color: #999; 109 | margin: 10px 0 0; 110 | white-space: pre-wrap; 111 | } 112 | 113 | ul.sections { 114 | list-style: none; 115 | padding:0 0 5px 0;; 116 | margin:0; 117 | } 118 | 119 | /* 120 | Force border-box so that % widths fit the parent 121 | container without overlap because of margin/padding. 122 | 123 | More Info : http://www.quirksmode.org/css/box.html 124 | */ 125 | ul.sections > li > div { 126 | -moz-box-sizing: border-box; /* firefox */ 127 | -ms-box-sizing: border-box; /* ie */ 128 | -webkit-box-sizing: border-box; /* webkit */ 129 | -khtml-box-sizing: border-box; /* konqueror */ 130 | box-sizing: border-box; /* css3 */ 131 | } 132 | 133 | 134 | /*---------------------- Jump Page -----------------------------*/ 135 | #jump_to, #jump_page { 136 | margin: 0; 137 | background: white; 138 | -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; 139 | -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; 140 | font: 16px Arial; 141 | cursor: pointer; 142 | text-align: right; 143 | list-style: none; 144 | } 145 | 146 | #jump_to a { 147 | text-decoration: none; 148 | } 149 | 150 | #jump_to a.large { 151 | display: none; 152 | } 153 | #jump_to a.small { 154 | font-size: 22px; 155 | font-weight: bold; 156 | color: #676767; 157 | } 158 | 159 | #jump_to, #jump_wrapper { 160 | position: fixed; 161 | right: 0; top: 0; 162 | padding: 10px 15px; 163 | margin:0; 164 | } 165 | 166 | #jump_wrapper { 167 | display: none; 168 | padding:0; 169 | } 170 | 171 | #jump_to:hover #jump_wrapper { 172 | display: block; 173 | } 174 | 175 | #jump_page { 176 | padding: 5px 0 3px; 177 | margin: 0 0 25px 25px; 178 | } 179 | 180 | #jump_page .source { 181 | display: block; 182 | padding: 15px; 183 | text-decoration: none; 184 | border-top: 1px solid #eee; 185 | } 186 | 187 | #jump_page .source:hover { 188 | background: #f5f5ff; 189 | } 190 | 191 | #jump_page .source:first-child { 192 | } 193 | 194 | /*---------------------- Low resolutions (> 320px) ---------------------*/ 195 | @media only screen and (min-width: 320px) { 196 | .pilwrap { display: none; } 197 | 198 | ul.sections > li > div { 199 | display: block; 200 | padding:5px 10px 0 10px; 201 | } 202 | 203 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { 204 | padding-left: 30px; 205 | } 206 | 207 | ul.sections > li > div.content { 208 | background: #f5f5ff; 209 | overflow-x:auto; 210 | -webkit-box-shadow: inset 0 0 5px #e5e5ee; 211 | box-shadow: inset 0 0 5px #e5e5ee; 212 | border: 1px solid #dedede; 213 | margin:5px 10px 5px 10px; 214 | padding-bottom: 5px; 215 | } 216 | 217 | ul.sections > li > div.annotation pre { 218 | margin: 7px 0 7px; 219 | padding-left: 15px; 220 | } 221 | 222 | ul.sections > li > div.annotation p tt, .annotation code { 223 | background: #f8f8ff; 224 | border: 1px solid #dedede; 225 | font-size: 12px; 226 | padding: 0 0.2em; 227 | } 228 | } 229 | 230 | /*---------------------- (> 481px) ---------------------*/ 231 | @media only screen and (min-width: 481px) { 232 | #container { 233 | position: relative; 234 | } 235 | body { 236 | background-color: #F5F5FF; 237 | font-size: 15px; 238 | line-height: 21px; 239 | } 240 | pre, tt, code { 241 | line-height: 18px; 242 | } 243 | p, ul, ol { 244 | margin: 0 0 15px; 245 | } 246 | 247 | 248 | #jump_to { 249 | padding: 5px 10px; 250 | } 251 | #jump_wrapper { 252 | padding: 0; 253 | } 254 | #jump_to, #jump_page { 255 | font: 10px Arial; 256 | text-transform: uppercase; 257 | } 258 | #jump_page .source { 259 | padding: 5px 10px; 260 | } 261 | #jump_to a.large { 262 | display: inline-block; 263 | } 264 | #jump_to a.small { 265 | display: none; 266 | } 267 | 268 | 269 | 270 | #background { 271 | position: absolute; 272 | top: 0; bottom: 0; 273 | width: 350px; 274 | background: #fff; 275 | border-right: 1px solid #e5e5ee; 276 | z-index: -1; 277 | } 278 | 279 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { 280 | padding-left: 40px; 281 | } 282 | 283 | ul.sections > li { 284 | white-space: nowrap; 285 | } 286 | 287 | ul.sections > li > div { 288 | display: inline-block; 289 | } 290 | 291 | ul.sections > li > div.annotation { 292 | max-width: 350px; 293 | min-width: 350px; 294 | min-height: 5px; 295 | padding: 13px; 296 | overflow-x: hidden; 297 | white-space: normal; 298 | vertical-align: top; 299 | text-align: left; 300 | } 301 | ul.sections > li > div.annotation pre { 302 | margin: 15px 0 15px; 303 | padding-left: 15px; 304 | } 305 | 306 | ul.sections > li > div.content { 307 | padding: 13px; 308 | vertical-align: top; 309 | background: #f5f5ff; 310 | border: none; 311 | -webkit-box-shadow: none; 312 | box-shadow: none; 313 | } 314 | 315 | .pilwrap { 316 | position: relative; 317 | display: inline; 318 | } 319 | 320 | .pilcrow { 321 | font: 12px Arial; 322 | text-decoration: none; 323 | color: #454545; 324 | position: absolute; 325 | top: 3px; left: -20px; 326 | padding: 1px 2px; 327 | opacity: 0; 328 | -webkit-transition: opacity 0.2s linear; 329 | } 330 | .for-h1 .pilcrow { 331 | top: 47px; 332 | } 333 | .for-h2 .pilcrow, .for-h3 .pilcrow, .for-h4 .pilcrow { 334 | top: 35px; 335 | } 336 | 337 | ul.sections > li > div.annotation:hover .pilcrow { 338 | opacity: 1; 339 | } 340 | } 341 | 342 | /*---------------------- (> 1025px) ---------------------*/ 343 | @media only screen and (min-width: 1025px) { 344 | 345 | body { 346 | font-size: 16px; 347 | line-height: 24px; 348 | } 349 | 350 | #background { 351 | width: 525px; 352 | } 353 | ul.sections > li > div.annotation { 354 | max-width: 525px; 355 | min-width: 525px; 356 | padding: 10px 25px 1px 50px; 357 | } 358 | ul.sections > li > div.content { 359 | padding: 9px 15px 16px 25px; 360 | } 361 | } 362 | 363 | /*---------------------- Syntax Highlighting -----------------------------*/ 364 | 365 | td.linenos { background-color: #f0f0f0; padding-right: 10px; } 366 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } 367 | /* 368 | 369 | github.com style (c) Vasily Polovnyov 370 | 371 | */ 372 | 373 | pre code { 374 | display: block; padding: 0.5em; 375 | color: #000; 376 | background: #f8f8ff 377 | } 378 | 379 | pre .comment, 380 | pre .template_comment, 381 | pre .diff .header, 382 | pre .javadoc { 383 | color: #408080; 384 | font-style: italic 385 | } 386 | 387 | pre .keyword, 388 | pre .assignment, 389 | pre .literal, 390 | pre .css .rule .keyword, 391 | pre .winutils, 392 | pre .javascript .title, 393 | pre .lisp .title, 394 | pre .subst { 395 | color: #954121; 396 | /*font-weight: bold*/ 397 | } 398 | 399 | pre .number, 400 | pre .hexcolor { 401 | color: #40a070 402 | } 403 | 404 | pre .string, 405 | pre .tag .value, 406 | pre .phpdoc, 407 | pre .tex .formula { 408 | color: #219161; 409 | } 410 | 411 | pre .title, 412 | pre .id { 413 | color: #19469D; 414 | } 415 | pre .params { 416 | color: #00F; 417 | } 418 | 419 | pre .javascript .title, 420 | pre .lisp .title, 421 | pre .subst { 422 | font-weight: normal 423 | } 424 | 425 | pre .class .title, 426 | pre .haskell .label, 427 | pre .tex .command { 428 | color: #458; 429 | font-weight: bold 430 | } 431 | 432 | pre .tag, 433 | pre .tag .title, 434 | pre .rules .property, 435 | pre .django .tag .keyword { 436 | color: #000080; 437 | font-weight: normal 438 | } 439 | 440 | pre .attribute, 441 | pre .variable, 442 | pre .instancevar, 443 | pre .lisp .body { 444 | color: #008080 445 | } 446 | 447 | pre .regexp { 448 | color: #B68 449 | } 450 | 451 | pre .class { 452 | color: #458; 453 | font-weight: bold 454 | } 455 | 456 | pre .symbol, 457 | pre .ruby .symbol .string, 458 | pre .ruby .symbol .keyword, 459 | pre .ruby .symbol .keymethods, 460 | pre .lisp .keyword, 461 | pre .tex .special, 462 | pre .input_number { 463 | color: #990073 464 | } 465 | 466 | pre .builtin, 467 | pre .constructor, 468 | pre .built_in, 469 | pre .lisp .title { 470 | color: #0086b3 471 | } 472 | 473 | pre .preprocessor, 474 | pre .pi, 475 | pre .doctype, 476 | pre .shebang, 477 | pre .cdata { 478 | color: #999; 479 | font-weight: bold 480 | } 481 | 482 | pre .deletion { 483 | background: #fdd 484 | } 485 | 486 | pre .addition { 487 | background: #dfd 488 | } 489 | 490 | pre .diff .change { 491 | background: #0086b3 492 | } 493 | 494 | pre .chunk { 495 | color: #aaa 496 | } 497 | 498 | pre .tex .formula { 499 | opacity: 0.5; 500 | } 501 | -------------------------------------------------------------------------------- /_site/docs/public/fonts/aller-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elasticfence/freeboard-elasticsearch/ba020fa0a3d8b71690713e1fcdf2366ce3628419/_site/docs/public/fonts/aller-bold.eot -------------------------------------------------------------------------------- /_site/docs/public/fonts/aller-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elasticfence/freeboard-elasticsearch/ba020fa0a3d8b71690713e1fcdf2366ce3628419/_site/docs/public/fonts/aller-bold.ttf -------------------------------------------------------------------------------- /_site/docs/public/fonts/aller-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elasticfence/freeboard-elasticsearch/ba020fa0a3d8b71690713e1fcdf2366ce3628419/_site/docs/public/fonts/aller-bold.woff -------------------------------------------------------------------------------- /_site/docs/public/fonts/aller-light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elasticfence/freeboard-elasticsearch/ba020fa0a3d8b71690713e1fcdf2366ce3628419/_site/docs/public/fonts/aller-light.eot -------------------------------------------------------------------------------- /_site/docs/public/fonts/aller-light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elasticfence/freeboard-elasticsearch/ba020fa0a3d8b71690713e1fcdf2366ce3628419/_site/docs/public/fonts/aller-light.ttf -------------------------------------------------------------------------------- /_site/docs/public/fonts/aller-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elasticfence/freeboard-elasticsearch/ba020fa0a3d8b71690713e1fcdf2366ce3628419/_site/docs/public/fonts/aller-light.woff -------------------------------------------------------------------------------- /_site/docs/public/fonts/fleurons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elasticfence/freeboard-elasticsearch/ba020fa0a3d8b71690713e1fcdf2366ce3628419/_site/docs/public/fonts/fleurons.eot -------------------------------------------------------------------------------- /_site/docs/public/fonts/fleurons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elasticfence/freeboard-elasticsearch/ba020fa0a3d8b71690713e1fcdf2366ce3628419/_site/docs/public/fonts/fleurons.ttf -------------------------------------------------------------------------------- /_site/docs/public/fonts/fleurons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elasticfence/freeboard-elasticsearch/ba020fa0a3d8b71690713e1fcdf2366ce3628419/_site/docs/public/fonts/fleurons.woff -------------------------------------------------------------------------------- /_site/docs/public/fonts/novecento-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elasticfence/freeboard-elasticsearch/ba020fa0a3d8b71690713e1fcdf2366ce3628419/_site/docs/public/fonts/novecento-bold.eot -------------------------------------------------------------------------------- /_site/docs/public/fonts/novecento-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elasticfence/freeboard-elasticsearch/ba020fa0a3d8b71690713e1fcdf2366ce3628419/_site/docs/public/fonts/novecento-bold.ttf -------------------------------------------------------------------------------- /_site/docs/public/fonts/novecento-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elasticfence/freeboard-elasticsearch/ba020fa0a3d8b71690713e1fcdf2366ce3628419/_site/docs/public/fonts/novecento-bold.woff -------------------------------------------------------------------------------- /_site/docs/public/images/gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elasticfence/freeboard-elasticsearch/ba020fa0a3d8b71690713e1fcdf2366ce3628419/_site/docs/public/images/gray.png -------------------------------------------------------------------------------- /_site/docs/public/stylesheets/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.0.1 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /* 8 | * Corrects `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | nav, 20 | section, 21 | summary { 22 | display: block; 23 | } 24 | 25 | /* 26 | * Corrects `inline-block` display not defined in IE 8/9. 27 | */ 28 | 29 | audio, 30 | canvas, 31 | video { 32 | display: inline-block; 33 | } 34 | 35 | /* 36 | * Prevents modern browsers from displaying `audio` without controls. 37 | * Remove excess height in iOS 5 devices. 38 | */ 39 | 40 | audio:not([controls]) { 41 | display: none; 42 | height: 0; 43 | } 44 | 45 | /* 46 | * Addresses styling for `hidden` attribute not present in IE 8/9. 47 | */ 48 | 49 | [hidden] { 50 | display: none; 51 | } 52 | 53 | /* ========================================================================== 54 | Base 55 | ========================================================================== */ 56 | 57 | /* 58 | * 1. Sets default font family to sans-serif. 59 | * 2. Prevents iOS text size adjust after orientation change, without disabling 60 | * user zoom. 61 | */ 62 | 63 | html { 64 | font-family: sans-serif; /* 1 */ 65 | -webkit-text-size-adjust: 100%; /* 2 */ 66 | -ms-text-size-adjust: 100%; /* 2 */ 67 | } 68 | 69 | /* 70 | * Removes default margin. 71 | */ 72 | 73 | body { 74 | margin: 0; 75 | } 76 | 77 | /* ========================================================================== 78 | Links 79 | ========================================================================== */ 80 | 81 | /* 82 | * Addresses `outline` inconsistency between Chrome and other browsers. 83 | */ 84 | 85 | a:focus { 86 | outline: thin dotted; 87 | } 88 | 89 | /* 90 | * Improves readability when focused and also mouse hovered in all browsers. 91 | */ 92 | 93 | a:active, 94 | a:hover { 95 | outline: 0; 96 | } 97 | 98 | /* ========================================================================== 99 | Typography 100 | ========================================================================== */ 101 | 102 | /* 103 | * Addresses `h1` font sizes within `section` and `article` in Firefox 4+, 104 | * Safari 5, and Chrome. 105 | */ 106 | 107 | h1 { 108 | font-size: 2em; 109 | } 110 | 111 | /* 112 | * Addresses styling not present in IE 8/9, Safari 5, and Chrome. 113 | */ 114 | 115 | abbr[title] { 116 | border-bottom: 1px dotted; 117 | } 118 | 119 | /* 120 | * Addresses style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 121 | */ 122 | 123 | b, 124 | strong { 125 | font-weight: bold; 126 | } 127 | 128 | /* 129 | * Addresses styling not present in Safari 5 and Chrome. 130 | */ 131 | 132 | dfn { 133 | font-style: italic; 134 | } 135 | 136 | /* 137 | * Addresses styling not present in IE 8/9. 138 | */ 139 | 140 | mark { 141 | background: #ff0; 142 | color: #000; 143 | } 144 | 145 | 146 | /* 147 | * Corrects font family set oddly in Safari 5 and Chrome. 148 | */ 149 | 150 | code, 151 | kbd, 152 | pre, 153 | samp { 154 | font-family: monospace, serif; 155 | font-size: 1em; 156 | } 157 | 158 | /* 159 | * Improves readability of pre-formatted text in all browsers. 160 | */ 161 | 162 | pre { 163 | white-space: pre; 164 | white-space: pre-wrap; 165 | word-wrap: break-word; 166 | } 167 | 168 | /* 169 | * Sets consistent quote types. 170 | */ 171 | 172 | q { 173 | quotes: "\201C" "\201D" "\2018" "\2019"; 174 | } 175 | 176 | /* 177 | * Addresses inconsistent and variable font size in all browsers. 178 | */ 179 | 180 | small { 181 | font-size: 80%; 182 | } 183 | 184 | /* 185 | * Prevents `sub` and `sup` affecting `line-height` in all browsers. 186 | */ 187 | 188 | sub, 189 | sup { 190 | font-size: 75%; 191 | line-height: 0; 192 | position: relative; 193 | vertical-align: baseline; 194 | } 195 | 196 | sup { 197 | top: -0.5em; 198 | } 199 | 200 | sub { 201 | bottom: -0.25em; 202 | } 203 | 204 | /* ========================================================================== 205 | Embedded content 206 | ========================================================================== */ 207 | 208 | /* 209 | * Removes border when inside `a` element in IE 8/9. 210 | */ 211 | 212 | img { 213 | border: 0; 214 | } 215 | 216 | /* 217 | * Corrects overflow displayed oddly in IE 9. 218 | */ 219 | 220 | svg:not(:root) { 221 | overflow: hidden; 222 | } 223 | 224 | /* ========================================================================== 225 | Figures 226 | ========================================================================== */ 227 | 228 | /* 229 | * Addresses margin not present in IE 8/9 and Safari 5. 230 | */ 231 | 232 | figure { 233 | margin: 0; 234 | } 235 | 236 | /* ========================================================================== 237 | Forms 238 | ========================================================================== */ 239 | 240 | /* 241 | * Define consistent border, margin, and padding. 242 | */ 243 | 244 | fieldset { 245 | border: 1px solid #c0c0c0; 246 | margin: 0 2px; 247 | padding: 0.35em 0.625em 0.75em; 248 | } 249 | 250 | /* 251 | * 1. Corrects color not being inherited in IE 8/9. 252 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 253 | */ 254 | 255 | legend { 256 | border: 0; /* 1 */ 257 | padding: 0; /* 2 */ 258 | } 259 | 260 | /* 261 | * 1. Corrects font family not being inherited in all browsers. 262 | * 2. Corrects font size not being inherited in all browsers. 263 | * 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome 264 | */ 265 | 266 | button, 267 | input, 268 | select, 269 | textarea { 270 | font-family: inherit; /* 1 */ 271 | font-size: 100%; /* 2 */ 272 | margin: 0; /* 3 */ 273 | } 274 | 275 | /* 276 | * Addresses Firefox 4+ setting `line-height` on `input` using `!important` in 277 | * the UA stylesheet. 278 | */ 279 | 280 | button, 281 | input { 282 | line-height: normal; 283 | } 284 | 285 | /* 286 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 287 | * and `video` controls. 288 | * 2. Corrects inability to style clickable `input` types in iOS. 289 | * 3. Improves usability and consistency of cursor style between image-type 290 | * `input` and others. 291 | */ 292 | 293 | button, 294 | html input[type="button"], /* 1 */ 295 | input[type="reset"], 296 | input[type="submit"] { 297 | -webkit-appearance: button; /* 2 */ 298 | cursor: pointer; /* 3 */ 299 | } 300 | 301 | /* 302 | * Re-set default cursor for disabled elements. 303 | */ 304 | 305 | button[disabled], 306 | input[disabled] { 307 | cursor: default; 308 | } 309 | 310 | /* 311 | * 1. Addresses box sizing set to `content-box` in IE 8/9. 312 | * 2. Removes excess padding in IE 8/9. 313 | */ 314 | 315 | input[type="checkbox"], 316 | input[type="radio"] { 317 | box-sizing: border-box; /* 1 */ 318 | padding: 0; /* 2 */ 319 | } 320 | 321 | /* 322 | * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. 323 | * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome 324 | * (include `-moz` to future-proof). 325 | */ 326 | 327 | input[type="search"] { 328 | -webkit-appearance: textfield; /* 1 */ 329 | -moz-box-sizing: content-box; 330 | -webkit-box-sizing: content-box; /* 2 */ 331 | box-sizing: content-box; 332 | } 333 | 334 | /* 335 | * Removes inner padding and search cancel button in Safari 5 and Chrome 336 | * on OS X. 337 | */ 338 | 339 | input[type="search"]::-webkit-search-cancel-button, 340 | input[type="search"]::-webkit-search-decoration { 341 | -webkit-appearance: none; 342 | } 343 | 344 | /* 345 | * Removes inner padding and border in Firefox 4+. 346 | */ 347 | 348 | button::-moz-focus-inner, 349 | input::-moz-focus-inner { 350 | border: 0; 351 | padding: 0; 352 | } 353 | 354 | /* 355 | * 1. Removes default vertical scrollbar in IE 8/9. 356 | * 2. Improves readability and alignment in all browsers. 357 | */ 358 | 359 | textarea { 360 | overflow: auto; /* 1 */ 361 | vertical-align: top; /* 2 */ 362 | } 363 | 364 | /* ========================================================================== 365 | Tables 366 | ========================================================================== */ 367 | 368 | /* 369 | * Remove most spacing between table cells. 370 | */ 371 | 372 | table { 373 | border-collapse: collapse; 374 | border-spacing: 0; 375 | } -------------------------------------------------------------------------------- /_site/es-plugin.properties: -------------------------------------------------------------------------------- 1 | description=Freeboard Dashboards for ElasticSearch 2 | version=1.0-SNAPSHOT 3 | -------------------------------------------------------------------------------- /_site/examples/altGuage.js: -------------------------------------------------------------------------------- 1 | window.dyngaugeID = 0; 2 | (function() { 3 | var dynamicGaugeWidget = function (settings) { 4 | var self = this; 5 | thisDynGaugeID = "dyngauge-" + window.dyngaugeID++; 6 | var titleElement = $('

'); 7 | var gaugeElement = $('
'); 8 | 9 | var gaugeObject; 10 | var rendered = false; 11 | 12 | var currentSettings = settings; 13 | 14 | function createGauge() { 15 | if (!rendered) { 16 | return; 17 | } 18 | 19 | gaugeElement.empty(); 20 | 21 | gaugeObject = new JustGage({ 22 | id: thisDynGaugeID, 23 | value: (_.isUndefined(currentSettings.min_value) ? 0 : currentSettings.min_value), 24 | min: (_.isUndefined(currentSettings.min_value) ? 0 : currentSettings.min_value), 25 | max: (_.isUndefined(currentSettings.max_value) ? 0 : currentSettings.max_value), 26 | label: currentSettings.units, 27 | showInnerShadow: false, 28 | valueFontColor: "#d3d4d4", 29 | levelColors: ['#ff0000', '#ffa500','#ffa500','#ffff00', '#00ff00'] 30 | }); 31 | } 32 | 33 | this.render = function (element) { 34 | rendered = true; 35 | $(element).append(titleElement).append($('
').append(gaugeElement)); 36 | createGauge(); 37 | } 38 | 39 | this.onSettingsChanged = function (newSettings) { 40 | if (newSettings.min_value != currentSettings.min_value || newSettings.max_value != currentSettings.max_value || newSettings.units != currentSettings.units) { 41 | currentSettings = newSettings; 42 | createGauge(); 43 | } 44 | else { 45 | currentSettings = newSettings; 46 | } 47 | 48 | titleElement.html(newSettings.title); 49 | } 50 | 51 | this.onCalculatedValueChanged = function (settingName, newValue) { 52 | if (!_.isUndefined(gaugeObject)) { 53 | gaugeObject.refresh(Number(newValue)); 54 | } 55 | } 56 | 57 | this.onDispose = function () { 58 | } 59 | 60 | this.getHeight = function () { 61 | return 3; 62 | } 63 | 64 | this.onSettingsChanged(settings); 65 | }; 66 | 67 | freeboard.loadWidgetPlugin({ 68 | type_name: "dyngauge", 69 | display_name: "DynamicGauge", 70 | "external_scripts" : [ 71 | "plugins/thirdparty/raphael.2.1.0.min.js", 72 | "plugins/thirdparty/justgage.1.0.1.js" 73 | ], 74 | settings: [ 75 | { 76 | name: "title", 77 | display_name: "Title", 78 | type: "text" 79 | }, 80 | { 81 | name: "value", 82 | display_name: "Value", 83 | type: "calculated" 84 | }, 85 | { 86 | name: "units", 87 | display_name: "Units", 88 | type: "text" 89 | }, 90 | { 91 | name: "min_value", 92 | display_name: "Minimum", 93 | type: "text", 94 | default_value: 0 95 | }, 96 | { 97 | name: "max_value", 98 | display_name: "Maximum", 99 | type: "text", 100 | default_value: 100 101 | } 102 | ], 103 | newInstance: function (settings, newInstanceCallback) { 104 | newInstanceCallback(new dynamicGaugeWidget(settings)); 105 | } 106 | }); 107 | 108 | }()); 109 | 110 | -------------------------------------------------------------------------------- /_site/examples/plugin_example.js: -------------------------------------------------------------------------------- 1 | // # Building a Freeboard Plugin 2 | // 3 | // A freeboard plugin is simply a javascript file that is loaded into a web page after the main freeboard.js file is loaded. 4 | // 5 | // Let's get started with an example of a datasource plugin and a widget plugin. 6 | // 7 | // ------------------- 8 | 9 | // Best to encapsulate your plugin in a closure, although not required. 10 | (function() 11 | { 12 | // ## A Datasource Plugin 13 | // 14 | // ------------------- 15 | // ### Datasource Definition 16 | // 17 | // ------------------- 18 | // **freeboard.loadDatasourcePlugin(definition)** tells freeboard that we are giving it a datasource plugin. It expects an object with the following: 19 | freeboard.loadDatasourcePlugin({ 20 | // **type_name** (required) : A unique name for this plugin. This name should be as unique as possible to avoid collisions with other plugins, and should follow naming conventions for javascript variable and function declarations. 21 | "type_name" : "my_datasource_plugin", 22 | // **display_name** : The pretty name that will be used for display purposes for this plugin. If the name is not defined, type_name will be used instead. 23 | "display_name": "Datasource Plugin Example", 24 | // **description** : A description of the plugin. This description will be displayed when the plugin is selected or within search results (in the future). The description may contain HTML if needed. 25 | "description" : "Some sort of description with optional html!", 26 | // **external_scripts** : Any external scripts that should be loaded before the plugin instance is created. 27 | "external_scripts" : [ 28 | "http://mydomain.com/myscript1.js", 29 | "http://mydomain.com/myscript2.js" 30 | ], 31 | // **settings** : An array of settings that will be displayed for this plugin when the user adds it. 32 | "settings" : [ 33 | { 34 | // **name** (required) : The name of the setting. This value will be used in your code to retrieve the value specified by the user. This should follow naming conventions for javascript variable and function declarations. 35 | "name" : "first_name", 36 | // **display_name** : The pretty name that will be shown to the user when they adjust this setting. 37 | "display_name" : "First Name", 38 | // **type** (required) : The type of input expected for this setting. "text" will display a single text box input. Examples of other types will follow in this documentation. 39 | "type" : "text", 40 | // **default_value** : A default value for this setting. 41 | "default_value": "John", 42 | // **description** : Text that will be displayed below the setting to give the user any extra information. 43 | "description" : "This is pretty self explanatory...", 44 | // **required** : If set to true, the field will be required to be filled in by the user. Defaults to false if not specified. 45 | "required" : true 46 | }, 47 | { 48 | "name" : "last_name", 49 | "display_name": "Last Name", 50 | // **type "calculated"** : This is a special text input box that may contain javascript formulas and references to datasources in the freeboard. 51 | "type" : "calculated" 52 | }, 53 | { 54 | "name" : "age", 55 | "display_name": "Age", 56 | // **type "number"** : A data of a numerical type. Requires the user to enter a numerical value 57 | "type" : "number" 58 | }, 59 | { 60 | "name" : "is_human", 61 | "display_name": "I am human", 62 | // **type "boolean"** : Will display a checkbox indicating a true/false setting. 63 | "type" : "boolean" 64 | }, 65 | { 66 | "name" : "age", 67 | "display_name": "Your age", 68 | // **type "option"** : Will display a dropdown box with a list of choices. 69 | "type" : "option", 70 | // **options** (required) : An array of options to be populated in the dropdown. 71 | "options" : [ 72 | { 73 | // **name** (required) : The text to be displayed in the dropdown. 74 | "name" : "0-50", 75 | // **value** : The value of the option. If not specified, the name parameter will be used. 76 | "value": "young" 77 | }, 78 | { 79 | "name" : "51-100", 80 | "value": "old" 81 | } 82 | ] 83 | }, 84 | { 85 | "name" : "other", 86 | "display_name": "Other attributes", 87 | // **type "array"** : Will allow a user to enter in rows of data. 88 | "type" : "array", 89 | // **settings** (required) : An array of columns of the text to be entered by the user. 90 | "settings" : [ 91 | { 92 | "name" : "name", 93 | "display_name": "Name", 94 | "type" : "text" 95 | }, 96 | { 97 | "name" : "value", 98 | "display_name": "Value", 99 | "type" : "text" 100 | } 101 | ] 102 | }, 103 | { 104 | "name" : "refresh_time", 105 | "display_name" : "Refresh Time", 106 | "type" : "text", 107 | "description" : "In milliseconds", 108 | "default_value": 5000 109 | } 110 | ], 111 | // **newInstance(settings, newInstanceCallback, updateCallback)** (required) : A function that will be called when a new instance of this plugin is requested. 112 | // * **settings** : A javascript object with the initial settings set by the user. The names of the properties in the object will correspond to the setting names defined above. 113 | // * **newInstanceCallback** : A callback function that you'll call when the new instance of the plugin is ready. This function expects a single argument, which is the new instance of your plugin object. 114 | // * **updateCallback** : A callback function that you'll call if and when your datasource has an update for freeboard to recalculate. This function expects a single parameter which is a javascript object with the new, updated data. You should hold on to this reference and call it when needed. 115 | newInstance : function(settings, newInstanceCallback, updateCallback) 116 | { 117 | // myDatasourcePlugin is defined below. 118 | newInstanceCallback(new myDatasourcePlugin(settings, updateCallback)); 119 | } 120 | }); 121 | 122 | 123 | // ### Datasource Implementation 124 | // 125 | // ------------------- 126 | // Here we implement the actual datasource plugin. We pass in the settings and updateCallback. 127 | var myDatasourcePlugin = function(settings, updateCallback) 128 | { 129 | // Always a good idea... 130 | var self = this; 131 | 132 | // Good idea to create a variable to hold on to our settings, because they might change in the future. See below. 133 | var currentSettings = settings; 134 | 135 | /* This is some function where I'll get my data from somewhere */ 136 | function getData() 137 | { 138 | var newData = { hello : "world! it's " + new Date().toLocaleTimeString() }; // Just putting some sample data in for fun. 139 | 140 | /* Get my data from somewhere and populate newData with it... Probably a JSON API or something. */ 141 | /* ... */ 142 | 143 | // I'm calling updateCallback to tell it I've got new data for it to munch on. 144 | updateCallback(newData); 145 | } 146 | 147 | // You'll probably want to implement some sort of timer to refresh your data every so often. 148 | var refreshTimer; 149 | 150 | function createRefreshTimer(interval) 151 | { 152 | if(refreshTimer) 153 | { 154 | clearInterval(refreshTimer); 155 | } 156 | 157 | refreshTimer = setInterval(function() 158 | { 159 | // Here we call our getData function to update freeboard with new data. 160 | getData(); 161 | }, interval); 162 | } 163 | 164 | // **onSettingsChanged(newSettings)** (required) : A public function we must implement that will be called when a user makes a change to the settings. 165 | self.onSettingsChanged = function(newSettings) 166 | { 167 | // Here we update our current settings with the variable that is passed in. 168 | currentSettings = newSettings; 169 | } 170 | 171 | // **updateNow()** (required) : A public function we must implement that will be called when the user wants to manually refresh the datasource 172 | self.updateNow = function() 173 | { 174 | // Most likely I'll just call getData() here. 175 | getData(); 176 | } 177 | 178 | // **onDispose()** (required) : A public function we must implement that will be called when this instance of this plugin is no longer needed. Do anything you need to cleanup after yourself here. 179 | self.onDispose = function() 180 | { 181 | // Probably a good idea to get rid of our timer. 182 | clearInterval(refreshTimer); 183 | refreshTimer = undefined; 184 | } 185 | 186 | // Here we call createRefreshTimer with our current settings, to kick things off, initially. Notice how we make use of one of the user defined settings that we setup earlier. 187 | createRefreshTimer(currentSettings.refresh_time); 188 | } 189 | 190 | 191 | // ## A Widget Plugin 192 | // 193 | // ------------------- 194 | // ### Widget Definition 195 | // 196 | // ------------------- 197 | // **freeboard.loadWidgetPlugin(definition)** tells freeboard that we are giving it a widget plugin. It expects an object with the following: 198 | freeboard.loadWidgetPlugin({ 199 | // Same stuff here as with datasource plugin. 200 | "type_name" : "my_widget_plugin", 201 | "display_name": "Widget Plugin Example", 202 | "description" : "Some sort of description with optional html!", 203 | // **external_scripts** : Any external scripts that should be loaded before the plugin instance is created. 204 | "external_scripts": [ 205 | "http://mydomain.com/myscript1.js", "http://mydomain.com/myscript2.js" 206 | ], 207 | // **fill_size** : If this is set to true, the widget will fill be allowed to fill the entire space given it, otherwise it will contain an automatic padding of around 10 pixels around it. 208 | "fill_size" : false, 209 | "settings" : [ 210 | { 211 | "name" : "the_text", 212 | "display_name": "Some Text", 213 | // We'll use a calculated setting because we want what's displayed in this widget to be dynamic based on something changing (like a datasource). 214 | "type" : "calculated" 215 | }, 216 | { 217 | "name" : "size", 218 | "display_name": "Size", 219 | "type" : "option", 220 | "options" : [ 221 | { 222 | "name" : "Regular", 223 | "value": "regular" 224 | }, 225 | { 226 | "name" : "Big", 227 | "value": "big" 228 | } 229 | ] 230 | } 231 | ], 232 | // Same as with datasource plugin, but there is no updateCallback parameter in this case. 233 | newInstance : function(settings, newInstanceCallback) 234 | { 235 | newInstanceCallback(new myWidgetPlugin(settings)); 236 | } 237 | }); 238 | 239 | // ### Widget Implementation 240 | // 241 | // ------------------- 242 | // Here we implement the actual widget plugin. We pass in the settings; 243 | var myWidgetPlugin = function(settings) 244 | { 245 | var self = this; 246 | var currentSettings = settings; 247 | 248 | // Here we create an element to hold the text we're going to display. We're going to set the value displayed in it below. 249 | var myTextElement = $(""); 250 | 251 | // **render(containerElement)** (required) : A public function we must implement that will be called when freeboard wants us to render the contents of our widget. The container element is the DIV that will surround the widget. 252 | self.render = function(containerElement) 253 | { 254 | // Here we append our text element to the widget container element. 255 | $(containerElement).append(myTextElement); 256 | } 257 | 258 | // **getHeight()** (required) : A public function we must implement that will be called when freeboard wants to know how big we expect to be when we render, and returns a height. This function will be called any time a user updates their settings (including the first time they create the widget). 259 | // 260 | // Note here that the height is not in pixels, but in blocks. A block in freeboard is currently defined as a rectangle that is fixed at 300 pixels wide and around 45 pixels multiplied by the value you return here. 261 | // 262 | // Blocks of different sizes may be supported in the future. 263 | self.getHeight = function() 264 | { 265 | if(currentSettings.size == "big") 266 | { 267 | return 2; 268 | } 269 | else 270 | { 271 | return 1; 272 | } 273 | } 274 | 275 | // **onSettingsChanged(newSettings)** (required) : A public function we must implement that will be called when a user makes a change to the settings. 276 | self.onSettingsChanged = function(newSettings) 277 | { 278 | // Normally we'd update our text element with the value we defined in the user settings above (the_text), but there is a special case for settings that are of type **"calculated"** -- see below. 279 | currentSettings = newSettings; 280 | } 281 | 282 | // **onCalculatedValueChanged(settingName, newValue)** (required) : A public function we must implement that will be called when a calculated value changes. Since calculated values can change at any time (like when a datasource is updated) we handle them in a special callback function here. 283 | self.onCalculatedValueChanged = function(settingName, newValue) 284 | { 285 | // Remember we defined "the_text" up above in our settings. 286 | if(settingName == "the_text") 287 | { 288 | // Here we do the actual update of the value that's displayed in on the screen. 289 | $(myTextElement).html(newValue); 290 | } 291 | } 292 | 293 | // **onDispose()** (required) : Same as with datasource plugins. 294 | self.onDispose = function() 295 | { 296 | } 297 | } 298 | }()); -------------------------------------------------------------------------------- /_site/examples/rl78.json: -------------------------------------------------------------------------------- 1 | { 2 | "allow_edit" : true, 3 | "header_image" : "https://raw.github.com/Freeboard/branding/master/renesas/renesas_logo.png", 4 | "panes": [ 5 | { 6 | "title" : "Tilt", 7 | "width" : 1, 8 | "row" : { "3": 1 }, 9 | "col" : { "3": 1 }, 10 | "widgets": [ 11 | { 12 | "type" : "pointer", 13 | "settings": { 14 | "direction" : "datasources.RL78.Acceleration_X * -90", 15 | "value_text": "(datasources.RL78.Acceleration_X * -90).toFixed(0)", 16 | "units" : "degrees" 17 | } 18 | }, 19 | { 20 | "type" : "text_widget", 21 | "settings": { 22 | "title" : "Y", 23 | "size" : "regular", 24 | "value" : "(datasources.RL78.Acceleration_Y * 90).toFixed(0)", 25 | "sparkline": true, 26 | "animate" : true, 27 | "units" : "°" 28 | } 29 | }, 30 | { 31 | "type" : "text_widget", 32 | "settings": { 33 | "title" : "Z", 34 | "size" : "regular", 35 | "value" : "(datasources.RL78.Acceleration_Z * -90).toFixed(0)", 36 | "sparkline": true, 37 | "animate" : true, 38 | "units" : "°" 39 | } 40 | } 41 | ] 42 | }, 43 | { 44 | "title" : "Buttons", 45 | "width" : 1, 46 | "row" : { "3": 5 }, 47 | "col" : { "3": 3 }, 48 | "widgets": [ 49 | { 50 | "type" : "indicator", 51 | "settings": { 52 | "value" : "datasources.RL78.Button_1", 53 | "on_text" : "Button 1 is ON", 54 | "off_text": "Button 1 is OFF" 55 | } 56 | }, 57 | { 58 | "type" : "indicator", 59 | "settings": { 60 | "value" : "datasources.RL78.Button_2", 61 | "on_text" : "Button 2 is ON", 62 | "off_text": "Button 2 is OFF" 63 | } 64 | }, 65 | { 66 | "type" : "indicator", 67 | "settings": { 68 | "value" : "datasources.RL78.Button_3", 69 | "on_text" : "Button 3 is ON", 70 | "off_text": "Button 3 is OFF" 71 | } 72 | } 73 | ] 74 | }, 75 | { 76 | "title" : "Temperature", 77 | "width" : 1, 78 | "row" : { "3": 1 }, 79 | "col" : { "3": 2 }, 80 | "widgets": [ 81 | { 82 | "type" : "text_widget", 83 | "settings": { 84 | "title" : "Indoor", 85 | "size" : "big", 86 | "value" : "datasources.RL78.Temperature", 87 | "sparkline": false, 88 | "animate" : true, 89 | "units" : "°F" 90 | } 91 | }, 92 | { 93 | "type" : "text_widget", 94 | "settings": { 95 | "title" : "Outdoor", 96 | "size" : "big", 97 | "value" : "((datasources.Weather.main.temp - 273.15) * 1.8 + 32).toFixed(2)", 98 | "animate": true, 99 | "units" : "°F" 100 | } 101 | } 102 | ] 103 | }, 104 | { 105 | "title" : "Potentiometer", 106 | "width" : 1, 107 | "row" : { "3": 1 }, 108 | "col" : { "3": 3 }, 109 | "widgets": [ 110 | { 111 | "type" : "gauge", 112 | "settings": { 113 | "title" : "", 114 | "value" : "datasources.RL78.Potentiometer", 115 | "units" : "Ω", 116 | "min_value": 0, 117 | "max_value": "1000" 118 | } 119 | } 120 | ] 121 | }, 122 | { 123 | "title" : "Miscellaneous", 124 | "width" : 1, 125 | "row" : { "3": 6 }, 126 | "col" : { "3": 2 }, 127 | "widgets": [ 128 | { 129 | "type" : "text_widget", 130 | "settings": { 131 | "title" : "Light", 132 | "size" : "regular", 133 | "value" : "datasources.RL78.Light", 134 | "sparkline": true, 135 | "animate" : true 136 | } 137 | }, 138 | { 139 | "type" : "text_widget", 140 | "settings": { 141 | "title" : "Sound", 142 | "size" : "regular", 143 | "value" : "datasources.RL78.Sound", 144 | "sparkline": true, 145 | "animate" : true 146 | } 147 | } 148 | ] 149 | } 150 | ], 151 | "datasources": [ 152 | { 153 | "name" : "RL78", 154 | "type" : "rl78", 155 | "settings": { 156 | "device_resource_id": "14f762c59815e24973165668aff677659b973d62" 157 | } 158 | }, 159 | { 160 | "name" : "Weather", 161 | "type" : "JSON", 162 | "settings": { 163 | "url" : "http://api.openweathermap.org/data/2.5/weather?q=Seattle,WA", 164 | "refresh" : 5, 165 | "is_jsonp": true 166 | } 167 | } 168 | ]} -------------------------------------------------------------------------------- /_site/examples/weather.json: -------------------------------------------------------------------------------- 1 | { 2 | "header_image" : "", 3 | "allow_edit" : true, 4 | "panes": [ 5 | { 6 | "title" : "Wind", 7 | "width" : 1, 8 | "row" : { "3" :1 }, 9 | "col" : { "3": 2 }, 10 | "widgets": [ 11 | { 12 | "type" : "pointer", 13 | "settings": { 14 | "direction" : "datasources.Weather.wind_direction", 15 | "value_text": "var dir = datasources.Weather.wind_direction;\n\nif(dir <= 22.5)\nreturn \"N\";\nelse if(dir <= 67.5)\nreturn \"NE\";\nelse if(dir <= 112.5)\nreturn \"E\";\nelse if(dir <= 157.5)\nreturn \"SE\";\nelse if(dir <= 202.5)\nreturn \"S\";\nelse if(dir <= 247.5)\nreturn \"SW\";\nelse if(dir <= 292.5)\nreturn \"W\";\nelse if(dir <= 337.5)\nreturn \"NW\";\nelse if(dir <= 360)\nreturn \"N\";" 16 | } 17 | }, 18 | { 19 | "type" : "text_widget", 20 | "settings": { 21 | "size" : "regular", 22 | "value" : "datasources.Weather.wind_speed", 23 | "sparkline": true, 24 | "animate" : true, 25 | "units" : "MPH" 26 | } 27 | } 28 | ] 29 | }, 30 | { 31 | "width" : 1, 32 | "row" : { "3": 5}, 33 | "col" : { "3": 3}, 34 | "widgets": [ 35 | { 36 | "type" : "text_widget", 37 | "settings": { 38 | "title" : "Sunrise", 39 | "size" : "regular", 40 | "value" : "datasources.Weather.sunrise", 41 | "animate": true 42 | } 43 | }, 44 | { 45 | "type" : "text_widget", 46 | "settings": { 47 | "title" : "Sunset", 48 | "size" : "regular", 49 | "value" : "datasources.Weather.sunset", 50 | "animate": true 51 | } 52 | } 53 | ] 54 | }, 55 | { 56 | "title" : "Temperature", 57 | "width" : 1, 58 | "row" : { "3": 4 }, 59 | "col" : { "3": 1 }, 60 | "widgets": [ 61 | { 62 | "type" : "text_widget", 63 | "settings": { 64 | "title" : "Current", 65 | "size" : "big", 66 | "value" : "datasources.Weather.current_temp", 67 | "animate": true, 68 | "units" : "°F" 69 | } 70 | }, 71 | { 72 | "type" : "text_widget", 73 | "settings": { 74 | "title" : "High", 75 | "size" : "regular", 76 | "value" : "datasources.Weather.high_temp", 77 | "animate": true, 78 | "units" : "°F" 79 | } 80 | }, 81 | { 82 | "type" : "text_widget", 83 | "settings": { 84 | "title" : "Low", 85 | "size" : "regular", 86 | "value" : "datasources.Weather.low_temp", 87 | "animate": true, 88 | "units" : "°F" 89 | } 90 | } 91 | ] 92 | }, 93 | { 94 | "title" : "Info", 95 | "width" : 1, 96 | "row" : { "3": 1 }, 97 | "col" : { "3": 1 }, 98 | "widgets": [ 99 | { 100 | "type" : "text_widget", 101 | "settings": { 102 | "title" : "City", 103 | "size" : "regular", 104 | "value" : "datasources.Weather.place_name", 105 | "animate": true 106 | } 107 | }, 108 | { 109 | "type" : "text_widget", 110 | "settings": { 111 | "title" : "Conditions", 112 | "size" : "regular", 113 | "value" : "datasources.Weather.conditions", 114 | "animate": true 115 | } 116 | } 117 | ] 118 | }, 119 | { 120 | "title" : "Humidity", 121 | "width" : 1, 122 | "row" : { "3": 1 }, 123 | "col" : { "3": 3 }, 124 | "widgets": [ 125 | { 126 | "type" : "gauge", 127 | "settings": { 128 | "value" : "datasources.Weather.humidity", 129 | "units" : "%", 130 | "min_value": 0, 131 | "max_value": 100 132 | } 133 | } 134 | ] 135 | }, 136 | { 137 | "title" : "Pressure", 138 | "width" : 1, 139 | "row" : { "3": 7 }, 140 | "col" : { "3": 2 }, 141 | "widgets": [ 142 | { 143 | "type" : "text_widget", 144 | "settings": { 145 | "size" : "regular", 146 | "value" : "datasources.Weather.pressure", 147 | "sparkline": true, 148 | "animate" : true, 149 | "units" : "mb" 150 | } 151 | } 152 | ] 153 | } 154 | ], "datasources": [ 155 | { 156 | "name" : "Weather", 157 | "type" : "openweathermap", 158 | "settings": { 159 | "location": "New York, NY", 160 | "units" : "imperial", 161 | "refresh" : 5 162 | } 163 | } 164 | ]} -------------------------------------------------------------------------------- /_site/img/dropdown-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elasticfence/freeboard-elasticsearch/ba020fa0a3d8b71690713e1fcdf2366ce3628419/_site/img/dropdown-arrow.png -------------------------------------------------------------------------------- /_site/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elasticfence/freeboard-elasticsearch/ba020fa0a3d8b71690713e1fcdf2366ce3628419/_site/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /_site/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elasticfence/freeboard-elasticsearch/ba020fa0a3d8b71690713e1fcdf2366ce3628419/_site/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /_site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | freeboard 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 98 | 99 | 100 |
101 | 102 |
103 |
    104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |

freeboard

112 |
113 |
    114 |
  • 115 |
  • 116 | 117 | 118 | 119 |
  • 120 |
  • 121 |
122 |
123 |
124 |
125 |

DATASOURCES

126 | 127 |
128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 141 | 142 | 149 | 150 | 151 |
NameLast Updated 
139 | 140 | 143 |
    144 |
  • 145 |
  • 146 |
  • 147 |
148 |
152 |
153 | ADD 154 |
155 |
156 |
157 |
158 |
    159 |
  • 160 |
  • 161 |
162 |
    163 |
  • 164 |
  • 165 |
166 |
167 |
168 | 169 |
170 |
171 | 172 |
173 |
    174 |
175 |
176 | 177 | 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /_site/index.min.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | freeboard 6 | 7 | 8 | 9 | 10 | 11 | 12 | 38 | 39 | 40 |
41 | 42 |
43 |
    44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |

freeboard

52 |
53 |
    54 |
  • 55 |
  • 56 | 57 | 58 | 59 |
  • 60 |
  • 61 |
62 |
63 |
64 |
65 |

DATASOURCES

66 | 67 |
68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 81 | 82 | 89 | 90 | 91 |
NameLast Updated 
79 | 80 | 83 |
    84 |
  • 85 |
  • 86 |
  • 87 |
88 |
92 |
93 | ADD 94 |
95 |
96 |
97 |
98 |
    99 |
  • 100 |
  • 101 |
102 |
    103 |
  • 104 |
  • 105 |
106 |
107 |
108 |
109 |
110 | 111 |
112 |
    113 |
114 |
115 | 116 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /_site/js/freeboard.creator.js: -------------------------------------------------------------------------------- 1 | var freeboardCreator = function(config) 2 | { 3 | this.boardName = "New Freeboard"; 4 | this.boardConfig = [config] 5 | } 6 | 7 | 8 | freeboardCreator.prototype.createPane = function(row, col, title) 9 | { 10 | var pane = { 11 | "title": title, 12 | "width": 1, 13 | "row": { 14 | "3": row || 1 15 | }, 16 | "col": { 17 | "3": col || 1 18 | }, 19 | "col_width": 1, 20 | "widgets": [] 21 | }; 22 | 23 | this.boardConfig.panes.push(pane); 24 | 25 | return pane; 26 | } 27 | 28 | freeboardCreator.prototype.createInNewWindow = function() 29 | { 30 | var fbForm = $("#fb_create_form"); 31 | var fbName = $("#fb_board_name"); 32 | var fbConfig = $("#fb_board_config"); 33 | 34 | if(fbForm.length === 0) 35 | { 36 | fbForm = $('
'); 37 | $("body").append(fbForm); 38 | } 39 | 40 | if(fbName.length === 0) 41 | { 42 | fbName = $(''); 43 | fbForm.append(fbName); 44 | } 45 | 46 | if(fbConfig.length === 0) 47 | { 48 | fbConfig = $(''); 49 | fbForm.append(fbConfig); 50 | } 51 | 52 | fbName.attr("value", this.boardName); 53 | fbConfig.attr("value", JSON.stringify(this.boardConfig)); 54 | 55 | fbForm.submit(); 56 | } 57 | 58 | freeboardCreator.prototype.getCreateLink = function() 59 | { 60 | return "https://freeboard.io/board/new" + "?board_name=" + encodeURIComponent(this.boardName) + "&board_config=" + encodeURIComponent(JSON.stringify(this.boardConfig)); 61 | } 62 | 63 | freeboardCreator.prototype.createAndGetLink = function(callback) 64 | { 65 | $.post( "https://freeboard.io/board/new", { board_name: this.boardName, board_config: JSON.stringify(this.boardConfig), no_redirect: true }) 66 | .done(function( data ) { 67 | 68 | if(data && data.board_url) 69 | { 70 | callback(null, data.board_url); 71 | } 72 | else 73 | { 74 | callback("error"); 75 | } 76 | }).fail(function() { 77 | callback("error"); 78 | }); 79 | } 80 | -------------------------------------------------------------------------------- /_site/lib/css/thirdparty/codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 300px; 7 | } 8 | .CodeMirror-scroll { 9 | /* Set scrolling behaviour here */ 10 | overflow: auto; 11 | } 12 | 13 | /* PADDING */ 14 | 15 | .CodeMirror-lines { 16 | padding: 4px 0; /* Vertical padding around content */ 17 | } 18 | .CodeMirror pre { 19 | padding: 0 4px; /* Horizontal padding of content */ 20 | } 21 | 22 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 23 | background-color: white; /* The little square between H and V scrollbars */ 24 | } 25 | 26 | /* GUTTER */ 27 | 28 | .CodeMirror-gutters { 29 | border-right: 1px solid #ddd; 30 | background-color: #f7f7f7; 31 | white-space: nowrap; 32 | } 33 | .CodeMirror-linenumbers {} 34 | .CodeMirror-linenumber { 35 | padding: 0 3px 0 5px; 36 | min-width: 20px; 37 | text-align: right; 38 | color: #999; 39 | -moz-box-sizing: content-box; 40 | box-sizing: content-box; 41 | } 42 | 43 | /* CURSOR */ 44 | 45 | .CodeMirror div.CodeMirror-cursor { 46 | border-left: 1px solid black; 47 | } 48 | /* Shown when moving in bi-directional text */ 49 | .CodeMirror div.CodeMirror-secondarycursor { 50 | border-left: 1px solid silver; 51 | } 52 | .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { 53 | width: auto; 54 | border: 0; 55 | background: #7e7; 56 | } 57 | /* Can style cursor different in overwrite (non-insert) mode */ 58 | div.CodeMirror-overwrite div.CodeMirror-cursor {} 59 | 60 | .cm-tab { display: inline-block; } 61 | 62 | .CodeMirror-ruler { 63 | border-left: 1px solid #ccc; 64 | position: absolute; 65 | } 66 | 67 | /* DEFAULT THEME */ 68 | 69 | .cm-s-default .cm-keyword {color: #708;} 70 | .cm-s-default .cm-atom {color: #219;} 71 | .cm-s-default .cm-number {color: #164;} 72 | .cm-s-default .cm-def {color: #00f;} 73 | .cm-s-default .cm-variable, 74 | .cm-s-default .cm-punctuation, 75 | .cm-s-default .cm-property, 76 | .cm-s-default .cm-operator {} 77 | .cm-s-default .cm-variable-2 {color: #05a;} 78 | .cm-s-default .cm-variable-3 {color: #085;} 79 | .cm-s-default .cm-comment {color: #a50;} 80 | .cm-s-default .cm-string {color: #a11;} 81 | .cm-s-default .cm-string-2 {color: #f50;} 82 | .cm-s-default .cm-meta {color: #555;} 83 | .cm-s-default .cm-qualifier {color: #555;} 84 | .cm-s-default .cm-builtin {color: #30a;} 85 | .cm-s-default .cm-bracket {color: #997;} 86 | .cm-s-default .cm-tag {color: #170;} 87 | .cm-s-default .cm-attribute {color: #00c;} 88 | .cm-s-default .cm-header {color: blue;} 89 | .cm-s-default .cm-quote {color: #090;} 90 | .cm-s-default .cm-hr {color: #999;} 91 | .cm-s-default .cm-link {color: #00c;} 92 | 93 | .cm-negative {color: #d44;} 94 | .cm-positive {color: #292;} 95 | .cm-header, .cm-strong {font-weight: bold;} 96 | .cm-em {font-style: italic;} 97 | .cm-link {text-decoration: underline;} 98 | 99 | .cm-s-default .cm-error {color: #f00;} 100 | .cm-invalidchar {color: #f00;} 101 | 102 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 103 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 104 | .CodeMirror-activeline-background {background: #e8f2ff;} 105 | 106 | /* STOP */ 107 | 108 | /* The rest of this file contains styles related to the mechanics of 109 | the editor. You probably shouldn't touch them. */ 110 | 111 | .CodeMirror { 112 | line-height: 1; 113 | position: relative; 114 | overflow: hidden; 115 | background: white; 116 | color: black; 117 | } 118 | 119 | .CodeMirror-scroll { 120 | /* 30px is the magic margin used to hide the element's real scrollbars */ 121 | /* See overflow: hidden in .CodeMirror */ 122 | margin-bottom: -30px; margin-right: -30px; 123 | padding-bottom: 30px; 124 | height: 100%; 125 | outline: none; /* Prevent dragging from highlighting the element */ 126 | position: relative; 127 | -moz-box-sizing: content-box; 128 | box-sizing: content-box; 129 | } 130 | .CodeMirror-sizer { 131 | position: relative; 132 | border-right: 30px solid transparent; 133 | -moz-box-sizing: content-box; 134 | box-sizing: content-box; 135 | } 136 | 137 | /* The fake, visible scrollbars. Used to force redraw during scrolling 138 | before actuall scrolling happens, thus preventing shaking and 139 | flickering artifacts. */ 140 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 141 | position: absolute; 142 | z-index: 6; 143 | display: none; 144 | } 145 | .CodeMirror-vscrollbar { 146 | right: 0; top: 0; 147 | overflow-x: hidden; 148 | overflow-y: scroll; 149 | } 150 | .CodeMirror-hscrollbar { 151 | bottom: 0; left: 0; 152 | overflow-y: hidden; 153 | overflow-x: scroll; 154 | } 155 | .CodeMirror-scrollbar-filler { 156 | right: 0; bottom: 0; 157 | } 158 | .CodeMirror-gutter-filler { 159 | left: 0; bottom: 0; 160 | } 161 | 162 | .CodeMirror-gutters { 163 | position: absolute; left: 0; top: 0; 164 | padding-bottom: 30px; 165 | z-index: 3; 166 | } 167 | .CodeMirror-gutter { 168 | white-space: normal; 169 | height: 100%; 170 | -moz-box-sizing: content-box; 171 | box-sizing: content-box; 172 | padding-bottom: 30px; 173 | margin-bottom: -32px; 174 | display: inline-block; 175 | /* Hack to make IE7 behave */ 176 | *zoom:1; 177 | *display:inline; 178 | } 179 | .CodeMirror-gutter-elt { 180 | position: absolute; 181 | cursor: default; 182 | z-index: 4; 183 | } 184 | 185 | .CodeMirror-lines { 186 | cursor: text; 187 | } 188 | .CodeMirror pre { 189 | /* Reset some styles that the rest of the page might have set */ 190 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 191 | border-width: 0; 192 | background: transparent; 193 | font-family: inherit; 194 | font-size: inherit; 195 | margin: 0; 196 | white-space: pre; 197 | word-wrap: normal; 198 | line-height: inherit; 199 | color: inherit; 200 | z-index: 2; 201 | position: relative; 202 | overflow: visible; 203 | } 204 | .CodeMirror-wrap pre { 205 | word-wrap: break-word; 206 | white-space: pre-wrap; 207 | word-break: normal; 208 | } 209 | 210 | .CodeMirror-linebackground { 211 | position: absolute; 212 | left: 0; right: 0; top: 0; bottom: 0; 213 | z-index: 0; 214 | } 215 | 216 | .CodeMirror-linewidget { 217 | position: relative; 218 | z-index: 2; 219 | overflow: auto; 220 | } 221 | 222 | .CodeMirror-widget {} 223 | 224 | .CodeMirror-wrap .CodeMirror-scroll { 225 | overflow-x: hidden; 226 | } 227 | 228 | .CodeMirror-measure { 229 | position: absolute; 230 | width: 100%; 231 | height: 0; 232 | overflow: hidden; 233 | visibility: hidden; 234 | } 235 | .CodeMirror-measure pre { position: static; } 236 | 237 | .CodeMirror div.CodeMirror-cursor { 238 | position: absolute; 239 | border-right: none; 240 | width: 0; 241 | } 242 | 243 | div.CodeMirror-cursors { 244 | visibility: hidden; 245 | position: relative; 246 | z-index: 1; 247 | } 248 | .CodeMirror-focused div.CodeMirror-cursors { 249 | visibility: visible; 250 | } 251 | 252 | .CodeMirror-selected { background: #d9d9d9; } 253 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 254 | .CodeMirror-crosshair { cursor: crosshair; } 255 | 256 | .cm-searching { 257 | background: #ffa; 258 | background: rgba(255, 255, 0, .4); 259 | } 260 | 261 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 262 | .CodeMirror span { *vertical-align: text-bottom; } 263 | 264 | /* Used to force a border model for a node */ 265 | .cm-force-border { padding-right: .1px; } 266 | 267 | @media print { 268 | /* Hide the cursor when printing */ 269 | .CodeMirror div.CodeMirror-cursors { 270 | visibility: hidden; 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /_site/lib/css/thirdparty/jquery.gridster.min.css: -------------------------------------------------------------------------------- 1 | /*! gridster.js - v0.1.0 - 2013-06-14 - * http://gridster.net/ - Copyright (c) 2013 ducksboard; Licensed MIT */ 2 | .gridster{position:relative}.gridster>*{margin:0 auto;-webkit-transition:height .4s;-moz-transition:height .4s;-o-transition:height .4s;-ms-transition:height .4s;transition:height .4s}.gridster .gs_w{z-index:2;position:absolute}.ready .gs_w:not(.preview-holder){-webkit-transition:opacity .3s,left .3s,top .3s;-moz-transition:opacity .3s,left .3s,top .3s;-o-transition:opacity .3s,left .3s,top .3s;transition:opacity .3s,left .3s,top .3s}.ready .gs_w:not(.preview-holder){-webkit-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;-moz-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;-o-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;transition:opacity .3s,left .3s,top .3s,width .3s,height .3s}.gridster .preview-holder{z-index:1;position:absolute;background-color:#fff;border-color:#fff;opacity:.3}.gridster .player-revert{z-index:10!important;-webkit-transition:left .3s,top .3s!important;-moz-transition:left .3s,top .3s!important;-o-transition:left .3s,top .3s!important;transition:left .3s,top .3s!important}.gridster .dragging{z-index:10!important;-webkit-transition:all 0s!important;-moz-transition:all 0s!important;-o-transition:all 0s!important;transition:all 0s!important} -------------------------------------------------------------------------------- /_site/lib/js/freeboard/DatasourceModel.js: -------------------------------------------------------------------------------- 1 | DatasourceModel = function(theFreeboardModel, datasourcePlugins) { 2 | var self = this; 3 | 4 | function disposeDatasourceInstance() 5 | { 6 | if(!_.isUndefined(self.datasourceInstance)) 7 | { 8 | if(_.isFunction(self.datasourceInstance.onDispose)) 9 | { 10 | self.datasourceInstance.onDispose(); 11 | } 12 | 13 | self.datasourceInstance = undefined; 14 | } 15 | } 16 | 17 | this.name = ko.observable(); 18 | this.latestData = ko.observable(); 19 | this.settings = ko.observable({}); 20 | this.settings.subscribe(function(newValue) 21 | { 22 | if(!_.isUndefined(self.datasourceInstance) && _.isFunction(self.datasourceInstance.onSettingsChanged)) 23 | { 24 | self.datasourceInstance.onSettingsChanged(newValue); 25 | } 26 | }); 27 | 28 | this.updateCallback = function(newData) 29 | { 30 | theFreeboardModel.processDatasourceUpdate(self, newData); 31 | 32 | self.latestData(newData); 33 | 34 | var now = new Date(); 35 | self.last_updated(now.toLocaleTimeString()); 36 | } 37 | 38 | this.type = ko.observable(); 39 | this.type.subscribe(function(newValue) 40 | { 41 | disposeDatasourceInstance(); 42 | 43 | if((newValue in datasourcePlugins) && _.isFunction(datasourcePlugins[newValue].newInstance)) 44 | { 45 | var datasourceType = datasourcePlugins[newValue]; 46 | 47 | function finishLoad() 48 | { 49 | datasourceType.newInstance(self.settings(), function(datasourceInstance) 50 | { 51 | 52 | self.datasourceInstance = datasourceInstance; 53 | datasourceInstance.updateNow(); 54 | 55 | }, self.updateCallback); 56 | } 57 | 58 | // Do we need to load any external scripts? 59 | if(datasourceType.external_scripts) 60 | { 61 | head.js(datasourceType.external_scripts.slice(0), finishLoad); // Need to clone the array because head.js adds some weird functions to it 62 | } 63 | else 64 | { 65 | finishLoad(); 66 | } 67 | } 68 | }); 69 | 70 | this.last_updated = ko.observable("never"); 71 | this.last_error = ko.observable(); 72 | 73 | this.serialize = function() 74 | { 75 | return { 76 | name : self.name(), 77 | type : self.type(), 78 | settings: self.settings() 79 | }; 80 | } 81 | 82 | this.deserialize = function(object) 83 | { 84 | self.settings(object.settings); 85 | self.name(object.name); 86 | self.type(object.type); 87 | } 88 | 89 | this.getDataRepresentation = function(dataPath) 90 | { 91 | var valueFunction = new Function("data", "return " + dataPath + ";"); 92 | return valueFunction.call(undefined, self.latestData()); 93 | } 94 | 95 | this.updateNow = function() 96 | { 97 | if(!_.isUndefined(self.datasourceInstance) && _.isFunction(self.datasourceInstance.updateNow)) 98 | { 99 | self.datasourceInstance.updateNow(); 100 | } 101 | } 102 | 103 | this.dispose = function() 104 | { 105 | disposeDatasourceInstance(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /_site/lib/js/freeboard/DeveloperConsole.js: -------------------------------------------------------------------------------- 1 | DeveloperConsole = function(theFreeboardModel) 2 | { 3 | function showDeveloperConsole() 4 | { 5 | var pluginScriptsInputs = []; 6 | var container = $('
'); 7 | var addScript = $('
ADD
'); 8 | var table = $('
'); 9 | 10 | table.append($('Plugin Script URL')); 11 | 12 | var tableBody = $(""); 13 | 14 | table.append(tableBody); 15 | 16 | container.append($("

Here you can add references to other scripts to load datasource or widget plugins.

")) 17 | .append(table) 18 | .append(addScript) 19 | .append('

To learn how to build plugins for freeboard, please visit http://freeboard.github.io/freeboard/docs/plugin_example.html

'); 20 | 21 | function refreshScript(scriptURL) 22 | { 23 | $('script[src="' + scriptURL + '"]').remove(); 24 | } 25 | 26 | function addNewScriptRow(scriptURL) 27 | { 28 | var tableRow = $(''); 29 | var tableOperations = $(''); 30 | var scriptInput = $(''); 31 | var deleteOperation = $('
  • ').click(function(e){ 32 | pluginScriptsInputs = _.without(pluginScriptsInputs, scriptInput); 33 | tableRow.remove(); 34 | }); 35 | 36 | pluginScriptsInputs.push(scriptInput); 37 | 38 | if(scriptURL) 39 | { 40 | scriptInput.val(scriptURL); 41 | } 42 | 43 | tableOperations.append(deleteOperation); 44 | tableBody 45 | .append(tableRow 46 | .append($('').append(scriptInput)) 47 | .append($('').append(tableOperations))); 48 | } 49 | 50 | _.each(theFreeboardModel.plugins(), function(pluginSource){ 51 | 52 | addNewScriptRow(pluginSource); 53 | 54 | }); 55 | 56 | addScript.click(function(e) 57 | { 58 | addNewScriptRow(); 59 | }); 60 | 61 | new DialogBox(container, "Developer Console", "OK", null, function(){ 62 | 63 | // Unload our previous scripts 64 | _.each(theFreeboardModel.plugins(), function(pluginSource){ 65 | 66 | $('script[src^="' + pluginSource + '"]').remove(); 67 | 68 | }); 69 | 70 | theFreeboardModel.plugins.removeAll(); 71 | 72 | _.each(pluginScriptsInputs, function(scriptInput){ 73 | 74 | var scriptURL = scriptInput.val(); 75 | 76 | if(scriptURL && scriptURL.length > 0) 77 | { 78 | theFreeboardModel.addPluginSource(scriptURL); 79 | 80 | // Load the script with a cache buster 81 | head.js(scriptURL + "?" + Date.now()); 82 | } 83 | }); 84 | 85 | }); 86 | } 87 | 88 | // Public API 89 | return { 90 | showDeveloperConsole : function() 91 | { 92 | showDeveloperConsole(); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /_site/lib/js/freeboard/DialogBox.js: -------------------------------------------------------------------------------- 1 | function DialogBox(contentElement, title, okTitle, cancelTitle, okCallback) 2 | { 3 | var modal_width = 900; 4 | 5 | // Initialize our modal overlay 6 | var overlay = $(''); 7 | 8 | var modalDialog = $(''); 9 | 10 | function closeModal() 11 | { 12 | overlay.fadeOut(200, function() 13 | { 14 | $(this).remove(); 15 | }); 16 | } 17 | 18 | // Create our header 19 | modalDialog.append('

    ' + title + "

    "); 20 | 21 | $('
    ').appendTo(modalDialog).append(contentElement); 22 | 23 | // Create our footer 24 | var footer = $('
    ').appendTo(modalDialog); 25 | 26 | if(okTitle) 27 | { 28 | $('' + okTitle + '').appendTo(footer).click(function() 29 | { 30 | var hold = false; 31 | 32 | if(_.isFunction(okCallback)) 33 | { 34 | hold = okCallback(); 35 | } 36 | 37 | if(!hold) 38 | { 39 | closeModal(); 40 | } 41 | }); 42 | } 43 | 44 | if(cancelTitle) 45 | { 46 | $('' + cancelTitle + '').appendTo(footer).click(function() 47 | { 48 | closeModal(); 49 | }); 50 | } 51 | 52 | overlay.append(modalDialog); 53 | $("body").append(overlay); 54 | overlay.fadeIn(200); 55 | } 56 | -------------------------------------------------------------------------------- /_site/lib/js/freeboard/FreeboardModel.js: -------------------------------------------------------------------------------- 1 | function FreeboardModel(datasourcePlugins, widgetPlugins, freeboardUI) 2 | { 3 | var self = this; 4 | 5 | var SERIALIZATION_VERSION = 1; 6 | 7 | this.version = 0; 8 | this.isEditing = ko.observable(false); 9 | this.allow_edit = ko.observable(false); 10 | this.allow_edit.subscribe(function(newValue) 11 | { 12 | if(newValue) 13 | { 14 | $("#main-header").show(); 15 | } 16 | else 17 | { 18 | $("#main-header").hide(); 19 | } 20 | }); 21 | 22 | this.header_image = ko.observable(); 23 | this.plugins = ko.observableArray(); 24 | this.datasources = ko.observableArray(); 25 | this.panes = ko.observableArray(); 26 | this.datasourceData = {}; 27 | this.processDatasourceUpdate = function(datasourceModel, newData) 28 | { 29 | var datasourceName = datasourceModel.name(); 30 | 31 | self.datasourceData[datasourceName] = newData; 32 | 33 | _.each(self.panes(), function(pane) 34 | { 35 | _.each(pane.widgets(), function(widget) 36 | { 37 | widget.processDatasourceUpdate(datasourceName); 38 | }); 39 | }); 40 | } 41 | 42 | this._datasourceTypes = ko.observable(); 43 | this.datasourceTypes = ko.computed({ 44 | read: function() 45 | { 46 | self._datasourceTypes(); 47 | 48 | var returnTypes = []; 49 | 50 | _.each(datasourcePlugins, function(datasourcePluginType) 51 | { 52 | var typeName = datasourcePluginType.type_name; 53 | var displayName = typeName; 54 | 55 | if(!_.isUndefined(datasourcePluginType.display_name)) 56 | { 57 | displayName = datasourcePluginType.display_name; 58 | } 59 | 60 | returnTypes.push({ 61 | name : typeName, 62 | display_name: displayName 63 | }); 64 | }); 65 | 66 | return returnTypes; 67 | } 68 | }); 69 | 70 | this._widgetTypes = ko.observable(); 71 | this.widgetTypes = ko.computed({ 72 | read: function() 73 | { 74 | self._widgetTypes(); 75 | 76 | var returnTypes = []; 77 | 78 | _.each(widgetPlugins, function(widgetPluginType) 79 | { 80 | var typeName = widgetPluginType.type_name; 81 | var displayName = typeName; 82 | 83 | if(!_.isUndefined(widgetPluginType.display_name)) 84 | { 85 | displayName = widgetPluginType.display_name; 86 | } 87 | 88 | returnTypes.push({ 89 | name : typeName, 90 | display_name: displayName 91 | }); 92 | }); 93 | 94 | return returnTypes; 95 | } 96 | }); 97 | 98 | this.addPluginSource = function(pluginSource) 99 | { 100 | if(pluginSource && self.plugins.indexOf(pluginSource) == -1) 101 | { 102 | self.plugins.push(pluginSource); 103 | } 104 | } 105 | 106 | this.serialize = function() 107 | { 108 | var panes = []; 109 | 110 | _.each(self.panes(), function(pane) 111 | { 112 | panes.push(pane.serialize()); 113 | }); 114 | 115 | var datasources = []; 116 | 117 | _.each(self.datasources(), function(datasource) 118 | { 119 | datasources.push(datasource.serialize()); 120 | }); 121 | 122 | return { 123 | version : SERIALIZATION_VERSION, 124 | header_image: self.header_image(), 125 | allow_edit : self.allow_edit(), 126 | plugins : self.plugins(), 127 | panes : panes, 128 | datasources : datasources, 129 | columns : freeboardUI.getUserColumns() 130 | }; 131 | } 132 | 133 | this.deserialize = function(object, finishedCallback) 134 | { 135 | self.clearDashboard(); 136 | 137 | function finishLoad() 138 | { 139 | freeboardUI.setUserColumns(object.columns); 140 | 141 | if(!_.isUndefined(object.allow_edit)) 142 | { 143 | self.allow_edit(object.allow_edit); 144 | } 145 | else 146 | { 147 | self.allow_edit(true); 148 | } 149 | self.version = object.version || 0; 150 | self.header_image(object.header_image); 151 | 152 | _.each(object.datasources, function(datasourceConfig) 153 | { 154 | var datasource = new DatasourceModel(self, datasourcePlugins); 155 | datasource.deserialize(datasourceConfig); 156 | self.addDatasource(datasource); 157 | }); 158 | 159 | var sortedPanes = _.sortBy(object.panes, function(pane){ 160 | return freeboardUI.getPositionForScreenSize(pane).row; 161 | }); 162 | 163 | _.each(sortedPanes, function(paneConfig) 164 | { 165 | var pane = new PaneModel(self, widgetPlugins); 166 | pane.deserialize(paneConfig); 167 | self.panes.push(pane); 168 | }); 169 | 170 | if(self.allow_edit() && self.panes().length == 0) 171 | { 172 | self.setEditing(true); 173 | } 174 | 175 | if(_.isFunction(finishedCallback)) 176 | { 177 | finishedCallback(); 178 | } 179 | 180 | freeboardUI.processResize(true); 181 | } 182 | 183 | // This could have been self.plugins(object.plugins), but for some weird reason head.js was causing a function to be added to the list of plugins. 184 | _.each(object.plugins, function(plugin) 185 | { 186 | self.addPluginSource(plugin); 187 | }); 188 | 189 | // Load any plugins referenced in this definition 190 | if(_.isArray(object.plugins) && object.plugins.length > 0) 191 | { 192 | head.js(object.plugins, function() 193 | { 194 | finishLoad(); 195 | }); 196 | } 197 | else 198 | { 199 | finishLoad(); 200 | } 201 | } 202 | 203 | this.clearDashboard = function() 204 | { 205 | freeboardUI.removeAllPanes(); 206 | 207 | _.each(self.datasources(), function(datasource) 208 | { 209 | datasource.dispose(); 210 | }); 211 | 212 | _.each(self.panes(), function(pane) 213 | { 214 | pane.dispose(); 215 | }); 216 | 217 | self.plugins.removeAll(); 218 | self.datasources.removeAll(); 219 | self.panes.removeAll(); 220 | } 221 | 222 | this.loadDashboard = function(dashboardData, callback) 223 | { 224 | freeboardUI.showLoadingIndicator(true); 225 | self.deserialize(dashboardData, function() 226 | { 227 | freeboardUI.showLoadingIndicator(false); 228 | 229 | if(_.isFunction(callback)) 230 | { 231 | callback(); 232 | } 233 | 234 | freeboard.emit("dashboard_loaded"); 235 | }); 236 | } 237 | 238 | this.loadDashboardFromLocalFile = function() 239 | { 240 | // Check for the various File API support. 241 | if(window.File && window.FileReader && window.FileList && window.Blob) 242 | { 243 | var input = document.createElement('input'); 244 | input.type = "file"; 245 | $(input).on("change", function(event) 246 | { 247 | var files = event.target.files; 248 | 249 | if(files && files.length > 0) 250 | { 251 | var file = files[0]; 252 | var reader = new FileReader(); 253 | 254 | reader.addEventListener("load", function(fileReaderEvent) 255 | { 256 | 257 | var textFile = fileReaderEvent.target; 258 | var jsonObject = JSON.parse(textFile.result); 259 | 260 | 261 | self.loadDashboard(jsonObject); 262 | self.setEditing(false); 263 | }); 264 | 265 | reader.readAsText(file); 266 | } 267 | 268 | }); 269 | $(input).trigger("click"); 270 | } 271 | else 272 | { 273 | alert('Unable to load a file in this browser.'); 274 | } 275 | } 276 | 277 | this.saveDashboardClicked = function(){ 278 | var target = $(event.currentTarget); 279 | var siblingsShown = target.data('siblings-shown') || false; 280 | if(!siblingsShown){ 281 | $(event.currentTarget).siblings('label').fadeIn('slow'); 282 | $(event.currentTarget).siblings('input').fadeIn('slow'); 283 | }else{ 284 | $(event.currentTarget).siblings('label').fadeOut('slow'); 285 | $(event.currentTarget).siblings('input').fadeOut('slow'); 286 | } 287 | target.data('siblings-shown', !siblingsShown); 288 | } 289 | 290 | this.saveDashboard = function(_thisref, event) 291 | { 292 | var pretty = $(event.currentTarget).data('pretty'); 293 | var contentType = 'application/octet-stream'; 294 | var a = document.createElement('a'); 295 | if(pretty){ 296 | var blob = new Blob([JSON.stringify(self.serialize(), null, '\t')], {'type': contentType}); 297 | }else{ 298 | var blob = new Blob([JSON.stringify(self.serialize())], {'type': contentType}); 299 | } 300 | document.body.appendChild(a); 301 | a.href = window.URL.createObjectURL(blob); 302 | a.download = "dashboard.json"; 303 | a.target="_self"; 304 | a.click(); 305 | } 306 | 307 | this.saveDashboardtoES = function(_thisref, event) 308 | { 309 | var dashboard = $(event.currentTarget).siblings('input')[0].value; 310 | dashboard = dashboard.replace(/([^a-z0-9]+)/gi, ''); 311 | var panel = self.serialize(); panel.title = dashboard; 312 | if (!dashboard | dashboard === ""){ 313 | return; 314 | } else { 315 | $.ajax({ 316 | url: '/freeboard/dashboards/'+dashboard, 317 | type: 'POST', 318 | data: JSON.stringify(panel), 319 | success: function(data) { 320 | console.log('Dashboard saved to ES ('+dashboard+')') 321 | var win = window.open('#source='+dashboard, '_blank'); 322 | win.focus(); 323 | } 324 | }); 325 | } 326 | $(event.currentTarget).siblings('label')[0].click(); 327 | } 328 | 329 | this.loadDashboardList = function() 330 | { 331 | var dashboards = []; 332 | $.ajax({ 333 | type:"GET", 334 | dataType:"json", 335 | url:"/freeboard/dashboards/_search", 336 | success:function (data) { 337 | if(data.hits.total > 0){ 338 | data.hits.hits.forEach(function(entry) { 339 | dashboards.push(entry._id); 340 | }); 341 | console.log('Dashboard list loaded from ES ('+dashboards+')') 342 | return dashboards; 343 | } else { return dashboards; } 344 | }, 345 | error: function(XMLHttpRequest, textStatus, errorThrown) { 346 | console.log("Error reading Dashboards: " + textStatus+", " + errorThrown); 347 | return; 348 | } 349 | }); 350 | } 351 | 352 | this.addDatasource = function(datasource) 353 | { 354 | self.datasources.push(datasource); 355 | } 356 | 357 | this.deleteDatasource = function(datasource) 358 | { 359 | delete self.datasourceData[datasource.name()]; 360 | datasource.dispose(); 361 | self.datasources.remove(datasource); 362 | } 363 | 364 | this.createPane = function() 365 | { 366 | var newPane = new PaneModel(self, widgetPlugins); 367 | self.addPane(newPane); 368 | } 369 | 370 | this.addGridColumnLeft = function() 371 | { 372 | freeboardUI.addGridColumnLeft(); 373 | } 374 | 375 | this.addGridColumnRight = function() 376 | { 377 | freeboardUI.addGridColumnRight(); 378 | } 379 | 380 | this.subGridColumnLeft = function() 381 | { 382 | freeboardUI.subGridColumnLeft(); 383 | } 384 | 385 | this.subGridColumnRight = function() 386 | { 387 | freeboardUI.subGridColumnRight(); 388 | } 389 | 390 | this.addPane = function(pane) 391 | { 392 | self.panes.push(pane); 393 | } 394 | 395 | this.deletePane = function(pane) 396 | { 397 | pane.dispose(); 398 | self.panes.remove(pane); 399 | } 400 | 401 | this.deleteWidget = function(widget) 402 | { 403 | ko.utils.arrayForEach(self.panes(), function(pane) 404 | { 405 | pane.widgets.remove(widget); 406 | }); 407 | 408 | widget.dispose(); 409 | } 410 | 411 | this.setEditing = function(editing, animate) 412 | { 413 | // Don't allow editing if it's not allowed 414 | if(!self.allow_edit() && editing) 415 | { 416 | return; 417 | } 418 | 419 | self.isEditing(editing); 420 | 421 | if(_.isUndefined(animate)) 422 | { 423 | animate = true; 424 | } 425 | 426 | var animateLength = (animate) ? 250 : 0; 427 | var barHeight = $("#admin-bar").outerHeight(); 428 | 429 | if(!editing) 430 | { 431 | $("#toggle-header-icon").addClass("icon-wrench").removeClass("icon-chevron-up"); 432 | $(".gridster .gs_w").css({cursor: "default"}); 433 | $("#main-header").animate({"top": "-" + barHeight + "px"}, animateLength); 434 | $("#board-content").animate({"top": "20"}, animateLength); 435 | $("#main-header").data().shown = false; 436 | $(".sub-section").unbind(); 437 | freeboardUI.disableGrid(); 438 | } 439 | else 440 | { 441 | $("#toggle-header-icon").addClass("icon-chevron-up").removeClass("icon-wrench"); 442 | $(".gridster .gs_w").css({cursor: "pointer"}); 443 | $("#main-header").animate({"top": "0px"}, animateLength); 444 | $("#board-content").animate({"top": (barHeight + 20) + "px"}, animateLength); 445 | $("#main-header").data().shown = true; 446 | freeboardUI.attachWidgetEditIcons($(".sub-section")); 447 | freeboardUI.enableGrid(); 448 | } 449 | 450 | freeboardUI.showPaneEditIcons(editing, animate); 451 | } 452 | 453 | this.toggleEditing = function() 454 | { 455 | var editing = !self.isEditing(); 456 | self.setEditing(editing); 457 | } 458 | } 459 | -------------------------------------------------------------------------------- /_site/lib/js/freeboard/FreeboardUI.js: -------------------------------------------------------------------------------- 1 | function FreeboardUI() 2 | { 3 | var PANE_MARGIN = 10; 4 | var PANE_WIDTH = 300; 5 | var MIN_COLUMNS = 3; 6 | var COLUMN_WIDTH = PANE_MARGIN + PANE_WIDTH + PANE_MARGIN; 7 | 8 | var userColumns = MIN_COLUMNS; 9 | 10 | var loadingIndicator = $('
    '); 11 | var grid; 12 | 13 | function processResize(layoutWidgets) 14 | { 15 | var maxDisplayableColumns = getMaxDisplayableColumnCount(); 16 | var repositionFunction = function(){}; 17 | if(layoutWidgets) 18 | { 19 | repositionFunction = function(index) 20 | { 21 | var paneElement = this; 22 | var paneModel = ko.dataFor(paneElement); 23 | 24 | var newPosition = getPositionForScreenSize(paneModel); 25 | $(paneElement).attr("data-sizex", Math.min(paneModel.col_width(), 26 | maxDisplayableColumns, grid.cols)) 27 | .attr("data-row", newPosition.row) 28 | .attr("data-col", newPosition.col); 29 | 30 | paneModel.processSizeChange(); 31 | } 32 | } 33 | 34 | updateGridWidth(Math.min(maxDisplayableColumns, userColumns)); 35 | 36 | repositionGrid(repositionFunction); 37 | updateGridColumnControls(); 38 | } 39 | 40 | function addGridColumn(shift) 41 | { 42 | var num_cols = grid.cols + 1; 43 | if(updateGridWidth(num_cols)) 44 | { 45 | repositionGrid(function() { 46 | var paneElement = this; 47 | var paneModel = ko.dataFor(paneElement); 48 | 49 | var prevColumnIndex = grid.cols > 1 ? grid.cols - 1 : 1; 50 | var prevCol = paneModel.col[prevColumnIndex]; 51 | var prevRow = paneModel.row[prevColumnIndex]; 52 | var newPosition; 53 | if(shift) 54 | { 55 | leftPreviewCol = true; 56 | var newCol = prevCol < grid.cols ? prevCol + 1 : grid.cols; 57 | newPosition = {row: prevRow, col: newCol}; 58 | } 59 | else 60 | { 61 | rightPreviewCol = true; 62 | newPosition = {row: prevRow, col: prevCol}; 63 | } 64 | $(paneElement).attr("data-sizex", Math.min(paneModel.col_width(), grid.cols)) 65 | .attr("data-row", newPosition.row) 66 | .attr("data-col", newPosition.col); 67 | }); 68 | } 69 | updateGridColumnControls(); 70 | userColumns = grid.cols; 71 | } 72 | 73 | function subtractGridColumn(shift) 74 | { 75 | var num_cols = grid.cols - 1; 76 | if(updateGridWidth(num_cols)) 77 | { 78 | repositionGrid(function() { 79 | var paneElement = this; 80 | var paneModel = ko.dataFor(paneElement); 81 | 82 | var prevColumnIndex = grid.cols + 1; 83 | var prevCol = paneModel.col[prevColumnIndex]; 84 | var prevRow = paneModel.row[prevColumnIndex]; 85 | var newPosition; 86 | if(shift) 87 | { 88 | var newCol = prevCol > 1 ? prevCol - 1 : 1; 89 | newPosition = {row: prevRow, col: newCol}; 90 | } 91 | else 92 | { 93 | var newCol = prevCol <= grid.cols ? prevCol : grid.cols; 94 | newPosition = {row: prevRow, col: newCol}; 95 | } 96 | $(paneElement).attr("data-sizex", Math.min(paneModel.col_width(), grid.cols)) 97 | .attr("data-row", newPosition.row) 98 | .attr("data-col", newPosition.col); 99 | }); 100 | } 101 | updateGridColumnControls(); 102 | userColumns = grid.cols; 103 | } 104 | 105 | function updateGridColumnControls() 106 | { 107 | var col_controls = $(".column-tool"); 108 | var available_width = $("#board-content").width(); 109 | var max_columns = Math.floor(available_width / COLUMN_WIDTH); 110 | 111 | if(grid.cols <= MIN_COLUMNS) 112 | { 113 | col_controls.addClass("min"); 114 | } 115 | else 116 | { 117 | col_controls.removeClass("min"); 118 | } 119 | 120 | if(grid.cols >= max_columns) 121 | { 122 | col_controls.addClass("max"); 123 | } 124 | else 125 | { 126 | col_controls.removeClass("max"); 127 | } 128 | } 129 | 130 | function getMaxDisplayableColumnCount() 131 | { 132 | var available_width = $("#board-content").width(); 133 | return Math.floor(available_width / COLUMN_WIDTH); 134 | } 135 | 136 | function updateGridWidth(newCols) 137 | { 138 | if(newCols === undefined || newCols < MIN_COLUMNS) 139 | { 140 | newCols = MIN_COLUMNS; 141 | } 142 | 143 | var max_columns = getMaxDisplayableColumnCount(); 144 | if(newCols > max_columns) 145 | { 146 | newCols = max_columns; 147 | } 148 | 149 | // +newCols to account for scaling on zoomed browsers 150 | var new_width = (COLUMN_WIDTH * newCols) + newCols; 151 | $(".responsive-column-width").css("max-width", new_width); 152 | 153 | if(newCols === grid.cols) 154 | { 155 | return false; 156 | } 157 | else 158 | { 159 | return true; 160 | } 161 | } 162 | 163 | function repositionGrid(repositionFunction) 164 | { 165 | var rootElement = grid.$el; 166 | 167 | rootElement.find("> li").unbind().removeData(); 168 | $(".responsive-column-width").css("width", ""); 169 | grid.generate_grid_and_stylesheet(); 170 | 171 | rootElement.find("> li").each(repositionFunction); 172 | 173 | grid.init(); 174 | $(".responsive-column-width").css("width", grid.cols * PANE_WIDTH + (grid.cols * PANE_MARGIN * 2)); 175 | } 176 | 177 | function getUserColumns() 178 | { 179 | return userColumns; 180 | } 181 | 182 | function setUserColumns(numCols) 183 | { 184 | userColumns = Math.max(MIN_COLUMNS, numCols); 185 | } 186 | 187 | ko.bindingHandlers.grid = { 188 | init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) 189 | { 190 | // Initialize our grid 191 | grid = $(element).gridster({ 192 | widget_margins : [PANE_MARGIN, PANE_MARGIN], 193 | widget_base_dimensions: [PANE_WIDTH, 10], 194 | resize: { 195 | enabled : false, 196 | axes : "x" 197 | } 198 | }).data("gridster"); 199 | 200 | processResize(false) 201 | 202 | grid.disable(); 203 | } 204 | } 205 | 206 | function addPane(element, viewModel, isEditing) 207 | { 208 | var position = getPositionForScreenSize(viewModel); 209 | var col = position.col; 210 | var row = position.row; 211 | var width = Number(viewModel.width()); 212 | var height = Number(viewModel.getCalculatedHeight()); 213 | 214 | grid.add_widget(element, width, height, col, row); 215 | 216 | if(isEditing) 217 | { 218 | showPaneEditIcons(true); 219 | } 220 | 221 | updatePositionForScreenSize(viewModel, row, col); 222 | 223 | $(element).attrchange({ 224 | trackValues: true, 225 | callback : function(event) 226 | { 227 | if(event.attributeName == "data-row") 228 | { 229 | updatePositionForScreenSize(viewModel, Number(event.newValue), undefined); 230 | } 231 | else if(event.attributeName == "data-col") 232 | { 233 | updatePositionForScreenSize(viewModel, undefined, Number(event.newValue)); 234 | } 235 | } 236 | }); 237 | } 238 | 239 | function updatePane(element, viewModel) 240 | { 241 | // If widget has been added or removed 242 | var calculatedHeight = viewModel.getCalculatedHeight(); 243 | 244 | var elementHeight = Number($(element).attr("data-sizey")); 245 | var elementWidth = Number($(element).attr("data-sizex")); 246 | 247 | if(calculatedHeight != elementHeight || viewModel.col_width() != elementWidth) 248 | { 249 | grid.resize_widget($(element), viewModel.col_width(), calculatedHeight, function(){ 250 | grid.set_dom_grid_height(); 251 | }); 252 | } 253 | } 254 | 255 | function updatePositionForScreenSize(paneModel, row, col) 256 | { 257 | var displayCols = grid.cols; 258 | 259 | if(!_.isUndefined(row)) paneModel.row[displayCols] = row; 260 | if(!_.isUndefined(col)) paneModel.col[displayCols] = col; 261 | } 262 | 263 | function showLoadingIndicator(show) 264 | { 265 | if(show) 266 | { 267 | loadingIndicator.fadeOut(0).appendTo("body").fadeIn(500); 268 | } 269 | else 270 | { 271 | loadingIndicator.fadeOut(500).remove(); 272 | } 273 | } 274 | 275 | function showPaneEditIcons(show, animate) 276 | { 277 | if(_.isUndefined(animate)) 278 | { 279 | animate = true; 280 | } 281 | 282 | var animateLength = (animate) ? 250 : 0; 283 | 284 | if(show) 285 | { 286 | $(".pane-tools").fadeIn(animateLength);//.css("display", "block").animate({opacity: 1.0}, animateLength); 287 | $("#column-tools").fadeIn(animateLength); 288 | } 289 | else 290 | { 291 | $(".pane-tools").fadeOut(animateLength);//.animate({opacity: 0.0}, animateLength).css("display", "none");//, function() 292 | $("#column-tools").fadeOut(animateLength); 293 | } 294 | } 295 | 296 | function attachWidgetEditIcons(element) 297 | { 298 | $(element).hover(function() 299 | { 300 | showWidgetEditIcons(this, true); 301 | }, function() 302 | { 303 | showWidgetEditIcons(this, false); 304 | }); 305 | } 306 | 307 | function showWidgetEditIcons(element, show) 308 | { 309 | if(show) 310 | { 311 | $(element).find(".sub-section-tools").fadeIn(250); 312 | } 313 | else 314 | { 315 | $(element).find(".sub-section-tools").fadeOut(250); 316 | } 317 | } 318 | 319 | function getPositionForScreenSize(paneModel) 320 | { 321 | var cols = grid.cols; 322 | 323 | if(_.isNumber(paneModel.row) && _.isNumber(paneModel.col)) // Support for legacy format 324 | { 325 | var obj = {}; 326 | obj[cols] = paneModel.row; 327 | paneModel.row = obj; 328 | 329 | 330 | obj = {}; 331 | obj[cols] = paneModel.col; 332 | paneModel.col = obj; 333 | } 334 | 335 | var newColumnIndex = 1; 336 | var columnDiff = 1000; 337 | 338 | for(var columnIndex in paneModel.col) 339 | { 340 | if(columnIndex == cols) // If we already have a position defined for this number of columns, return that position 341 | { 342 | return {row: paneModel.row[columnIndex], col: paneModel.col[columnIndex]}; 343 | } 344 | else if(paneModel.col[columnIndex] > cols) // If it's greater than our display columns, put it in the last column 345 | { 346 | newColumnIndex = cols; 347 | } 348 | else // If it's less than, pick whichever one is closest 349 | { 350 | var delta = cols - columnIndex; 351 | 352 | if(delta < columnDiff) 353 | { 354 | newColumnIndex = columnIndex; 355 | columnDiff = delta; 356 | } 357 | } 358 | } 359 | 360 | if(newColumnIndex in paneModel.col && newColumnIndex in paneModel.row) 361 | { 362 | return {row: paneModel.row[newColumnIndex], col: paneModel.col[newColumnIndex]}; 363 | } 364 | 365 | return {row:1,col:newColumnIndex}; 366 | } 367 | 368 | 369 | // Public Functions 370 | return { 371 | showLoadingIndicator : function(show) 372 | { 373 | showLoadingIndicator(show); 374 | }, 375 | showPaneEditIcons : function(show, animate) 376 | { 377 | showPaneEditIcons(show, animate); 378 | }, 379 | attachWidgetEditIcons : function(element) 380 | { 381 | attachWidgetEditIcons(element); 382 | }, 383 | getPositionForScreenSize : function(paneModel) 384 | { 385 | return getPositionForScreenSize(paneModel); 386 | }, 387 | processResize : function(layoutWidgets) 388 | { 389 | processResize(layoutWidgets); 390 | }, 391 | disableGrid : function() 392 | { 393 | grid.disable(); 394 | }, 395 | enableGrid : function() 396 | { 397 | grid.enable(); 398 | }, 399 | addPane : function(element, viewModel, isEditing) 400 | { 401 | addPane(element, viewModel, isEditing); 402 | }, 403 | updatePane : function(element, viewModel) 404 | { 405 | updatePane(element, viewModel); 406 | }, 407 | removePane : function(element) 408 | { 409 | grid.remove_widget(element); 410 | }, 411 | removeAllPanes : function() 412 | { 413 | grid.remove_all_widgets(); 414 | }, 415 | addGridColumnLeft : function() 416 | { 417 | addGridColumn(true); 418 | }, 419 | addGridColumnRight : function() 420 | { 421 | addGridColumn(false); 422 | }, 423 | subGridColumnLeft : function() 424 | { 425 | subtractGridColumn(true); 426 | }, 427 | subGridColumnRight : function() 428 | { 429 | subtractGridColumn(false); 430 | }, 431 | getUserColumns : function() 432 | { 433 | return getUserColumns(); 434 | }, 435 | setUserColumns : function(numCols) 436 | { 437 | setUserColumns(numCols); 438 | } 439 | } 440 | } 441 | -------------------------------------------------------------------------------- /_site/lib/js/freeboard/JSEditor.js: -------------------------------------------------------------------------------- 1 | JSEditor = function () { 2 | var assetRoot = "" 3 | 4 | function setAssetRoot(_assetRoot) { 5 | assetRoot = _assetRoot; 6 | } 7 | 8 | function displayJSEditor(value, callback) { 9 | 10 | var exampleText = "// Example: Convert temp from C to F and truncate to 2 decimal places.\n// return (datasources[\"MyDatasource\"].sensor.tempInF * 1.8 + 32).toFixed(2);"; 11 | 12 | // If value is empty, go ahead and suggest something 13 | if (!value) { 14 | value = exampleText; 15 | } 16 | 17 | var codeWindow = $('
    '); 18 | var codeMirrorWrapper = $('
    '); 19 | var codeWindowFooter = $(''); 20 | var codeWindowHeader = $('
    This javascript will be re-evaluated any time a datasource referenced here is updated, and the value you return will be displayed in the widget. You can assume this javascript is wrapped in a function of the form function(datasources) where datasources is a collection of javascript objects (keyed by their name) corresponding to the most current data in a datasource.
    '); 21 | 22 | codeWindow.append([codeWindowHeader, codeMirrorWrapper, codeWindowFooter]); 23 | 24 | $("body").append(codeWindow); 25 | 26 | var codeMirrorEditor = CodeMirror(codeMirrorWrapper.get(0), 27 | { 28 | value: value, 29 | mode: "javascript", 30 | theme: "ambiance", 31 | indentUnit: 4, 32 | lineNumbers: true, 33 | matchBrackets: true, 34 | autoCloseBrackets: true 35 | } 36 | ); 37 | 38 | var closeButton = $('Close').click(function () { 39 | if (callback) { 40 | var newValue = codeMirrorEditor.getValue(); 41 | 42 | if (newValue === exampleText) { 43 | newValue = ""; 44 | } 45 | 46 | callback(newValue); 47 | codeWindow.remove(); 48 | } 49 | }); 50 | 51 | codeWindowFooter.append(closeButton); 52 | } 53 | 54 | // Public API 55 | return { 56 | displayJSEditor: function (value, callback) { 57 | displayJSEditor(value, callback); 58 | }, 59 | setAssetRoot: function (assetRoot) { 60 | setAssetRoot(assetRoot) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /_site/lib/js/freeboard/PaneModel.js: -------------------------------------------------------------------------------- 1 | function PaneModel(theFreeboardModel, widgetPlugins) { 2 | var self = this; 3 | 4 | this.title = ko.observable(); 5 | this.width = ko.observable(1); 6 | this.row = {}; 7 | this.col = {}; 8 | 9 | this.col_width = ko.observable(1); 10 | this.col_width.subscribe(function(newValue) 11 | { 12 | self.processSizeChange(); 13 | }); 14 | 15 | this.widgets = ko.observableArray(); 16 | 17 | this.addWidget = function (widget) { 18 | this.widgets.push(widget); 19 | } 20 | 21 | this.widgetCanMoveUp = function (widget) { 22 | return (self.widgets.indexOf(widget) >= 1); 23 | } 24 | 25 | this.widgetCanMoveDown = function (widget) { 26 | var i = self.widgets.indexOf(widget); 27 | 28 | return (i < self.widgets().length - 1); 29 | } 30 | 31 | this.moveWidgetUp = function (widget) { 32 | if (self.widgetCanMoveUp(widget)) { 33 | var i = self.widgets.indexOf(widget); 34 | var array = self.widgets(); 35 | self.widgets.splice(i - 1, 2, array[i], array[i - 1]); 36 | } 37 | } 38 | 39 | this.moveWidgetDown = function (widget) { 40 | if (self.widgetCanMoveDown(widget)) { 41 | var i = self.widgets.indexOf(widget); 42 | var array = self.widgets(); 43 | self.widgets.splice(i, 2, array[i + 1], array[i]); 44 | } 45 | } 46 | 47 | this.processSizeChange = function() 48 | { 49 | // Give the animation a moment to complete. Really hacky. 50 | // TODO: Make less hacky. Also, doesn't work when screen resizes. 51 | setTimeout(function(){ 52 | _.each(self.widgets(), function (widget) { 53 | widget.processSizeChange(); 54 | }); 55 | }, 1000); 56 | } 57 | 58 | this.getCalculatedHeight = function () { 59 | var sumHeights = _.reduce(self.widgets(), function (memo, widget) { 60 | return memo + widget.height(); 61 | }, 0); 62 | 63 | sumHeights *= 6; 64 | sumHeights += 3; 65 | 66 | sumHeights *= 10; 67 | 68 | var rows = Math.ceil((sumHeights + 20) / 30); 69 | 70 | return Math.max(4, rows); 71 | } 72 | 73 | this.serialize = function () { 74 | var widgets = []; 75 | 76 | _.each(self.widgets(), function (widget) { 77 | widgets.push(widget.serialize()); 78 | }); 79 | 80 | return { 81 | title: self.title(), 82 | width: self.width(), 83 | row: self.row, 84 | col: self.col, 85 | col_width: self.col_width(), 86 | widgets: widgets 87 | }; 88 | } 89 | 90 | this.deserialize = function (object) { 91 | self.title(object.title); 92 | self.width(object.width); 93 | 94 | self.row = object.row; 95 | self.col = object.col; 96 | self.col_width(object.col_width || 1); 97 | 98 | _.each(object.widgets, function (widgetConfig) { 99 | var widget = new WidgetModel(theFreeboardModel, widgetPlugins); 100 | widget.deserialize(widgetConfig); 101 | self.widgets.push(widget); 102 | }); 103 | } 104 | 105 | this.dispose = function () { 106 | _.each(self.widgets(), function (widget) { 107 | widget.dispose(); 108 | }); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /_site/lib/js/freeboard/ValueEditor.js: -------------------------------------------------------------------------------- 1 | ValueEditor = function(theFreeboardModel) 2 | { 3 | var _veDatasourceRegex = new RegExp(".*datasources\\[\"([^\"]*)(\"\\])?(.*)$"); 4 | 5 | var dropdown = null; 6 | var selectedOptionIndex = 0; 7 | var _autocompleteOptions = []; 8 | var currentValue = null; 9 | 10 | var EXPECTED_TYPE = { 11 | ANY : "any", 12 | ARRAY : "array", 13 | OBJECT : "object", 14 | STRING : "string", 15 | NUMBER : "number", 16 | BOOLEAN : "boolean" 17 | }; 18 | 19 | function _isPotentialTypeMatch(value, expectsType) 20 | { 21 | if(_.isArray(value) || _.isObject(value)) 22 | { 23 | return true; 24 | } 25 | return _isTypeMatch(value, expectsType); 26 | } 27 | 28 | function _isTypeMatch(value, expectsType) { 29 | switch(expectsType) 30 | { 31 | case EXPECTED_TYPE.ANY: return true; 32 | case EXPECTED_TYPE.ARRAY: return _.isArray(value); 33 | case EXPECTED_TYPE.OBJECT: return _.isObject(value); 34 | case EXPECTED_TYPE.STRING: return _.isString(value); 35 | case EXPECTED_TYPE.NUMBER: return _.isNumber(value); 36 | case EXPECTED_TYPE.BOOLEAN: return _.isBoolean(value); 37 | } 38 | } 39 | 40 | function _checkCurrentValueType(element, expectsType) { 41 | $(element).parent().find(".validation-error").remove(); 42 | if(!_isTypeMatch(currentValue, expectsType)) { 43 | $(element).parent().append("
    " + 44 | "This field expects an expression that evaluates to type " + 45 | expectsType + ".
    "); 46 | } 47 | } 48 | 49 | function _resizeValueEditor(element) 50 | { 51 | var lineBreakCount = ($(element).val().match(/\n/g) || []).length; 52 | 53 | var newHeight = Math.min(200, 20 * (lineBreakCount + 1)); 54 | 55 | $(element).css({height: newHeight + "px"}); 56 | } 57 | 58 | function _autocompleteFromDatasource(inputString, datasources, expectsType) 59 | { 60 | var match = _veDatasourceRegex.exec(inputString); 61 | 62 | var options = []; 63 | 64 | if(match) 65 | { 66 | // Editor value is: datasources["; List all datasources 67 | if(match[1] == "") 68 | { 69 | _.each(datasources, function(datasource) 70 | { 71 | options.push({value: datasource.name(), entity: undefined, 72 | precede_char: "", follow_char: "\"]"}); 73 | }); 74 | } 75 | // Editor value is a partial match for a datasource; list matching datasources 76 | else if(match[1] != "" && _.isUndefined(match[2])) 77 | { 78 | var replacementString = match[1]; 79 | 80 | _.each(datasources, function(datasource) 81 | { 82 | var dsName = datasource.name(); 83 | 84 | if(dsName != replacementString && dsName.indexOf(replacementString) == 0) 85 | { 86 | options.push({value: dsName, entity: undefined, 87 | precede_char: "", follow_char: "\"]"}); 88 | } 89 | }); 90 | } 91 | // Editor value matches a datasources; parse JSON in order to populate list 92 | else 93 | { 94 | // We already have a datasource selected; find it 95 | var datasource = _.find(datasources, function(datasource) 96 | { 97 | return (datasource.name() === match[1]); 98 | }); 99 | 100 | if(!_.isUndefined(datasource)) 101 | { 102 | var dataPath = "data"; 103 | var remainder = ""; 104 | 105 | // Parse the partial JSON selectors 106 | if(!_.isUndefined(match[2])) 107 | { 108 | // Strip any incomplete field values, and store the remainder 109 | var remainderIndex = match[3].lastIndexOf("]") + 1; 110 | dataPath = dataPath + match[3].substring(0, remainderIndex); 111 | remainder = match[3].substring(remainderIndex, match[3].length); 112 | remainder = remainder.replace(/^[\[\"]*/, ""); 113 | remainder = remainder.replace(/[\"\]]*$/, ""); 114 | } 115 | 116 | // Get the data for the last complete JSON field 117 | var dataValue = datasource.getDataRepresentation(dataPath); 118 | currentValue = dataValue; 119 | 120 | // For arrays, list out the indices 121 | if(_.isArray(dataValue)) 122 | { 123 | for(var index = 0; index < dataValue.length; index++) 124 | { 125 | if(index.toString().indexOf(remainder) == 0) 126 | { 127 | var value = dataValue[index]; 128 | if(_isPotentialTypeMatch(value, expectsType)) 129 | { 130 | options.push({value: index, entity: value, 131 | precede_char: "[", follow_char: "]", 132 | preview: value.toString()}); 133 | } 134 | } 135 | } 136 | } 137 | // For objects, list out the keys 138 | else if(_.isObject(dataValue)) 139 | { 140 | _.each(dataValue, function(value, name) 141 | { 142 | if(name.indexOf(remainder) == 0) 143 | { 144 | if(_isPotentialTypeMatch(value, expectsType)) 145 | { 146 | options.push({value: name, entity: value, 147 | precede_char: "[\"", follow_char: "\"]"}); 148 | } 149 | } 150 | }); 151 | } 152 | // For everything else, do nothing (no further selection possible) 153 | else 154 | { 155 | // no-op 156 | } 157 | } 158 | } 159 | } 160 | _autocompleteOptions = options; 161 | } 162 | 163 | function _renderAutocompleteDropdown(element, expectsType) 164 | { 165 | var inputString = $(element).val().substring(0, $(element).getCaretPosition()); 166 | 167 | // Weird issue where the textarea box was putting in ASCII (nbsp) for spaces. 168 | inputString = inputString.replace(String.fromCharCode(160), " "); 169 | 170 | _autocompleteFromDatasource(inputString, theFreeboardModel.datasources(), expectsType); 171 | 172 | if(_autocompleteOptions.length > 0) 173 | { 174 | if(!dropdown) 175 | { 176 | dropdown = $('') 177 | .insertAfter(element) 178 | .width($(element).outerWidth() - 2) 179 | .css("left", $(element).position().left) 180 | .css("top", $(element).position().top + $(element).outerHeight() - 1); 181 | } 182 | 183 | dropdown.empty(); 184 | dropdown.scrollTop(0); 185 | 186 | var selected = true; 187 | selectedOptionIndex = 0; 188 | 189 | _.each(_autocompleteOptions, function(option, index) 190 | { 191 | var li = _renderAutocompleteDropdownOption(element, inputString, option, index); 192 | if(selected) 193 | { 194 | $(li).addClass("selected"); 195 | selected = false; 196 | } 197 | }); 198 | } 199 | else 200 | { 201 | _checkCurrentValueType(element, expectsType); 202 | $(element).next("ul#value-selector").remove(); 203 | dropdown = null; 204 | selectedOptionIndex = -1; 205 | } 206 | } 207 | 208 | function _renderAutocompleteDropdownOption(element, inputString, option, currentIndex) 209 | { 210 | var optionLabel = option.value; 211 | if(option.preview) 212 | { 213 | optionLabel = optionLabel + "" + option.preview + ""; 214 | } 215 | var li = $('
  • ' + optionLabel + '
  • ').appendTo(dropdown) 216 | .mouseenter(function() 217 | { 218 | $(this).trigger("freeboard-select"); 219 | }) 220 | .mousedown(function(event) 221 | { 222 | $(this).trigger("freeboard-insertValue"); 223 | event.preventDefault(); 224 | }) 225 | .data("freeboard-optionIndex", currentIndex) 226 | .data("freeboard-optionValue", option.value) 227 | .bind("freeboard-insertValue", function() 228 | { 229 | var optionValue = option.value; 230 | optionValue = option.precede_char + optionValue + option.follow_char; 231 | 232 | var replacementIndex = inputString.lastIndexOf("]"); 233 | if(replacementIndex != -1) 234 | { 235 | $(element).replaceTextAt(replacementIndex+1, $(element).val().length, 236 | optionValue); 237 | } 238 | else 239 | { 240 | $(element).insertAtCaret(optionValue); 241 | } 242 | 243 | currentValue = option.entity; 244 | $(element).triggerHandler("mouseup"); 245 | }) 246 | .bind("freeboard-select", function() 247 | { 248 | $(this).parent().find("li.selected").removeClass("selected"); 249 | $(this).addClass("selected"); 250 | selectedOptionIndex = $(this).data("freeboard-optionIndex"); 251 | }); 252 | return li; 253 | } 254 | 255 | function createValueEditor(element, expectsType) 256 | { 257 | $(element).addClass("calculated-value-input") 258 | .bind("keyup mouseup freeboard-eval", function(event) { 259 | // Ignore arrow keys and enter keys 260 | if(dropdown && event.type == "keyup" 261 | && (event.keyCode == 38 || event.keyCode == 40 || event.keyCode == 13)) 262 | { 263 | event.preventDefault(); 264 | return; 265 | } 266 | _renderAutocompleteDropdown(element, expectsType); 267 | }) 268 | .focus(function() 269 | { 270 | $(element).css({"z-index" : 3001}); 271 | _resizeValueEditor(element); 272 | }) 273 | .focusout(function() 274 | { 275 | _checkCurrentValueType(element, expectsType); 276 | $(element).css({ 277 | "height": "", 278 | "z-index" : 3000 279 | }); 280 | $(element).next("ul#value-selector").remove(); 281 | dropdown = null; 282 | selectedOptionIndex = -1; 283 | }) 284 | .bind("keydown", function(event) 285 | { 286 | 287 | if(dropdown) 288 | { 289 | if(event.keyCode == 38 || event.keyCode == 40) // Handle Arrow keys 290 | { 291 | event.preventDefault(); 292 | 293 | var optionItems = $(dropdown).find("li"); 294 | 295 | if(event.keyCode == 38) // Up Arrow 296 | { 297 | selectedOptionIndex--; 298 | } 299 | else if(event.keyCode == 40) // Down Arrow 300 | { 301 | selectedOptionIndex++; 302 | } 303 | 304 | if(selectedOptionIndex < 0) 305 | { 306 | selectedOptionIndex = optionItems.size() - 1; 307 | } 308 | else if(selectedOptionIndex >= optionItems.size()) 309 | { 310 | selectedOptionIndex = 0; 311 | } 312 | 313 | var optionElement = $(optionItems).eq(selectedOptionIndex); 314 | 315 | optionElement.trigger("freeboard-select"); 316 | $(dropdown).scrollTop($(optionElement).position().top); 317 | } 318 | else if(event.keyCode == 13) // Handle enter key 319 | { 320 | event.preventDefault(); 321 | 322 | if(selectedOptionIndex != -1) 323 | { 324 | $(dropdown).find("li").eq(selectedOptionIndex) 325 | .trigger("freeboard-insertValue"); 326 | } 327 | } 328 | } 329 | }); 330 | } 331 | 332 | // Public API 333 | return { 334 | createValueEditor : function(element, expectsType) 335 | { 336 | if(expectsType) 337 | { 338 | createValueEditor(element, expectsType); 339 | } 340 | else { 341 | createValueEditor(element, EXPECTED_TYPE.ANY); 342 | } 343 | }, 344 | EXPECTED_TYPE : EXPECTED_TYPE 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /_site/lib/js/freeboard/WidgetModel.js: -------------------------------------------------------------------------------- 1 | function WidgetModel(theFreeboardModel, widgetPlugins) { 2 | function disposeWidgetInstance() { 3 | if (!_.isUndefined(self.widgetInstance)) { 4 | if (_.isFunction(self.widgetInstance.onDispose)) { 5 | self.widgetInstance.onDispose(); 6 | } 7 | 8 | self.widgetInstance = undefined; 9 | } 10 | } 11 | 12 | var self = this; 13 | 14 | this.datasourceRefreshNotifications = {}; 15 | this.calculatedSettingScripts = {}; 16 | 17 | this.title = ko.observable(); 18 | this.fillSize = ko.observable(false); 19 | 20 | this.type = ko.observable(); 21 | this.type.subscribe(function (newValue) { 22 | disposeWidgetInstance(); 23 | 24 | if ((newValue in widgetPlugins) && _.isFunction(widgetPlugins[newValue].newInstance)) { 25 | var widgetType = widgetPlugins[newValue]; 26 | 27 | function finishLoad() { 28 | widgetType.newInstance(self.settings(), function (widgetInstance) { 29 | 30 | self.fillSize((widgetType.fill_size === true)); 31 | self.widgetInstance = widgetInstance; 32 | self.shouldRender(true); 33 | self._heightUpdate.valueHasMutated(); 34 | 35 | }); 36 | } 37 | 38 | // Do we need to load any external scripts? 39 | if (widgetType.external_scripts) { 40 | head.js(widgetType.external_scripts.slice(0), finishLoad); // Need to clone the array because head.js adds some weird functions to it 41 | } 42 | else { 43 | finishLoad(); 44 | } 45 | } 46 | }); 47 | 48 | this.settings = ko.observable({}); 49 | this.settings.subscribe(function (newValue) { 50 | if (!_.isUndefined(self.widgetInstance) && _.isFunction(self.widgetInstance.onSettingsChanged)) { 51 | self.widgetInstance.onSettingsChanged(newValue); 52 | } 53 | 54 | self.updateCalculatedSettings(); 55 | self._heightUpdate.valueHasMutated(); 56 | }); 57 | 58 | this.processDatasourceUpdate = function (datasourceName) { 59 | var refreshSettingNames = self.datasourceRefreshNotifications[datasourceName]; 60 | 61 | if (_.isArray(refreshSettingNames)) { 62 | _.each(refreshSettingNames, function (settingName) { 63 | self.processCalculatedSetting(settingName); 64 | }); 65 | } 66 | } 67 | 68 | this.callValueFunction = function (theFunction) { 69 | return theFunction.call(undefined, theFreeboardModel.datasourceData); 70 | } 71 | 72 | this.processSizeChange = function () { 73 | if (!_.isUndefined(self.widgetInstance) && _.isFunction(self.widgetInstance.onSizeChanged)) { 74 | self.widgetInstance.onSizeChanged(); 75 | } 76 | } 77 | 78 | this.processCalculatedSetting = function (settingName) { 79 | if (_.isFunction(self.calculatedSettingScripts[settingName])) { 80 | var returnValue = undefined; 81 | 82 | try { 83 | returnValue = self.callValueFunction(self.calculatedSettingScripts[settingName]); 84 | } 85 | catch (e) { 86 | var rawValue = self.settings()[settingName]; 87 | 88 | // If there is a reference error and the value just contains letters and numbers, then 89 | if (e instanceof ReferenceError && (/^\w+$/).test(rawValue)) { 90 | returnValue = rawValue; 91 | } 92 | } 93 | 94 | if (!_.isUndefined(self.widgetInstance) && _.isFunction(self.widgetInstance.onCalculatedValueChanged) && !_.isUndefined(returnValue)) { 95 | try { 96 | self.widgetInstance.onCalculatedValueChanged(settingName, returnValue); 97 | //force height refresh 98 | self._heightUpdate.valueHasMutated(); 99 | } 100 | catch (e) { 101 | console.log(e.toString()); 102 | } 103 | } 104 | } 105 | } 106 | 107 | this.updateCalculatedSettings = function () { 108 | self.datasourceRefreshNotifications = {}; 109 | self.calculatedSettingScripts = {}; 110 | 111 | if (_.isUndefined(self.type())) { 112 | return; 113 | } 114 | 115 | // Check for any calculated settings 116 | var settingsDefs = widgetPlugins[self.type()].settings; 117 | var datasourceRegex = new RegExp("datasources.([\\w_-]+)|datasources\\[['\"]([^'\"]+)", "g"); 118 | var currentSettings = self.settings(); 119 | 120 | _.each(settingsDefs, function (settingDef) { 121 | if (settingDef.type == "calculated") { 122 | var script = currentSettings[settingDef.name]; 123 | 124 | if (!_.isUndefined(script)) { 125 | 126 | if(_.isArray(script)) { 127 | script = "[" + script.join(",") + "]"; 128 | } 129 | 130 | // If there is no return, add one 131 | if ((script.match(/;/g) || []).length <= 1 && script.indexOf("return") == -1) { 132 | script = "return " + script; 133 | } 134 | 135 | var valueFunction; 136 | 137 | try { 138 | valueFunction = new Function("datasources", script); 139 | } 140 | catch (e) { 141 | var literalText = currentSettings[settingDef.name].replace(/"/g, '\\"').replace(/[\r\n]/g, ' \\\n'); 142 | 143 | // If the value function cannot be created, then go ahead and treat it as literal text 144 | valueFunction = new Function("datasources", "return \"" + literalText + "\";"); 145 | } 146 | 147 | self.calculatedSettingScripts[settingDef.name] = valueFunction; 148 | self.processCalculatedSetting(settingDef.name); 149 | 150 | // Are there any datasources we need to be subscribed to? 151 | var matches; 152 | 153 | while (matches = datasourceRegex.exec(script)) { 154 | var dsName = (matches[1] || matches[2]); 155 | var refreshSettingNames = self.datasourceRefreshNotifications[dsName]; 156 | 157 | if (_.isUndefined(refreshSettingNames)) { 158 | refreshSettingNames = []; 159 | self.datasourceRefreshNotifications[dsName] = refreshSettingNames; 160 | } 161 | 162 | if(_.indexOf(refreshSettingNames, settingDef.name) == -1) // Only subscribe to this notification once. 163 | { 164 | refreshSettingNames.push(settingDef.name); 165 | } 166 | } 167 | } 168 | } 169 | }); 170 | } 171 | 172 | this._heightUpdate = ko.observable(); 173 | this.height = ko.computed({ 174 | read: function () { 175 | self._heightUpdate(); 176 | 177 | if (!_.isUndefined(self.widgetInstance) && _.isFunction(self.widgetInstance.getHeight)) { 178 | return self.widgetInstance.getHeight(); 179 | } 180 | 181 | return 1; 182 | } 183 | }); 184 | 185 | this.shouldRender = ko.observable(false); 186 | this.render = function (element) { 187 | self.shouldRender(false); 188 | if (!_.isUndefined(self.widgetInstance) && _.isFunction(self.widgetInstance.render)) { 189 | self.widgetInstance.render(element); 190 | self.updateCalculatedSettings(); 191 | } 192 | } 193 | 194 | this.dispose = function () { 195 | 196 | } 197 | 198 | this.serialize = function () { 199 | return { 200 | title: self.title(), 201 | type: self.type(), 202 | settings: self.settings() 203 | }; 204 | } 205 | 206 | this.deserialize = function (object) { 207 | self.title(object.title); 208 | self.settings(object.settings); 209 | self.type(object.type); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /_site/lib/js/thirdparty/head.js: -------------------------------------------------------------------------------- 1 | /*! head.core - v1.0.2 */ 2 | (function(n,t){"use strict";function r(n){a[a.length]=n}function k(n){var t=new RegExp(" ?\\b"+n+"\\b");c.className=c.className.replace(t,"")}function p(n,t){for(var i=0,r=n.length;in?(i.screensCss.gt&&r("gt-"+n),i.screensCss.gte&&r("gte-"+n)):tt);u.feature("landscape",fe?(i.browserCss.gt&&r("gt-"+f+e),i.browserCss.gte&&r("gte-"+f+e)):h2&&this[u+1]!==t)u&&r(this.slice(u,u+1).join("-").toLowerCase()+i.section);else{var f=n||"index",e=f.indexOf(".");e>0&&(f=f.substring(0,e));c.id=f.toLowerCase()+i.page;u||r("root"+i.section)}});u.screen={height:n.screen.height,width:n.screen.width};tt();b=0;n.addEventListener?n.addEventListener("resize",it,!1):n.attachEvent("onresize",it)})(window); 3 | /*! head.css3 - v1.0.0 */ 4 | (function(n,t){"use strict";function a(n){for(var r in n)if(i[n[r]]!==t)return!0;return!1}function r(n){var t=n.charAt(0).toUpperCase()+n.substr(1),i=(n+" "+c.join(t+" ")+t).split(" ");return!!a(i)}var h=n.document,o=h.createElement("i"),i=o.style,s=" -o- -moz- -ms- -webkit- -khtml- ".split(" "),c="Webkit Moz O ms Khtml".split(" "),l=n.head_conf&&n.head_conf.head||"head",u=n[l],f={gradient:function(){var n="background-image:";return i.cssText=(n+s.join("gradient(linear,left top,right bottom,from(#9f9),to(#fff));"+n)+s.join("linear-gradient(left top,#eee,#fff);"+n)).slice(0,-n.length),!!i.backgroundImage},rgba:function(){return i.cssText="background-color:rgba(0,0,0,0.5)",!!i.backgroundColor},opacity:function(){return o.style.opacity===""},textshadow:function(){return i.textShadow===""},multiplebgs:function(){i.cssText="background:url(https://),url(https://),red url(https://)";var n=(i.background||"").match(/url/g);return Object.prototype.toString.call(n)==="[object Array]"&&n.length===3},boxshadow:function(){return r("boxShadow")},borderimage:function(){return r("borderImage")},borderradius:function(){return r("borderRadius")},cssreflections:function(){return r("boxReflect")},csstransforms:function(){return r("transform")},csstransitions:function(){return r("transition")},touch:function(){return"ontouchstart"in n},retina:function(){return n.devicePixelRatio>1},fontface:function(){var t=u.browser.name,n=u.browser.version;switch(t){case"ie":return n>=9;case"chrome":return n>=13;case"ff":return n>=6;case"ios":return n>=5;case"android":return!1;case"webkit":return n>=5.1;case"opera":return n>=10;default:return!1}}};for(var e in f)f[e]&&u.feature(e,f[e].call(),!0);u.feature()})(window); 5 | /*! head.load - v1.0.3 */ 6 | (function(n,t){"use strict";function w(){}function u(n,t){if(n){typeof n=="object"&&(n=[].slice.call(n));for(var i=0,r=n.length;iu;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(j.has(n,a)&&t.call(e,n[a],a,n)===r)return};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var E="Reduce of empty array with no initial value";j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduce===v)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(E);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduceRight===h)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(E);return r},j.find=j.detect=function(n,t,r){var e;return O(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:d&&n.filter===d?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:g&&n.every===g?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var O=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:O(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,function(n){return n[t]})},j.where=function(n,t,r){return j.isEmpty(t)?r?void 0:[]:j[r?"find":"filter"](n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},j.findWhere=function(n,t){return j.where(n,t,!0)},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);if(!t&&j.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>e.computed&&(e={value:n,computed:a})}),e.value},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);if(!t&&j.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;ae||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.indexi;){var o=i+a>>>1;r.call(e,n[o])=0})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){for(var n=j.max(j.pluck(arguments,"length").concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,""+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var M=function(){};j.bind=function(n,t){var r,e;if(w&&n.bind===w)return w.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));M.prototype=n.prototype;var u=new M;M.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){return n.apply(this,t.concat(o.call(arguments)))}},j.bindAll=function(n){var t=o.call(arguments,1);if(0===t.length)throw new Error("bindAll must be passed function names");return A(t,function(t){n[t]=j.bind(n[t],n)}),n},j.memoize=function(n,t){var r={};return t||(t=j.identity),function(){var e=t.apply(this,arguments);return j.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},j.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},j.defer=function(n){return j.delay.apply(j,[n,1].concat(o.call(arguments,1)))},j.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var c=function(){o=r.leading===!1?0:new Date,a=null,i=n.apply(e,u)};return function(){var l=new Date;o||r.leading!==!1||(o=l);var f=t-(l-o);return e=this,u=arguments,0>=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u)):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u=null;return function(){var i=this,a=arguments,o=function(){u=null,r||(e=n.apply(i,a))},c=r&&!u;return clearTimeout(u),u=setTimeout(o,t),c&&(e=n.apply(i,a)),e}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},j.keys=_||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){var t=[];for(var r in n)j.has(n,r)&&t.push(n[r]);return t},j.pairs=function(n){var t=[];for(var r in n)j.has(n,r)&&t.push([r,n[r]]);return t},j.invert=function(n){var t={};for(var r in n)j.has(n,r)&&(t[n[r]]=r);return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==String(t);case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o))return!1;r.push(n),e.push(t);var c=0,f=!0;if("[object Array]"==u){if(c=n.length,f=c==t.length)for(;c--&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c--)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return"[object Array]"==l.call(n)},j.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){j["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,"callee"))}),"function"!=typeof/./&&(j.isFunction=function(n){return"function"==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))};var I={escape:{"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"}};I.unescape=j.invert(I.escape);var T={escape:new RegExp("["+j.keys(I.escape).join("")+"]","g"),unescape:new RegExp("("+j.keys(I.unescape).join("|")+")","g")};j.each(["escape","unescape"],function(n){j[n]=function(t){return null==t?"":(""+t).replace(T[n],function(t){return I[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+"";return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(D,function(n){return"\\"+B[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=new Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},j.chain=function(n){return j(n).chain()};var z=function(n){return this._chain?j(n).chain():n};j.mixin(j),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];j.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}.call(this); 6 | //# sourceMappingURL=underscore-min.map -------------------------------------------------------------------------------- /_site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "freeboard", 3 | "title": "freeboard", 4 | "version": "1.1.3", 5 | "devDependencies": { 6 | "grunt": "0.4.1", 7 | "grunt-contrib-concat": "0.1.3", 8 | "grunt-contrib-cssmin": "0.6.1", 9 | "grunt-contrib-watch": "0.5.3", 10 | "grunt-contrib-uglify": "0.6.0", 11 | "grunt-string-replace": "^0.2.7" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /_site/plugins/thirdparty/raphael.2.1.0.min.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elasticfence/freeboard-elasticsearch/ba020fa0a3d8b71690713e1fcdf2366ce3628419/_site/plugins/thirdparty/raphael.2.1.0.min.js -------------------------------------------------------------------------------- /_site/plugins/thirdparty/widget.tables.js: -------------------------------------------------------------------------------- 1 | (function() 2 | { 3 | //Setting white-space to normal to override gridster's inherited value 4 | freeboard.addStyle('table.list-table', "width: 100%; white-space: normal !important; "); 5 | freeboard.addStyle('table.list-table td, table.list-table th', "padding: 2px 2px 2px 2px; vertical-align: top; "); 6 | 7 | var tableWidget = function (settings) { 8 | var self = this; 9 | var titleElement = $('

    '); 10 | var stateElement = $('
    '); 11 | var currentSettings = settings; 12 | //store our calculated values in an object 13 | var stateObject = {}; 14 | 15 | function updateState() { 16 | stateElement.find('thead').empty(); 17 | stateElement.find('tbody').remove(); 18 | var bodyHTML = $(''); 19 | var classObject = {}; 20 | var classCounter = 0; 21 | 22 | var replaceValue = (_.isUndefined(currentSettings.replace_value) ? '' : currentSettings.replace_value); 23 | 24 | // This function handles arrays and objects 25 | function eachRecursive(obj) 26 | { 27 | for (var k in obj) 28 | { 29 | if (typeof obj[k] == "object" && obj[k] !== null) 30 | eachRecursive(obj[k]); 31 | else 32 | var tr = ""; 33 | tr += "" + k + "" + "" + obj[k] + ""; 34 | tbody.innerHTML += tr; 35 | } 36 | } 37 | 38 | 39 | // console.log('object:', stateObject); 40 | if (stateObject.value) { 41 | var tabledata = { value: {} }; 42 | tabledata.value.data = [stateObject.value]; 43 | tabledata.value.header = []; 44 | for(var k in stateObject.value) tabledata['value']['header'].unshift(k); 45 | // console.log(tabledata); 46 | } 47 | 48 | //only proceed if we have a valid JSON object 49 | if (tabledata && tabledata.value && tabledata.value.header && tabledata.value.data) { 50 | 51 | var headerRow = $(''); 52 | var templateRow = $(''); 53 | var rowHTML; 54 | 55 | //Loop through the 'header' array, building up our header row and also a template row 56 | try { 57 | $.each(tabledata.value.header, function(headerKey, headerName){ 58 | classObject[headerName] = 'td-' + classCounter; 59 | headerRow.append($('').html(headerName)); 60 | templateRow.append($('').addClass('td-' + classCounter).html(replaceValue)); 61 | classCounter++; 62 | }) 63 | } catch (e) { 64 | console.log(e); 65 | } 66 | 67 | //Loop through each 'data' object, cloning the templateRow and using the class to set the value in the correct 68 | try { 69 | $.each(tabledata.value.data, function(k, v){ 70 | rowHTML = templateRow.clone(); 71 | $.each(v, function(dataKey, dataValue){ 72 | if(dataKey.toLowerCase().indexOf("byte") !=-1) { 73 | var i = Math.floor( Math.log(dataValue) / Math.log(1024) ); 74 | dataValue = ( dataValue / Math.pow(1024, i) ).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]; 75 | rowHTML.find('.' + classObject[dataKey]).html(dataValue); 76 | } else { 77 | rowHTML.find('.' + classObject[dataKey]).html(dataValue); 78 | } 79 | 80 | }) 81 | bodyHTML.append(rowHTML); 82 | }) 83 | } catch (e) { 84 | console.log(e) 85 | } 86 | 87 | //Append the header and body 88 | stateElement.find('thead').append(headerRow); 89 | stateElement.find('table').append(bodyHTML); 90 | 91 | //show or hide the header based on the setting 92 | if (currentSettings.show_header) { 93 | stateElement.find('thead').show(); 94 | } else { 95 | stateElement.find('thead').hide(); 96 | } 97 | } 98 | } 99 | 100 | this.render = function (element) { 101 | $(element).append(titleElement).append(stateElement); 102 | } 103 | 104 | this.onSettingsChanged = function (newSettings) { 105 | currentSettings = newSettings; 106 | titleElement.html((_.isUndefined(newSettings.title) ? "" : newSettings.title)); 107 | updateState(); 108 | } 109 | 110 | this.onCalculatedValueChanged = function (settingName, newValue) { 111 | //whenver a calculated value changes, stored them in the variable 'stateObject' 112 | stateObject[settingName] = newValue; 113 | updateState(); 114 | } 115 | 116 | this.onDispose = function () { 117 | } 118 | 119 | this.getHeight = function () { 120 | var height = Math.ceil(stateElement.height() / 55); 121 | return (height > 0 ? height : 1); 122 | } 123 | 124 | this.onSettingsChanged(settings); 125 | }; 126 | 127 | freeboard.loadWidgetPlugin({ 128 | type_name: "list", 129 | display_name: "Table", 130 | settings: [ 131 | { 132 | name: "title", 133 | display_name: "Title", 134 | type: "text" 135 | }, 136 | { 137 | name: "show_header", 138 | display_name: "Show Headers", 139 | default_value: true, 140 | type: "boolean" 141 | }, 142 | { 143 | name: "replace_value", 144 | display_name: "Replace blank values", 145 | type: "text" 146 | }, 147 | { 148 | name: "value", 149 | display_name: "Value", 150 | type: "calculated" 151 | } 152 | ], 153 | newInstance: function (settings, newInstanceCallback) { 154 | newInstanceCallback(new tableWidget(settings)); 155 | } 156 | }); 157 | }()); 158 | -------------------------------------------------------------------------------- /_site/test/casper/tests/smoke_test.js: -------------------------------------------------------------------------------- 1 | var util = require('../util/test_util.js'); 2 | 3 | casper.options.viewportSize = {width: 1024, height: 768} 4 | casper.options.onError = function() { 5 | casper.capture("error_screenshot.png"); 6 | }; 7 | 8 | casper.test.begin('Freeboard smoke test', function testFunction(test) 9 | { 10 | casper.start(util.FREEBOARD_URL, function() 11 | { 12 | // Test initial page load state 13 | test.assertTitle('freeboard'); 14 | test.assertVisible(util.SELECTORS.adminBar); 15 | test.assertNotVisible(util.SELECTORS.modalOverlay); 16 | }); 17 | 18 | util.addJSONDatasource(test, 'json_input', 'test/fixtures/input.json'); 19 | 20 | casper.then(function() { 21 | // Click add pane 22 | test.assertNotVisible(util.SELECTORS.allPanes); 23 | test.assertVisible(util.SELECTORS.boardTools); 24 | this.click(util.SELECTORS.addPane); 25 | test.assertVisible(util.SELECTORS.allPanes); 26 | 27 | // Click add widget 28 | this.click(util.SELECTORS.addWidget); 29 | test.assertVisible(util.SELECTORS.pluginTypeDropdown); 30 | 31 | // Select text widget 32 | util.setValueAndTriggerChange(test, util.SELECTORS.pluginTypeDropdown, 'text_widget'); 33 | 34 | // Click datasource autofill 35 | test.assertVisible(util.SELECTORS.valueValueInput); 36 | test.assertVisible(util.SELECTORS.autofillDatasource); 37 | this.click(util.SELECTORS.autofillDatasource); 38 | test.assertVisible(util.SELECTORS.autofillMenu); 39 | var valueName = this.fetchText(util.getAutofillMenuItemSelector(1)); 40 | test.assertEquals(valueName, 'json_input'); 41 | 42 | // Select the first (and only) datasource 43 | this.mouseEvent('mousedown', util.getAutofillMenuItemSelector(1)); 44 | test.assertVisible(util.SELECTORS.autofillMenu); 45 | 46 | // Select the "meta" sub-object of the datasource 47 | valueName = this.fetchText(util.getAutofillMenuItemSelector(4)); 48 | test.assertEquals(valueName, 'meta'); 49 | this.mouseEvent('mousedown', util.getAutofillMenuItemSelector(4)); 50 | test.assertVisible(util.SELECTORS.autofillMenu); 51 | 52 | // Select the "year" field to be displayed 53 | valueName = this.fetchText(util.getAutofillMenuItemSelector(5)); 54 | test.assertEquals(valueName, 'year'); 55 | this.mouseEvent('mousedown', util.getAutofillMenuItemSelector(5)); 56 | test.assertNotVisible(util.SELECTORS.autofillMenu); 57 | }); 58 | 59 | // Click save 60 | util.clickModalOK(test); 61 | 62 | casper.then(function() { 63 | // Assert that the new text widget displays the correct value 64 | test.assertVisible(util.SELECTORS.singlePaneTextWidget); 65 | var textValue = this.fetchText(util.SELECTORS.singlePaneTextWidget); 66 | test.assertEquals(textValue, '2018'); 67 | 68 | // Click and confirm delete widget 69 | this.click(util.SELECTORS.trashWidget); 70 | }); 71 | 72 | util.clickModalOK(test); 73 | 74 | casper.then(function() { 75 | // Assert widget deleted, pane not deleted 76 | test.assertNotVisible(util.SELECTORS.allWidgets); 77 | test.assertVisible(util.SELECTORS.allPanes); 78 | 79 | // Click and confirm delete pane 80 | this.click(util.SELECTORS.trashPane); 81 | }); 82 | 83 | util.clickModalOK(test); 84 | 85 | casper.then(function() { 86 | // Confirm pane deleted 87 | test.assertNotVisible(util.SELECTORS.allPanes); 88 | 89 | // Click and confirm delete datasource 90 | test.assertVisible(util.SELECTORS.datasourceTable); 91 | this.click(util.SELECTORS.trashDatasource); 92 | }); 93 | 94 | util.clickModalOK(test); 95 | 96 | casper.then(function() { 97 | // Confirm datasource deleted 98 | test.assertNotVisible(util.SELECTORS.datasourceTable); 99 | }); 100 | 101 | casper.run(function() { 102 | this.reload(); 103 | test.done(); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /_site/test/casper/util/test_util.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | exports.FREEBOARD_URL = fs.workingDirectory + '/index.html'; 4 | 5 | exports.BOGUS = 'json_input'; 6 | 7 | var SELECTORS = { 8 | adminBar : '#admin-bar', 9 | modelOverlay : '#modal_overlay', 10 | addDatasource : '#datasources span.text-button', 11 | pluginTypeDropdown : '#modal_overlay #setting-value-container-plugin-types select', 12 | nameValueInput : '#modal_overlay #setting-value-container-name input', 13 | urlValueInput : '#modal_overlay #setting-value-container-url input', 14 | valueValueInput : '#modal_overlay #setting-value-container-value textarea', 15 | modalOK : '#modal_overlay #dialog-ok', 16 | modalValidationError : '#modal_overlay validation-error', 17 | datasourceTable : '#datasources table#datasources-list', 18 | datasourceName : '#datasources table#datasources-list span.datasource-name', 19 | datasoureUpdated : '#datasources table#datasources-list tr td:nth-child(2)', 20 | allPanes : '#board-content li', 21 | allWidgets : '#board-content li section', 22 | boardTools : '#board-tools', 23 | addPane : '#board-tools #add-pane', 24 | addWidget : '#board-content li header li i.icon-plus', 25 | autofillDatasource : '#modal_overlay #setting-value-container-value li i.icon-plus', 26 | autofillMenu : '#modal_overlay #value-selector', 27 | autofillMenuItem : '#modal_overlay #value-selector li:nth-child(%s)', 28 | singlePaneTextWidget : '#board-content li section div.tw-value', 29 | trashWidget : '#board-content li section i.icon-trash', 30 | trashPane : '#board-content li header i.icon-trash', 31 | trashDatasource : '#datasources table#datasources-list i.icon-trash' 32 | }; 33 | exports.SELECTORS = SELECTORS; 34 | 35 | function setValueAndTriggerChange(test, selector, value) { 36 | test.assertVisible(selector); 37 | casper.evaluate(function(selector, value) { 38 | $(selector).val(value).change(); 39 | }, selector, value); 40 | } 41 | exports.setValueAndTriggerChange = setValueAndTriggerChange; 42 | 43 | function clickModalOK(test) { 44 | casper.then(function() { 45 | test.assertVisible(SELECTORS.modalOK); 46 | casper.click(SELECTORS.modalOK); 47 | test.assertNotVisible(SELECTORS.modalValidationError); 48 | }); 49 | casper.waitWhileVisible('#modal_overlay'); 50 | } 51 | exports.clickModalOK = clickModalOK; 52 | 53 | exports.getAutofillMenuItemSelector = function(index) { 54 | return SELECTORS.autofillMenuItem.replace('%s', index); 55 | } 56 | 57 | exports.addJSONDatasource = function(test, name, url) { 58 | 59 | casper.then(function() { 60 | // Click Add datasource 61 | casper.click(SELECTORS.addDatasource); 62 | 63 | // Select JSON 64 | setValueAndTriggerChange(test, SELECTORS.pluginTypeDropdown, 'JSON'); 65 | 66 | // Name the datasource "json_input" 67 | setValueAndTriggerChange(test, SELECTORS.nameValueInput, name); 68 | 69 | // Specify fixtures/input.json as the source 70 | setValueAndTriggerChange(test, SELECTORS.urlValueInput, url); 71 | }); 72 | 73 | // Click ok 74 | clickModalOK(test); 75 | 76 | casper.then(function() { 77 | // Assert that the datasource displays correctly 78 | test.assertVisible(SELECTORS.datasourceName); 79 | var datasourceName = casper.fetchText(SELECTORS.datasourceName); 80 | test.assertEquals(datasourceName, name); 81 | var datasourceTime = casper.fetchText(SELECTORS.datasourceUpdated); 82 | test.assertNotEquals(datasourceTime, 'never'); 83 | }); 84 | }; 85 | -------------------------------------------------------------------------------- /_site/test/fixtures/input.json: -------------------------------------------------------------------------------- 1 | { 2 | "x" : 42, 3 | "y" : 19, 4 | "z" : 313, 5 | "meta" : { 6 | "owner" : 7 | { 8 | "first_name" : "Alfonso", 9 | "last_name" : "Jones", 10 | "maiden_name" : "Smith" 11 | }, 12 | "country" : "USA", 13 | "make" : "BMW", 14 | "model" : "XL", 15 | "year" : "2018" 16 | }, 17 | "entries" : ["2000", "2003", "2004", "2006", "2011", "2014"], 18 | "previous_owners" : [ 19 | { 20 | "owner" : "Zelnit", 21 | "country" : "Russia" 22 | }, 23 | { 24 | "owner" : "Yolga", 25 | "country" : "Venezuela" 26 | }, 27 | { 28 | "owner" : "Xavier", 29 | "country" : "Poland" 30 | }, 31 | { 32 | "owner" : "Voltron", 33 | "country" : "Greenland" 34 | }, 35 | { 36 | "owner" : "Uther", 37 | "country" : "Cambodia" 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /_site/test/run_browser_tests.sh: -------------------------------------------------------------------------------- 1 | cd "$( dirname "$0" )" 2 | cd .. 3 | casperjs test test/casper/tests/ 4 | -------------------------------------------------------------------------------- /plugin-descriptor.properties: -------------------------------------------------------------------------------- 1 | description=Freeboard dashboards for Elasticsearch 2 | version=1.0 3 | site=true 4 | name=freeboard-elasticsearch 5 | --------------------------------------------------------------------------------