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 |
106 |
107 |
108 |
109 |
156 |
157 |
167 |
170 |
171 |
172 |
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 |
46 |
47 |
48 |
49 |
96 |
97 |
107 |
109 |
110 |
111 |
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('");
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 = $('');
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 |
--------------------------------------------------------------------------------