├── .editorconfig
├── .gitattributes
├── .gitignore
├── .jshintrc
├── .travis.yml
├── CONTRIBUTING.md
├── Gruntfile.js
├── LICENSE
├── README.md
├── bower.json
├── demo
├── index.html
├── scripts
│ └── demo.js
├── styles
│ └── demo.css
└── views
│ └── view.html
├── dist
└── malhar-angular-widgets.js
├── docs
└── AngularJSDashboard.png
├── karma-e2e.conf.js
├── karma.conf.js
├── package.json
├── src
├── models
│ ├── random.js
│ └── websocket.js
├── modules.js
├── service
│ ├── visibility.js
│ └── websocket.js
├── vendor
│ ├── gauge_vendor.js
│ └── visibly.js
└── widgets
│ ├── barChart
│ └── barChart.js
│ ├── gauge
│ └── gauge.js
│ ├── historicalChart
│ └── historicalChart.js
│ ├── lineChart
│ └── lineChart.js
│ ├── metricsChart
│ └── metricsChart.js
│ ├── nvd3LineChart
│ └── nvd3LineChart.js
│ ├── pieChart
│ └── pieChart.js
│ ├── random
│ └── random.js
│ ├── scopeWatch
│ └── scopeWatch.js
│ ├── select
│ └── select.js
│ ├── time
│ └── time.js
│ └── topN
│ └── topN.js
├── template
└── widgets
│ ├── barChart
│ └── barChart.html
│ ├── historicalChart
│ └── historicalChart.html
│ ├── metricsChart
│ └── metricsChart.html
│ ├── nvd3LineChart
│ └── nvd3LineChart.html
│ ├── pieChart
│ └── pieChart.html
│ ├── random
│ └── random.html
│ ├── scopeWatch
│ └── scopeWatch.html
│ ├── select
│ └── select.html
│ ├── time
│ └── time.html
│ └── topN
│ └── topN.html
└── test
├── .jshintrc
├── runner.html
└── spec
├── webSocket_test.js
└── widgets
└── main.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 |
10 | # Change these settings to your own preference
11 | indent_style = space
12 | indent_size = 2
13 |
14 | # We recommend you to keep these unchanged
15 | end_of_line = lf
16 | charset = utf-8
17 | trim_trailing_whitespace = true
18 | insert_final_newline = true
19 |
20 | [*.md]
21 | trim_trailing_whitespace = false
22 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .tmp
3 | .sass-cache
4 | .idea
5 | .DS_Store
6 | bower_components
7 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "browser": true,
4 | "esnext": true,
5 | "bitwise": true,
6 | "camelcase": true,
7 | "curly": true,
8 | "eqeqeq": true,
9 | "immed": true,
10 | "indent": 2,
11 | "latedef": true,
12 | "newcap": true,
13 | "noarg": true,
14 | "quotmark": "single",
15 | "regexp": true,
16 | "undef": true,
17 | "unused": true,
18 | "strict": true,
19 | "trailing": true,
20 | "smarttabs": true,
21 | "globals": {
22 | "angular": false,
23 | "jQuery": false,
24 | "_": false,
25 | "d3": false,
26 | "google": false,
27 | "Gauge": false
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '0.10'
4 | before_script:
5 | - 'npm install -g bower grunt-cli'
6 | - 'bower install'
7 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributing Guidelines
2 | =======================
3 | This project welcomes new contributors.
4 |
5 | Licensing
6 | ---------
7 | You acknowledge that your submissions to DataTorrent on this repository are made pursuant the terms of the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.html) and constitute "Contributions," as defined therein, and you represent and warrant that you have the right and authority to do so.
8 |
9 | When adding **new javascript files**, please include the following Apache v2.0 license header at the top of the file, with the fields enclosed by brackets "[]" replaced with your own identifying information. **(Don't include the brackets!)**:
10 |
11 | ```JavaScript
12 | /*
13 | * Copyright (c) [XXXX] [NAME OF COPYRIGHT OWNER]
14 | *
15 | * Licensed under the Apache License, Version 2.0 (the "License");
16 | * you may not use this file except in compliance with the License.
17 | * You may obtain a copy of the License at
18 | *
19 | * http://www.apache.org/licenses/LICENSE-2.0
20 | *
21 | * Unless required by applicable law or agreed to in writing, software
22 | * distributed under the License is distributed on an "AS IS" BASIS,
23 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24 | * See the License for the specific language governing permissions and
25 | * limitations under the License.
26 | */
27 | ```
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function (grunt) {
4 | require('load-grunt-tasks')(grunt);
5 | require('time-grunt')(grunt);
6 |
7 | grunt.initConfig({
8 | ngtemplates: {
9 | dashboard: {
10 | options: {
11 | module: 'ui.widgets'
12 | },
13 | src: 'template/widgets/{,*/}*.html',
14 | dest: 'template/templates.js'
15 | }
16 | },
17 | karma: {
18 | unit: {
19 | configFile: 'karma.conf.js',
20 | singleRun: true
21 | }
22 | },
23 | concat: {
24 | dist: {
25 | src: [
26 | 'src/modules.js',
27 | 'src/**/*.js',
28 | 'template/templates.js'
29 | ],
30 | dest: 'dist/malhar-angular-widgets.js'
31 | }
32 | },
33 | watch: {
34 | files: [
35 | 'src/**/*.js',
36 | 'template/widgets/{,*/}*.html'
37 | ],
38 | tasks: ['ngtemplates', 'concat'],
39 | livereload: {
40 | options: {
41 | livereload: '<%= connect.options.livereload %>'
42 | },
43 | files: [
44 | 'demo/{,*/}*.html',
45 | 'demo/{,*/}*.css',
46 | 'demo/{,*/}*.js',
47 | 'dist/*.js'
48 | ]
49 | }
50 | },
51 | jshint: {
52 | options: {
53 | jshintrc: '.jshintrc',
54 | ignores: [
55 | 'src/vendor/{,*/}*.js'
56 | ]
57 | },
58 | all: [
59 | 'Gruntfile.js',
60 | 'demo/*.js',
61 | 'src/**/*.js'
62 | ]
63 | },
64 | copy: {
65 | dist: {
66 | files: [{
67 | expand: true,
68 | flatten: true,
69 | src: ['src/angular-ui-dashboard.css'],
70 | dest: 'dist'
71 | }]
72 | }
73 | },
74 | clean: {
75 | dist: {
76 | files: [{
77 | src: [
78 | 'dist/*'
79 | ]
80 | }]
81 | },
82 | templates: {
83 | src: ['<%= ngtemplates.dashboard.dest %>']
84 | }
85 | },
86 | connect: {
87 | options: {
88 | port: 9001,
89 | // Change this to '0.0.0.0' to access the server from outside.
90 | hostname: 'localhost',
91 | livereload: 35739
92 | },
93 | livereload: {
94 | options: {
95 | open: true,
96 | base: [
97 | '.',
98 | 'demo'
99 | ]
100 | }
101 | }
102 | }
103 | });
104 |
105 | grunt.registerTask('test', [
106 | 'ngtemplates',
107 | 'karma'
108 | ]);
109 |
110 | grunt.registerTask('demo', [
111 | 'connect:livereload',
112 | 'watch'
113 | ]);
114 |
115 | grunt.registerTask('default', [
116 | 'clean:dist',
117 | 'jshint',
118 | 'ngtemplates',
119 | 'karma',
120 | 'concat',
121 | 'clean:templates'
122 | ]);
123 | };
124 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2014 DataTorrent, Inc.
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | angular-widgets
2 | =================
3 |
4 | [](https://travis-ci.org/DataTorrent/malhar-angular-widgets)
5 |
6 | Widget (Line Chart, Bar Chart, Top N and more).
7 |
8 | [Online Demo](http://datatorrent.github.io/malhar-dashboard-webapp/#/) using the library [[source code](https://github.com/DataTorrent/malhar-dashboard-webapp)]
9 |
10 | 
11 |
12 | ## Running Demo Application
13 | Install dependencies:
14 |
15 | ``` bash
16 | $ npm install
17 | ```
18 |
19 | Install Bower dependencies:
20 |
21 | ``` bash
22 | $ bower install
23 | ```
24 |
25 | Run demo server:
26 |
27 | ``` bash
28 | $ grunt demo
29 | ```
30 |
31 | Application will be available at http://localhost:9001
32 |
33 | ## Building Application
34 |
35 | Application is built with Grunt.
36 |
37 | ``` bash
38 | $ npm install -g grunt-cli
39 | $ grunt
40 | ```
41 |
42 | ## Contributing
43 |
44 | This project welcomes new contributors.
45 |
46 | You acknowledge that your submissions to DataTorrent on this repository are made pursuant the terms of the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.html) and constitute "Contributions," as defined therein, and you represent and warrant that you have the right and authority to do so.
47 |
48 | When **adding new javascript files**, please prepend the Apache v2.0 license header, which can be found in [CONTRIBUTING.md file](https://github.com/DataTorrent/malhar-angular-widgets/blob/master/CONTRIBUTING.md).
49 |
50 | ## Links
51 |
52 | [malhar-angular-dashboard](https://github.com/DataTorrent/malhar-angular-dashboard) AngularJS Dashboard directive.
53 |
54 | [malhar-dashboard-webapp](https://github.com/DataTorrent/malhar-dashboard-webapp) Demo using dashboard and widget library
55 |
56 | [Node.js](http://nodejs.org/) Software platform built on JavaScript runtime
57 |
58 | [AngularJS](http://angularjs.org/) JavaScript framework
59 |
60 | [ui-sortable](https://github.com/angular-ui/ui-sortable) AngularJS UI Sortable
61 |
62 | [Bower](http://bower.io/) Package manager for the web
63 |
64 | [Grunt](http://gruntjs.com/) JavaScript Task Runner
65 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "malhar-angular-widgets",
3 | "version": "0.2.0",
4 | "license": "Apache License, v2.0",
5 | "dependencies": {
6 | "angular": "~1.3",
7 | "jquery": "~2.0.3",
8 | "angular-sanitize": "~1.3",
9 | "bootstrap": "~3.0.3",
10 | "angular-bootstrap": "0.11.0",
11 | "angular-route": "~1.3",
12 | "malhar-angular-table": ">=1.2.0",
13 | "d3": "3.4.4",
14 | "nvd3": "~1.1.15-beta",
15 | "angularjs-nvd3-directives": "~0.0.7",
16 | "malhar-angular-dashboard": "https://github.com/DataTorrent/malhar-angular-dashboard.git#master",
17 | "visibilityjs": "~1.2.1",
18 | "pines-notify": "~2.0.1",
19 | "angular-pines-notify": "~1.0.0"
20 | },
21 | "devDependencies": {
22 | "angular-mocks": "~1.3",
23 | "angular-scenario": "~1.3"
24 | },
25 | "resolutions": {
26 | "jquery": "~2.0.3",
27 | "d3": "3.4.4",
28 | "angular": ">=1.3",
29 | "angular-bootstrap": "0.12.x",
30 | "bootstrap": "~3.3.1"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/demo/scripts/demo.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | angular.module('app', [
20 | 'ngRoute',
21 | 'ui.dashboard',
22 | 'ui.widgets',
23 | 'ui.models'
24 | ])
25 | .config(function ($routeProvider) {
26 | $routeProvider
27 | .when('/', {
28 | templateUrl: 'views/view.html',
29 | controller: 'DemoCtrl'
30 | })
31 | .otherwise({
32 | redirectTo: '/'
33 | });
34 | })
35 | .controller('DemoCtrl', function ($scope, $interval, RandomTopNDataModel, RandomTimeSeriesDataModel,
36 | RandomMinutesDataModel, RandomNVD3TimeSeriesDataModel, RandomMetricsTimeSeriesDataModel) {
37 | var widgetDefinitions = [
38 | {
39 | name: 'time',
40 | directive: 'wt-time',
41 | style: {
42 | width: '33%'
43 | }
44 | },
45 | {
46 | name: 'random',
47 | directive: 'wt-random',
48 | style: {
49 | width: '33%'
50 | }
51 | },
52 | {
53 | name: 'scope-watch',
54 | directive: 'wt-scope-watch',
55 | attrs: {
56 | value: 'randomValue'
57 | },
58 | style: {
59 | width: '34%'
60 | }
61 | },
62 | {
63 | name: 'Metrics Chart',
64 | directive: 'wt-metrics-chart',
65 | dataAttrName: 'data',
66 | dataModelType: RandomMetricsTimeSeriesDataModel,
67 | style: {
68 | width: '50%'
69 | }
70 | },
71 | {
72 | name: 'NVD3 Line Chart',
73 | directive: 'wt-nvd3-line-chart',
74 | dataAttrName: 'data',
75 | dataModelType: RandomNVD3TimeSeriesDataModel,
76 | dataModelArgs: {
77 | rate: 40
78 | },
79 | attrs: {
80 | 'show-time-range': true
81 | },
82 | style: {
83 | width: '50%'
84 | }
85 | },
86 | {
87 | name: 'Line Chart',
88 | directive: 'wt-line-chart',
89 | dataAttrName: 'chart',
90 | dataModelType: RandomTimeSeriesDataModel,
91 | style: {
92 | width: '50%'
93 | }
94 | },
95 | {
96 | name: 'Bar Chart',
97 | directive: 'wt-bar-chart',
98 | dataAttrName: 'data',
99 | dataModelType: RandomMinutesDataModel,
100 | dataModelArgs: {
101 | limit: 1000
102 | },
103 | style: {
104 | width: '50%'
105 | }
106 | },
107 | {
108 | name: 'topN',
109 | directive: 'wt-top-n',
110 | dataAttrName: 'data',
111 | dataModelType: RandomTopNDataModel
112 | },
113 | {
114 | name: 'gauge',
115 | directive: 'wt-gauge',
116 | attrs: {
117 | value: 'percentage'
118 | },
119 | style: {
120 | width: '250px'
121 | }
122 | },
123 | ];
124 |
125 | var defaultWidgets = [
126 | { name: 'time' },
127 | { name: 'random' },
128 | { name: 'scope-watch' },
129 | { name: 'Metrics Chart' },
130 | { name: 'NVD3 Line Chart' },
131 | { name: 'Line Chart' },
132 | { name: 'Bar Chart' },
133 | { name: 'topN' },
134 | { name: 'gauge' }
135 | ];
136 |
137 | $scope.dashboardOptions = {
138 | widgetButtons: true,
139 | widgetDefinitions: widgetDefinitions,
140 | defaultWidgets: defaultWidgets
141 | };
142 |
143 | // random scope value (scope-watch widget)
144 | $interval(function () {
145 | $scope.randomValue = Math.random();
146 | }, 500);
147 |
148 | // percentage (gauge widget, progressbar widget)
149 | $scope.percentage = 5;
150 | $interval(function () {
151 | $scope.percentage = ($scope.percentage + 10) % 100;
152 | }, 1000);
153 |
154 | // line chart widget
155 | $interval(function () {
156 | $scope.topN = _.map(_.range(0, 10), function (index) {
157 | return {
158 | name: 'item' + index,
159 | value: Math.floor(Math.random() * 100)
160 | };
161 | });
162 | }, 500);
163 | });
164 |
165 |
--------------------------------------------------------------------------------
/demo/styles/demo.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 15px;
3 | }
4 |
5 | .top-n .grid {
6 | height: 350px;
7 | border: 1px solid rgb(212, 212, 212);
8 | }
9 |
10 | .line-chart, .bar-chart {
11 | height: 220px
12 | }
--------------------------------------------------------------------------------
/demo/views/view.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dist/malhar-angular-widgets.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | angular.module('ui.widgets', ['datatorrent.mlhrTable', 'nvd3ChartDirectives']);
18 | angular.module('ui.websocket', ['ui.visibility', 'ui.notify']);
19 | angular.module('ui.models', ['ui.visibility', 'ui.websocket']);
20 |
21 | /*
22 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
23 | *
24 | * Licensed under the Apache License, Version 2.0 (the "License");
25 | * you may not use this file except in compliance with the License.
26 | * You may obtain a copy of the License at
27 | *
28 | * http://www.apache.org/licenses/LICENSE-2.0
29 | *
30 | * Unless required by applicable law or agreed to in writing, software
31 | * distributed under the License is distributed on an "AS IS" BASIS,
32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
33 | * See the License for the specific language governing permissions and
34 | * limitations under the License.
35 | */
36 |
37 | 'use strict';
38 |
39 | angular.module('ui.models')
40 | .factory('RandomBaseDataModel', function (WidgetDataModel, Visibility) {
41 | function RandomBaseDataModel() {
42 | }
43 |
44 | RandomBaseDataModel.prototype = Object.create(WidgetDataModel.prototype);
45 | RandomBaseDataModel.prototype.constructor = WidgetDataModel;
46 |
47 | angular.extend(RandomBaseDataModel.prototype, {
48 | init: function () {
49 | this.stopUpdates = false;
50 | this.visibilityListener = Visibility.change(function (e, state) {
51 | if (state === 'hidden') {
52 | this.stopUpdates = true;
53 | } else {
54 | this.stopUpdates = false;
55 | }
56 | }.bind(this));
57 | },
58 |
59 | updateScope: function (data) {
60 | if (!this.stopUpdates) {
61 | WidgetDataModel.prototype.updateScope.call(this, data);
62 | }
63 | },
64 |
65 | destroy: function () {
66 | WidgetDataModel.prototype.destroy.call(this);
67 | Visibility.unbind(this.visibilityListener);
68 | }
69 | });
70 |
71 | return RandomBaseDataModel;
72 | })
73 | .factory('RandomPercentageDataModel', function (RandomBaseDataModel, $interval) {
74 | function RandomPercentageDataModel() {
75 | }
76 |
77 | RandomPercentageDataModel.prototype = Object.create(RandomBaseDataModel.prototype);
78 |
79 | RandomPercentageDataModel.prototype.init = function () {
80 | var value = 50;
81 |
82 | this.intervalPromise = $interval(function () {
83 | value += Math.random() * 40 - 20;
84 | value = value < 0 ? 0 : value > 100 ? 100 : value;
85 |
86 | this.updateScope(value);
87 | }.bind(this), 500);
88 | };
89 |
90 | RandomPercentageDataModel.prototype.destroy = function () {
91 | RandomBaseDataModel.prototype.destroy.call(this);
92 | $interval.cancel(this.intervalPromise);
93 | };
94 |
95 | return RandomPercentageDataModel;
96 | })
97 | .factory('RandomTopNDataModel', function (RandomBaseDataModel, $interval) {
98 | function RandomTopNDataModel() {
99 | }
100 |
101 | RandomTopNDataModel.prototype = Object.create(RandomBaseDataModel.prototype);
102 |
103 | RandomTopNDataModel.prototype.init = function () {
104 | this.intervalPromise = $interval(function () {
105 | var topTen = _.map(_.range(0, 10), function (index) {
106 | return {
107 | name: 'item' + index,
108 | value: Math.floor(Math.random() * 100)
109 | };
110 | });
111 | this.updateScope(topTen);
112 | }.bind(this), 500);
113 | };
114 |
115 | RandomTopNDataModel.prototype.destroy = function () {
116 | RandomBaseDataModel.prototype.destroy.call(this);
117 | $interval.cancel(this.intervalPromise);
118 | };
119 |
120 | return RandomTopNDataModel;
121 | })
122 | .factory('RandomBaseTimeSeriesDataModel', function (RandomBaseDataModel, $interval) {
123 | function RandomTimeSeriesDataModel(options) {
124 | this.upperBound = (options && options.upperBound) ? options.upperBound : 100;
125 | this.rate = (options && options.rate) ? options.rate : Math.round(this.upperBound / 2);
126 | }
127 |
128 | RandomTimeSeriesDataModel.prototype = Object.create(RandomBaseDataModel.prototype);
129 | RandomTimeSeriesDataModel.prototype.constructor = RandomBaseDataModel;
130 |
131 | angular.extend(RandomTimeSeriesDataModel.prototype, {
132 | init: function () {
133 | RandomBaseDataModel.prototype.init.call(this);
134 |
135 | var max = 30;
136 | var upperBound = this.upperBound;
137 | var data = [];
138 | var chartValue = Math.round(upperBound / 2);
139 | var rate = this.rate;
140 |
141 | function nextValue() {
142 | chartValue += Math.random() * rate - rate / 2;
143 | chartValue = chartValue < 0 ? 0 : chartValue > upperBound ? upperBound : chartValue;
144 | return Math.round(chartValue);
145 | }
146 |
147 | var now = Date.now();
148 | for (var i = max - 1; i >= 0; i--) {
149 | data.push({
150 | timestamp: now - i * 1000,
151 | value: nextValue()
152 | });
153 | }
154 |
155 | this.updateScope(data);
156 |
157 | this.intervalPromise = $interval(function () {
158 | if (data.length >= max) {
159 | data.shift();
160 | }
161 | data.push({
162 | timestamp: Date.now(),
163 | value: nextValue()
164 | });
165 |
166 | this.updateScope(data);
167 | }.bind(this), 1000);
168 | },
169 |
170 | destroy: function () {
171 | RandomBaseDataModel.prototype.destroy.call(this);
172 | $interval.cancel(this.intervalPromise);
173 | }
174 | });
175 |
176 | return RandomTimeSeriesDataModel;
177 | })
178 | .factory('RandomTimeSeriesDataModel', function (RandomBaseTimeSeriesDataModel) {
179 | function RandomTimeSeriesDataModel(options) {
180 | RandomBaseTimeSeriesDataModel.call(this, options);
181 | }
182 |
183 | RandomTimeSeriesDataModel.prototype = Object.create(RandomBaseTimeSeriesDataModel.prototype);
184 |
185 | angular.extend(RandomTimeSeriesDataModel.prototype, {
186 | updateScope: function (data) {
187 | var chart = {
188 | data: data,
189 | max: 30,
190 | chartOptions: {
191 | vAxis: {}
192 | }
193 | };
194 |
195 | RandomBaseTimeSeriesDataModel.prototype.updateScope.call(this, chart);
196 | }
197 | });
198 |
199 | return RandomTimeSeriesDataModel;
200 | })
201 | .factory('RandomMetricsTimeSeriesDataModel', function (RandomBaseTimeSeriesDataModel) {
202 | function RandomMetricsTimeSeriesDataModel(options) {
203 | RandomBaseTimeSeriesDataModel.call(this, options);
204 | }
205 |
206 | RandomMetricsTimeSeriesDataModel.prototype = Object.create(RandomBaseTimeSeriesDataModel.prototype);
207 |
208 | angular.extend(RandomMetricsTimeSeriesDataModel.prototype, {
209 | updateScope: function (data) {
210 | var chart = [
211 | {
212 | key: 'Stream1',
213 | values: data
214 | },
215 | {
216 | key: 'Stream2',
217 | values: _.map(data, function (item) {
218 | return { timestamp: item.timestamp, value: item.value + 10 };
219 | })
220 | }
221 | ];
222 |
223 | RandomBaseTimeSeriesDataModel.prototype.updateScope.call(this, chart);
224 | }
225 | });
226 |
227 | return RandomMetricsTimeSeriesDataModel;
228 | })
229 | .factory('RandomNVD3TimeSeriesDataModel', function (RandomBaseTimeSeriesDataModel) {
230 | function RandomTimeSeriesDataModel(options) {
231 | RandomBaseTimeSeriesDataModel.call(this, options);
232 | }
233 |
234 | RandomTimeSeriesDataModel.prototype = Object.create(RandomBaseTimeSeriesDataModel.prototype);
235 |
236 | angular.extend(RandomTimeSeriesDataModel.prototype, {
237 | updateScope: function (data) {
238 | var chart = [
239 | {
240 | key: 'Data',
241 | values: data
242 | }
243 | ];
244 |
245 | RandomBaseTimeSeriesDataModel.prototype.updateScope.call(this, chart);
246 | }
247 | });
248 |
249 | return RandomTimeSeriesDataModel;
250 | })
251 | .factory('RandomMinutesDataModel', function (RandomBaseDataModel, $interval) {
252 | function RandomTimeSeriesDataModel(options) {
253 | this.limit = (options && options.limit) ? options.limit : 500;
254 | }
255 |
256 | RandomTimeSeriesDataModel.prototype = Object.create(RandomBaseDataModel.prototype);
257 |
258 | RandomTimeSeriesDataModel.prototype.init = function () {
259 | this.generateChart();
260 | this.intervalPromise = $interval(this.generateChart.bind(this), 2000);
261 | };
262 |
263 | RandomTimeSeriesDataModel.prototype.generateChart = function () {
264 | var minuteCount = 30;
265 | var data = [];
266 | var limit = this.limit;
267 | var chartValue = limit / 2;
268 |
269 | function nextValue() {
270 | chartValue += Math.random() * (limit * 0.4) - (limit * 0.2);
271 | chartValue = chartValue < 0 ? 0 : chartValue > limit ? limit : chartValue;
272 | return chartValue;
273 | }
274 |
275 | var now = Date.now();
276 |
277 | for (var i = minuteCount - 1; i >= 0; i--) {
278 | data.push({
279 | timestamp: now - i * 1000 * 60,
280 | value: nextValue()
281 | });
282 | }
283 |
284 | var widgetData = [
285 | {
286 | key: 'Data',
287 | values: data
288 | }
289 | ];
290 |
291 | this.updateScope(widgetData);
292 | };
293 |
294 | RandomTimeSeriesDataModel.prototype.destroy = function () {
295 | RandomBaseDataModel.prototype.destroy.call(this);
296 | $interval.cancel(this.intervalPromise);
297 | };
298 |
299 | return RandomTimeSeriesDataModel;
300 | });
301 | /*
302 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
303 | *
304 | * Licensed under the Apache License, Version 2.0 (the "License");
305 | * you may not use this file except in compliance with the License.
306 | * You may obtain a copy of the License at
307 | *
308 | * http://www.apache.org/licenses/LICENSE-2.0
309 | *
310 | * Unless required by applicable law or agreed to in writing, software
311 | * distributed under the License is distributed on an "AS IS" BASIS,
312 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
313 | * See the License for the specific language governing permissions and
314 | * limitations under the License.
315 | */
316 |
317 | 'use strict';
318 |
319 | angular.module('ui.models')
320 | .factory('WebSocketDataModel', function (WidgetDataModel, webSocket) {
321 | function WebSocketDataModel() {
322 | }
323 |
324 | WebSocketDataModel.prototype = Object.create(WidgetDataModel.prototype);
325 |
326 | WebSocketDataModel.prototype.init = function () {
327 | this.topic = null;
328 | this.callback = null;
329 | if (this.dataModelOptions && this.dataModelOptions.defaultTopic) {
330 | this.update(this.dataModelOptions.defaultTopic);
331 | }
332 | };
333 |
334 | WebSocketDataModel.prototype.update = function (newTopic) {
335 | var that = this;
336 |
337 | if (this.topic && this.callback) {
338 | webSocket.unsubscribe(this.topic, this.callback);
339 | }
340 |
341 | this.callback = function (message) {
342 | that.updateScope(message);
343 | that.widgetScope.$apply();
344 | };
345 |
346 | this.topic = newTopic;
347 | webSocket.subscribe(this.topic, this.callback, this.widgetScope);
348 | };
349 |
350 | WebSocketDataModel.prototype.destroy = function () {
351 | WidgetDataModel.prototype.destroy.call(this);
352 |
353 | if (this.topic && this.callback) {
354 | webSocket.unsubscribe(this.topic, this.callback);
355 | }
356 | };
357 |
358 | return WebSocketDataModel;
359 | })
360 | .factory('TimeSeriesDataModel', function (WebSocketDataModel) {
361 | function TimeSeriesDataModel() {
362 | }
363 |
364 | TimeSeriesDataModel.prototype = Object.create(WebSocketDataModel.prototype);
365 |
366 | TimeSeriesDataModel.prototype.init = function () {
367 | WebSocketDataModel.prototype.init.call(this);
368 | };
369 |
370 | TimeSeriesDataModel.prototype.update = function (newTopic) {
371 | WebSocketDataModel.prototype.update.call(this, newTopic);
372 | this.items = [];
373 | };
374 |
375 | TimeSeriesDataModel.prototype.updateScope = function (value) {
376 | value = _.isArray(value) ? value[0] : value;
377 |
378 | this.items.push({
379 | timestamp: parseInt(value.timestamp, 10), //TODO
380 | value: parseInt(value.value, 10) //TODO
381 | });
382 |
383 | if (this.items.length > 100) { //TODO
384 | this.items.shift();
385 | }
386 |
387 | var chart = {
388 | data: this.items,
389 | max: 30
390 | };
391 |
392 | WebSocketDataModel.prototype.updateScope.call(this, chart);
393 | this.data = [];
394 | };
395 |
396 | return TimeSeriesDataModel;
397 | })
398 | .factory('PieChartDataModel', function (WebSocketDataModel) {
399 | function PieChartDataModel() {
400 | }
401 |
402 | PieChartDataModel.prototype = Object.create(WebSocketDataModel.prototype);
403 |
404 | PieChartDataModel.prototype.init = function () {
405 | WebSocketDataModel.prototype.init.call(this);
406 | this.data = [];
407 | };
408 |
409 | PieChartDataModel.prototype.update = function (newTopic) {
410 | WebSocketDataModel.prototype.update.call(this, newTopic);
411 | };
412 |
413 | PieChartDataModel.prototype.updateScope = function (value) {
414 | var sum = _.reduce(value, function (memo, item) {
415 | return memo + parseFloat(item.value);
416 | }, 0);
417 |
418 | var sectors = _.map(value, function (item) {
419 | return {
420 | key: item.label,
421 | y: item.value / sum
422 | };
423 | });
424 |
425 | sectors = _.sortBy(sectors, function (item) {
426 | return item.key;
427 | });
428 |
429 | WebSocketDataModel.prototype.updateScope.call(this, sectors);
430 | };
431 |
432 | return PieChartDataModel;
433 | });
434 |
435 | /*
436 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
437 | *
438 | * Licensed under the Apache License, Version 2.0 (the "License");
439 | * you may not use this file except in compliance with the License.
440 | * You may obtain a copy of the License at
441 | *
442 | * http://www.apache.org/licenses/LICENSE-2.0
443 | *
444 | * Unless required by applicable law or agreed to in writing, software
445 | * distributed under the License is distributed on an "AS IS" BASIS,
446 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
447 | * See the License for the specific language governing permissions and
448 | * limitations under the License.
449 | */
450 |
451 | 'use strict';
452 |
453 | angular.module('ui.visibility', [])
454 | .factory('Visibility', function ($window) {
455 | return $window.Visibility;
456 | });
457 | /*
458 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
459 | *
460 | * Licensed under the Apache License, Version 2.0 (the "License");
461 | * you may not use this file except in compliance with the License.
462 | * You may obtain a copy of the License at
463 | *
464 | * http://www.apache.org/licenses/LICENSE-2.0
465 | *
466 | * Unless required by applicable law or agreed to in writing, software
467 | * distributed under the License is distributed on an "AS IS" BASIS,
468 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
469 | * See the License for the specific language governing permissions and
470 | * limitations under the License.
471 | */
472 |
473 | 'use strict';
474 |
475 | angular.module('ui.websocket')
476 | .factory('Visibility', function ($window) {
477 | return $window.Visibility;
478 | })
479 | .provider('webSocket', function () {
480 | var visibilityTimeout = 20000;
481 | var maxConnectionAttempts = 5;
482 | var connectionAttemptInterval = 2000;
483 | var webSocketURL;
484 | var explicitConnect;
485 | var closeRequested;
486 |
487 | return {
488 | $get: function ($q, $rootScope, $timeout, notificationService, Visibility, $log, $window) {
489 |
490 | var socket;
491 |
492 | var webSocketService;
493 |
494 | var deferred = $q.defer();
495 |
496 | var webSocketError = false;
497 |
498 | var connectionAttempts = 0;
499 |
500 | var onopen = function () {
501 | $log.info('WebSocket connection has been made. URL: ', webSocketURL);
502 | connectionAttempts = 0;
503 | deferred.resolve();
504 | $rootScope.$apply();
505 | };
506 |
507 | var onclose = function() {
508 | // Reset the deferred
509 | deferred = $q.defer();
510 |
511 | // Check if this close was requested
512 | if (!closeRequested) {
513 |
514 | // Check connectionAttempts count
515 | if (connectionAttempts < maxConnectionAttempts) {
516 | // Try to re-establish connection
517 | var url = this.url;
518 | connectionAttempts++;
519 | $timeout(function() {
520 | $log.info('Attempting to reconnect to websocket');
521 | webSocketService.connect(url);
522 | }, connectionAttemptInterval);
523 | }
524 |
525 | else {
526 | $log.error('Could not re-establish the WebSocket connection.');
527 | notificationService.notify({
528 | type: 'error',
529 | title: 'Could not re-establish WebSocket Connection',
530 | text: 'The dashboard lost contact with your DataTorrent Gateway for over ' +
531 | Math.round((maxConnectionAttempts * connectionAttemptInterval)/1000) +
532 | ' seconds. Double-check your connection and try refreshing the page.'
533 | });
534 | }
535 |
536 | }
537 |
538 | // Otherwise reset some flags
539 | else {
540 | connectionAttempts = 0;
541 | closeRequested = false;
542 | }
543 | };
544 |
545 | // TODO: listeners for error and close, exposed on
546 | // service itself
547 | var onerror = function () {
548 | webSocketError = true;
549 | $log.error('WebSocket encountered an error');
550 | };
551 |
552 | var onmessage = function (event) {
553 | if (stopUpdates) { // stop updates if page is inactive
554 | return;
555 | }
556 |
557 | var message = JSON.parse(event.data);
558 |
559 | var topic = message.topic;
560 |
561 | if (topicMap.hasOwnProperty(topic)) {
562 | if ($window.WS_DEBUG) {
563 | if ($window.WS_DEBUG === true) {
564 | $log.debug('WebSocket ', topic, ' => ', message.data);
565 | }
566 | else {
567 | var search = new RegExp($window.WS_DEBUG + '');
568 | if (search.test(topic)) {
569 | $log.debug('WebSocket ', topic, ' => ', message.data);
570 | }
571 | }
572 | }
573 | topicMap[topic].fire(message.data);
574 | }
575 | };
576 |
577 | var topicMap = {}; // topic -> [callbacks] mapping
578 |
579 | var stopUpdates = false;
580 |
581 |
582 | if (Visibility.isSupported()) {
583 | var timeoutPromise;
584 |
585 | Visibility.change(angular.bind(this, function (e, state) {
586 | if (state === 'hidden') {
587 | timeoutPromise = $timeout(function () {
588 | stopUpdates = true;
589 | timeoutPromise = null;
590 | }, visibilityTimeout);
591 | } else {
592 | stopUpdates = false;
593 |
594 | if (timeoutPromise) {
595 | $timeout.cancel(timeoutPromise);
596 | }
597 |
598 | $log.debug('visible');
599 | }
600 | }));
601 | }
602 |
603 | webSocketService = {
604 | send: function (message) {
605 | var msg = JSON.stringify(message);
606 |
607 | deferred.promise.then(function () {
608 | $log.debug('send ' + msg);
609 | socket.send(msg);
610 | });
611 | },
612 |
613 | subscribe: function (topic, callback, $scope) {
614 | var callbacks = topicMap[topic];
615 |
616 | // If a jQuery.Callbacks object has not been created for this
617 | // topic, one should be created and a "subscribe" message
618 | // should be sent.
619 | if (!callbacks) {
620 |
621 | // send the subscribe message
622 | var message = { type: 'subscribe', topic: topic };
623 | this.send(message);
624 |
625 | // create the Callbacks object
626 | callbacks = jQuery.Callbacks();
627 | topicMap[topic] = callbacks;
628 | }
629 |
630 | // When scope is provided...
631 | if ($scope) {
632 |
633 | // ...it's $digest method should be called
634 | // after the callback has been triggered, so
635 | // we have to wrap the function.
636 | var wrappedCallback = function () {
637 | callback.apply({}, arguments);
638 | $scope.$digest();
639 | };
640 | callbacks.add(wrappedCallback);
641 |
642 | // We should also be listening for the destroy
643 | // event so we can automatically unsubscribe.
644 | $scope.$on('$destroy', angular.bind(this, function () {
645 | this.unsubscribe(topic, wrappedCallback);
646 | }));
647 |
648 | return wrappedCallback;
649 | }
650 | else {
651 | callbacks.add(callback);
652 | return callback;
653 | }
654 | },
655 | // Unsubscribe callback is optional. If callback is omitted, all callbacks are removed.
656 | unsubscribe: function (topic, callback) {
657 | if (topicMap.hasOwnProperty(topic)) {
658 | var callbacks = topicMap[topic];
659 | if (callback) {
660 | callbacks.remove(callback);
661 | }
662 |
663 | // If no callback is provided proceed to delete all existing callbacks
664 | // Or check callbacks.has() which will return false if there are no more handlers
665 | // registered in this callbacks collection.
666 | if (!callback || !callbacks.has()) {
667 |
668 | // Send the unsubscribe message first
669 | var message = { type: 'unsubscribe', topic: topic };
670 | this.send(message);
671 |
672 | // Then remove the callbacks object for this topic
673 | delete topicMap[topic];
674 |
675 | }
676 | }
677 | },
678 |
679 | disconnect: function() {
680 | if (!socket) {
681 | return;
682 | }
683 | closeRequested = true;
684 | socket.close();
685 | },
686 |
687 | connect: function(url) {
688 | if (!url) {
689 | if (webSocketURL) {
690 | url = webSocketURL;
691 | }
692 | else {
693 | throw new TypeError('No WebSocket connection URL specified in connect method');
694 | }
695 | }
696 |
697 | if (socket && socket.readyState === $window.WebSocket.OPEN) {
698 | $log.info('webSocket.connect called, but webSocket connection already established.');
699 | return;
700 | }
701 |
702 | socket = new $window.WebSocket(url);
703 | // deferred = $q.defer();
704 | socket.onopen = onopen;
705 | socket.onclose = onclose;
706 | socket.onerror = onerror;
707 | socket.onmessage = onmessage;
708 |
709 | // resubscribe to topics
710 | // send the subscribe message
711 | for (var topic in topicMap) {
712 | if (topicMap.hasOwnProperty(topic)) {
713 | var message = { type: 'subscribe', topic: topic };
714 | this.send(message);
715 | }
716 | }
717 | }
718 | };
719 |
720 | if (!explicitConnect) {
721 | webSocketService.connect();
722 | }
723 |
724 | return webSocketService;
725 | },
726 |
727 | setVisibilityTimeout: function (timeout) {
728 | visibilityTimeout = timeout;
729 | },
730 |
731 | setWebSocketURL: function (wsURL) {
732 | webSocketURL = wsURL;
733 | },
734 |
735 | setExplicitConnection: function(flag) {
736 | explicitConnect = flag;
737 | },
738 |
739 | setMaxConnectionAttempts: function(max) {
740 | maxConnectionAttempts = max;
741 | },
742 |
743 | setConnectionAttemptInterval: function(interval) {
744 | maxConnectionAttempts = interval;
745 | }
746 | };
747 | });
748 |
749 | /**
750 | * Copied from https://github.com/lithiumtech/angular_and_d3
751 | */
752 |
753 | /* jshint ignore:start */
754 |
755 | function Gauge(element, configuration)
756 | {
757 | this.element = element;
758 |
759 | var self = this; // for internal d3 functions
760 |
761 | this.configure = function(configuration)
762 | {
763 | this.config = configuration;
764 |
765 | this.config.size = this.config.size * 0.9;
766 |
767 | this.config.raduis = this.config.size * 0.97 / 2;
768 | this.config.cx = this.config.size / 2;
769 | this.config.cy = this.config.size / 2;
770 |
771 | this.config.min = undefined != configuration.min ? configuration.min : 0;
772 | this.config.max = undefined != configuration.max ? configuration.max : 100;
773 | this.config.range = this.config.max - this.config.min;
774 |
775 | this.config.majorTicks = configuration.majorTicks || 5;
776 | this.config.minorTicks = configuration.minorTicks || 2;
777 |
778 | this.config.greenColor = configuration.greenColor || "#109618";
779 | this.config.yellowColor = configuration.yellowColor || "#FF9900";
780 | this.config.redColor = configuration.redColor || "#DC3912";
781 |
782 | this.config.transitionDuration = configuration.transitionDuration || 500;
783 | }
784 |
785 | this.render = function()
786 | {
787 | this.body = d3.select( this.element )
788 | .append("svg:svg")
789 | .attr("class", "gauge")
790 | .attr("width", this.config.size)
791 | .attr("height", this.config.size);
792 |
793 | this.body.append("svg:circle")
794 | .attr("cx", this.config.cx)
795 | .attr("cy", this.config.cy)
796 | .attr("r", this.config.raduis)
797 | .style("fill", "#ccc")
798 | .style("stroke", "#000")
799 | .style("stroke-width", "0.5px");
800 |
801 | this.body.append("svg:circle")
802 | .attr("cx", this.config.cx)
803 | .attr("cy", this.config.cy)
804 | .attr("r", 0.9 * this.config.raduis)
805 | .style("fill", "#fff")
806 | .style("stroke", "#e0e0e0")
807 | .style("stroke-width", "2px");
808 |
809 | for (var index in this.config.greenZones)
810 | {
811 | this.drawBand(this.config.greenZones[index].from, this.config.greenZones[index].to, self.config.greenColor);
812 | }
813 |
814 | for (var index in this.config.yellowZones)
815 | {
816 | this.drawBand(this.config.yellowZones[index].from, this.config.yellowZones[index].to, self.config.yellowColor);
817 | }
818 |
819 | for (var index in this.config.redZones)
820 | {
821 | this.drawBand(this.config.redZones[index].from, this.config.redZones[index].to, self.config.redColor);
822 | }
823 |
824 | if (undefined != this.config.label)
825 | {
826 | var fontSize = Math.round(this.config.size / 9);
827 | this.body.append("svg:text")
828 | .attr("x", this.config.cx)
829 | .attr("y", this.config.cy / 2 + fontSize / 2)
830 | .attr("dy", fontSize / 2)
831 | .attr("text-anchor", "middle")
832 | .text(this.config.label)
833 | .style("font-size", fontSize + "px")
834 | .style("fill", "#333")
835 | .style("stroke-width", "0px");
836 | }
837 |
838 | var fontSize = Math.round(this.config.size / 16);
839 | var majorDelta = this.config.range / (this.config.majorTicks - 1);
840 | for (var major = this.config.min; major <= this.config.max; major += majorDelta)
841 | {
842 | var minorDelta = majorDelta / this.config.minorTicks;
843 | for (var minor = major + minorDelta; minor < Math.min(major + majorDelta, this.config.max); minor += minorDelta)
844 | {
845 | var point1 = this.valueToPoint(minor, 0.75);
846 | var point2 = this.valueToPoint(minor, 0.85);
847 |
848 | this.body.append("svg:line")
849 | .attr("x1", point1.x)
850 | .attr("y1", point1.y)
851 | .attr("x2", point2.x)
852 | .attr("y2", point2.y)
853 | .style("stroke", "#666")
854 | .style("stroke-width", "1px");
855 | }
856 |
857 | var point1 = this.valueToPoint(major, 0.7);
858 | var point2 = this.valueToPoint(major, 0.85);
859 |
860 | this.body.append("svg:line")
861 | .attr("x1", point1.x)
862 | .attr("y1", point1.y)
863 | .attr("x2", point2.x)
864 | .attr("y2", point2.y)
865 | .style("stroke", "#333")
866 | .style("stroke-width", "2px");
867 |
868 | if (major == this.config.min || major == this.config.max)
869 | {
870 | var point = this.valueToPoint(major, 0.63);
871 |
872 | this.body.append("svg:text")
873 | .attr("x", point.x)
874 | .attr("y", point.y)
875 | .attr("dy", fontSize / 3)
876 | .attr("text-anchor", major == this.config.min ? "start" : "end")
877 | .text(major)
878 | .style("font-size", fontSize + "px")
879 | .style("fill", "#333")
880 | .style("stroke-width", "0px");
881 | }
882 | }
883 |
884 | var pointerContainer = this.body.append("svg:g").attr("class", "pointerContainer");
885 |
886 | var midValue = (this.config.min + this.config.max) / 2;
887 |
888 | var pointerPath = this.buildPointerPath(midValue);
889 |
890 | var pointerLine = d3.svg.line()
891 | .x(function(d) { return d.x })
892 | .y(function(d) { return d.y })
893 | .interpolate("basis");
894 |
895 | pointerContainer.selectAll("path")
896 | .data([pointerPath])
897 | .enter()
898 | .append("svg:path")
899 | .attr("d", pointerLine)
900 | .style("fill", "#dc3912")
901 | .style("stroke", "#c63310")
902 | .style("fill-opacity", 0.7)
903 |
904 | pointerContainer.append("svg:circle")
905 | .attr("cx", this.config.cx)
906 | .attr("cy", this.config.cy)
907 | .attr("r", 0.12 * this.config.raduis)
908 | .style("fill", "#4684EE")
909 | .style("stroke", "#666")
910 | .style("opacity", 1);
911 |
912 | var fontSize = Math.round(this.config.size / 10);
913 | pointerContainer.selectAll("text")
914 | .data([midValue])
915 | .enter()
916 | .append("svg:text")
917 | .attr("x", this.config.cx)
918 | .attr("y", this.config.size - this.config.cy / 4 - fontSize)
919 | .attr("dy", fontSize / 2)
920 | .attr("text-anchor", "middle")
921 | .style("font-size", fontSize + "px")
922 | .style("fill", "#000")
923 | .style("stroke-width", "0px");
924 |
925 | this.redraw(this.config.min, 0);
926 | }
927 |
928 | this.buildPointerPath = function(value)
929 | {
930 | var delta = this.config.range / 13;
931 |
932 | var head = valueToPoint(value, 0.85);
933 | var head1 = valueToPoint(value - delta, 0.12);
934 | var head2 = valueToPoint(value + delta, 0.12);
935 |
936 | var tailValue = value - (this.config.range * (1/(270/360)) / 2);
937 | var tail = valueToPoint(tailValue, 0.28);
938 | var tail1 = valueToPoint(tailValue - delta, 0.12);
939 | var tail2 = valueToPoint(tailValue + delta, 0.12);
940 |
941 | return [head, head1, tail2, tail, tail1, head2, head];
942 |
943 | function valueToPoint(value, factor)
944 | {
945 | var point = self.valueToPoint(value, factor);
946 | point.x -= self.config.cx;
947 | point.y -= self.config.cy;
948 | return point;
949 | }
950 | }
951 |
952 | this.drawBand = function(start, end, color)
953 | {
954 | if (0 >= end - start) return;
955 |
956 | this.body.append("svg:path")
957 | .style("fill", color)
958 | .attr("d", d3.svg.arc()
959 | .startAngle(this.valueToRadians(start))
960 | .endAngle(this.valueToRadians(end))
961 | .innerRadius(0.65 * this.config.raduis)
962 | .outerRadius(0.85 * this.config.raduis))
963 | .attr("transform", function() { return "translate(" + self.config.cx + ", " + self.config.cy + ") rotate(270)" });
964 | }
965 |
966 | this.redraw = function(value, transitionDuration)
967 | {
968 | var pointerContainer = this.body.select(".pointerContainer");
969 |
970 | pointerContainer.selectAll("text").text(Math.round(value));
971 |
972 | var pointer = pointerContainer.selectAll("path");
973 | pointer.transition()
974 | .duration(undefined != transitionDuration ? transitionDuration : this.config.transitionDuration)
975 | //.delay(0)
976 | //.ease("linear")
977 | //.attr("transform", function(d)
978 | .attrTween("transform", function()
979 | {
980 | var pointerValue = value;
981 | if (value > self.config.max) pointerValue = self.config.max + 0.02*self.config.range;
982 | else if (value < self.config.min) pointerValue = self.config.min - 0.02*self.config.range;
983 | var targetRotation = (self.valueToDegrees(pointerValue) - 90);
984 | var currentRotation = self._currentRotation || targetRotation;
985 | self._currentRotation = targetRotation;
986 |
987 | return function(step)
988 | {
989 | var rotation = currentRotation + (targetRotation-currentRotation)*step;
990 | return "translate(" + self.config.cx + ", " + self.config.cy + ") rotate(" + rotation + ")";
991 | }
992 | });
993 | }
994 |
995 | this.valueToDegrees = function(value)
996 | {
997 | // thanks @closealert
998 | //return value / this.config.range * 270 - 45;
999 | return value / this.config.range * 270 - (this.config.min / this.config.range * 270 + 45);
1000 | }
1001 |
1002 | this.valueToRadians = function(value)
1003 | {
1004 | return this.valueToDegrees(value) * Math.PI / 180;
1005 | }
1006 |
1007 | this.valueToPoint = function(value, factor)
1008 | {
1009 | return { x: this.config.cx - this.config.raduis * factor * Math.cos(this.valueToRadians(value)),
1010 | y: this.config.cy - this.config.raduis * factor * Math.sin(this.valueToRadians(value)) };
1011 | }
1012 |
1013 | // initialization
1014 | this.configure(configuration);
1015 | }
1016 |
1017 | /* jshint ignore:end */
1018 | /*!
1019 | * visibly - v0.6 Aug 2011 - Page Visibility API Polyfill
1020 | * http://github.com/addyosmani
1021 | * Copyright (c) 2011 Addy Osmani
1022 | * Dual licensed under the MIT and GPL licenses.
1023 | *
1024 | * Methods supported:
1025 | * visibly.onVisible(callback)
1026 | * visibly.onHidden(callback)
1027 | * visibly.hidden()
1028 | * visibly.visibilityState()
1029 | * visibly.visibilitychange(callback(state));
1030 | */
1031 |
1032 | ;(function () {
1033 |
1034 | window.visibly = {
1035 | q: document,
1036 | p: undefined,
1037 | prefixes: ['webkit', 'ms','o','moz','khtml'],
1038 | props: ['VisibilityState', 'visibilitychange', 'Hidden'],
1039 | m: ['focus', 'blur'],
1040 | visibleCallbacks: [],
1041 | hiddenCallbacks: [],
1042 | genericCallbacks:[],
1043 | _callbacks: [],
1044 | cachedPrefix:"",
1045 | fn:null,
1046 |
1047 | onVisible: function (_callback) {
1048 | if(typeof _callback == 'function' ){
1049 | this.visibleCallbacks.push(_callback);
1050 | }
1051 | },
1052 | onHidden: function (_callback) {
1053 | if(typeof _callback == 'function' ){
1054 | this.hiddenCallbacks.push(_callback);
1055 | }
1056 | },
1057 | getPrefix:function(){
1058 | if(!this.cachedPrefix){
1059 | for(var l=0;b=this.prefixes[l++];){
1060 | if(b + this.props[2] in this.q){
1061 | this.cachedPrefix = b;
1062 | return this.cachedPrefix;
1063 | }
1064 | }
1065 | }
1066 | },
1067 |
1068 | visibilityState:function(){
1069 | return this._getProp(0);
1070 | },
1071 | hidden:function(){
1072 | return this._getProp(2);
1073 | },
1074 | visibilitychange:function(fn){
1075 | if(typeof fn == 'function' ){
1076 | this.genericCallbacks.push(fn);
1077 | }
1078 |
1079 | var n = this.genericCallbacks.length;
1080 | if(n){
1081 | if(this.cachedPrefix){
1082 | while(n--){
1083 | this.genericCallbacks[n].call(this, this.visibilityState());
1084 | }
1085 | }else{
1086 | while(n--){
1087 | this.genericCallbacks[n].call(this, arguments[0]);
1088 | }
1089 | }
1090 | }
1091 |
1092 | },
1093 | isSupported: function (index) {
1094 | return ((this.cachedPrefix + this.props[2]) in this.q);
1095 | },
1096 | _getProp:function(index){
1097 | return this.q[this.cachedPrefix + this.props[index]];
1098 | },
1099 | _execute: function (index) {
1100 | if (index) {
1101 | this._callbacks = (index == 1) ? this.visibleCallbacks : this.hiddenCallbacks;
1102 | var n = this._callbacks.length;
1103 | while(n--){
1104 | this._callbacks[n]();
1105 | }
1106 | }
1107 | },
1108 | _visible: function () {
1109 | window.visibly._execute(1);
1110 | window.visibly.visibilitychange.call(window.visibly, 'visible');
1111 | },
1112 | _hidden: function () {
1113 | window.visibly._execute(2);
1114 | window.visibly.visibilitychange.call(window.visibly, 'hidden');
1115 | },
1116 | _nativeSwitch: function () {
1117 | this[this._getProp(2) ? '_hidden' : '_visible']();
1118 | },
1119 | _listen: function () {
1120 | try { /*if no native page visibility support found..*/
1121 | if (!(this.isSupported())) {
1122 | if (this.q.addEventListener) { /*for browsers without focusin/out support eg. firefox, opera use focus/blur*/
1123 | window.addEventListener(this.m[0], this._visible, 1);
1124 | window.addEventListener(this.m[1], this._hidden, 1);
1125 | } else { /*IE <10s most reliable focus events are onfocusin/onfocusout*/
1126 | if (this.q.attachEvent) {
1127 | this.q.attachEvent('onfocusin', this._visible);
1128 | this.q.attachEvent('onfocusout', this._hidden);
1129 | }
1130 | }
1131 | } else { /*switch support based on prefix detected earlier*/
1132 | this.q.addEventListener(this.cachedPrefix + this.props[1], function () {
1133 | window.visibly._nativeSwitch.apply(window.visibly, arguments);
1134 | }, 1);
1135 | }
1136 | } catch (e) {}
1137 | },
1138 | init: function () {
1139 | this.getPrefix();
1140 | this._listen();
1141 | }
1142 | };
1143 |
1144 | this.visibly.init();
1145 | })();
1146 |
1147 | /*
1148 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
1149 | *
1150 | * Licensed under the Apache License, Version 2.0 (the "License");
1151 | * you may not use this file except in compliance with the License.
1152 | * You may obtain a copy of the License at
1153 | *
1154 | * http://www.apache.org/licenses/LICENSE-2.0
1155 | *
1156 | * Unless required by applicable law or agreed to in writing, software
1157 | * distributed under the License is distributed on an "AS IS" BASIS,
1158 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1159 | * See the License for the specific language governing permissions and
1160 | * limitations under the License.
1161 | */
1162 |
1163 | 'use strict';
1164 |
1165 | angular.module('ui.widgets')
1166 | .directive('wtBarChart', function ($filter) {
1167 | return {
1168 | restrict: 'A',
1169 | replace: true,
1170 | templateUrl: 'template/widgets/barChart/barChart.html',
1171 | scope: {
1172 | data: '=data'
1173 | },
1174 | controller: function ($scope) {
1175 | var filter = $filter('date');
1176 |
1177 | $scope.xAxisTickFormatFunction = function () {
1178 | return function(d) {
1179 | return filter(d, 'HH:mm');
1180 | };
1181 | };
1182 |
1183 | $scope.xFunction = function(){
1184 | return function(d) {
1185 | return d.timestamp;
1186 | };
1187 | };
1188 | $scope.yFunction = function(){
1189 | return function(d) {
1190 | return d.value;
1191 | };
1192 | };
1193 | },
1194 | link: function postLink(scope) {
1195 | scope.$watch('data', function (data) {
1196 | if (data && data[0] && data[0].values && (data[0].values.length > 1)) {
1197 | var timeseries = _.sortBy(data[0].values, function (item) {
1198 | return item.timestamp;
1199 | });
1200 |
1201 | var start = timeseries[0].timestamp;
1202 | var end = timeseries[timeseries.length - 1].timestamp;
1203 | scope.start = start;
1204 | scope.end = end;
1205 | }
1206 | });
1207 | }
1208 | };
1209 | });
1210 | /*
1211 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
1212 | *
1213 | * Licensed under the Apache License, Version 2.0 (the "License");
1214 | * you may not use this file except in compliance with the License.
1215 | * You may obtain a copy of the License at
1216 | *
1217 | * http://www.apache.org/licenses/LICENSE-2.0
1218 | *
1219 | * Unless required by applicable law or agreed to in writing, software
1220 | * distributed under the License is distributed on an "AS IS" BASIS,
1221 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1222 | * See the License for the specific language governing permissions and
1223 | * limitations under the License.
1224 | */
1225 |
1226 | 'use strict';
1227 |
1228 | angular.module('ui.widgets')
1229 | .directive('wtGauge', function () {
1230 | return {
1231 | replace: true,
1232 | scope: {
1233 | label: '@',
1234 | min: '=',
1235 | max: '=',
1236 | value: '='
1237 | },
1238 | link: function (scope, element, attrs) {
1239 | var config = {
1240 | size: 200,
1241 | label: attrs.label,
1242 | min: undefined !== scope.min ? scope.min : 0,
1243 | max: undefined !== scope.max ? scope.max : 100,
1244 | minorTicks: 5
1245 | };
1246 |
1247 | var range = config.max - config.min;
1248 | config.yellowZones = [
1249 | { from: config.min + range * 0.75, to: config.min + range * 0.9 }
1250 | ];
1251 | config.redZones = [
1252 | { from: config.min + range * 0.9, to: config.max }
1253 | ];
1254 |
1255 | scope.gauge = new Gauge(element[0], config);
1256 | scope.gauge.render();
1257 |
1258 | function update(value) {
1259 | var percentage;
1260 | if (_.isString(value)) {
1261 | percentage = parseFloat(value);
1262 | } else if (_.isNumber(value)) {
1263 | percentage = value;
1264 | }
1265 |
1266 | if (!_.isUndefined(percentage)) {
1267 | scope.gauge.redraw(percentage);
1268 | }
1269 | }
1270 |
1271 | update(0);
1272 |
1273 | scope.$watch('value', function (value) {
1274 | if (scope.gauge) {
1275 | update(value);
1276 | }
1277 | });
1278 | }
1279 | };
1280 | });
1281 | /*
1282 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
1283 | *
1284 | * Licensed under the Apache License, Version 2.0 (the "License");
1285 | * you may not use this file except in compliance with the License.
1286 | * You may obtain a copy of the License at
1287 | *
1288 | * http://www.apache.org/licenses/LICENSE-2.0
1289 | *
1290 | * Unless required by applicable law or agreed to in writing, software
1291 | * distributed under the License is distributed on an "AS IS" BASIS,
1292 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1293 | * See the License for the specific language governing permissions and
1294 | * limitations under the License.
1295 | */
1296 |
1297 | 'use strict';
1298 |
1299 | angular.module('ui.widgets')
1300 | .directive('wtHistoricalChart', function () {
1301 | return {
1302 | restrict: 'A',
1303 | replace: true,
1304 | templateUrl: 'template/widgets/historicalChart/historicalChart.html',
1305 | scope: {
1306 | chart: '='
1307 | },
1308 | controller: function ($scope) {
1309 | $scope.mode = 'MINUTES';
1310 |
1311 | $scope.changeMode = function (mode) {
1312 | $scope.mode = mode;
1313 | $scope.$emit('modeChanged', mode);
1314 | };
1315 | }
1316 | };
1317 | });
1318 | /*
1319 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
1320 | *
1321 | * Licensed under the Apache License, Version 2.0 (the "License");
1322 | * you may not use this file except in compliance with the License.
1323 | * You may obtain a copy of the License at
1324 | *
1325 | * http://www.apache.org/licenses/LICENSE-2.0
1326 | *
1327 | * Unless required by applicable law or agreed to in writing, software
1328 | * distributed under the License is distributed on an "AS IS" BASIS,
1329 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1330 | * See the License for the specific language governing permissions and
1331 | * limitations under the License.
1332 | */
1333 |
1334 | 'use strict';
1335 |
1336 | angular.module('ui.widgets')
1337 | .directive('wtLineChart', function () {
1338 | return {
1339 | template: '
',
1340 | scope: {
1341 | chart: '='
1342 | },
1343 | replace: true,
1344 | link: function postLink(scope, element) {
1345 | var lineChart = new google.visualization.LineChart(element[0]);
1346 |
1347 | function draw(chart) {
1348 | var data = chart.data;
1349 |
1350 | var table = new google.visualization.DataTable();
1351 | table.addColumn('datetime');
1352 | table.addColumn('number');
1353 | table.addRows(data.length);
1354 |
1355 | var view = new google.visualization.DataView(table);
1356 |
1357 | for (var i = 0; i < data.length; i++) {
1358 | var item = data[i];
1359 | table.setCell(i, 0, new Date(item.timestamp));
1360 | var value = parseFloat(item.value);
1361 | table.setCell(i, 1, value);
1362 | }
1363 |
1364 | var chartOptions = {
1365 | legend: 'none',
1366 | vAxis: { minValue: 0, maxValue: 100 }
1367 | //chartArea: { top: 20, left: 30, height: 240 }
1368 | };
1369 |
1370 | if (chart.max) {
1371 | var lastTimestamp;
1372 |
1373 | if (data.length) {
1374 | var last = data[data.length - 1];
1375 | lastTimestamp = last.timestamp;
1376 | } else {
1377 | lastTimestamp = Date.now();
1378 | }
1379 |
1380 | var max = new Date(lastTimestamp);
1381 | var min = new Date(lastTimestamp - (chart.max - 1) * 1000);
1382 |
1383 | angular.extend(chartOptions, {
1384 | hAxis: { viewWindow: { min: min, max: max }}
1385 | });
1386 | }
1387 |
1388 | if (chart.chartOptions) {
1389 | angular.extend(chartOptions, chart.chartOptions);
1390 | }
1391 |
1392 | lineChart.draw(view, chartOptions);
1393 | }
1394 |
1395 | scope.$watch('chart', function (chart) {
1396 | if (!chart) {
1397 | chart = {
1398 | data: [],
1399 | max: 30
1400 | };
1401 | }
1402 |
1403 | if (chart.data) {
1404 | draw(chart);
1405 | }
1406 | });
1407 | }
1408 | };
1409 | });
1410 | /*
1411 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
1412 | *
1413 | * Licensed under the Apache License, Version 2.0 (the "License");
1414 | * you may not use this file except in compliance with the License.
1415 | * You may obtain a copy of the License at
1416 | *
1417 | * http://www.apache.org/licenses/LICENSE-2.0
1418 | *
1419 | * Unless required by applicable law or agreed to in writing, software
1420 | * distributed under the License is distributed on an "AS IS" BASIS,
1421 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1422 | * See the License for the specific language governing permissions and
1423 | * limitations under the License.
1424 | */
1425 |
1426 | 'use strict';
1427 |
1428 | angular.module('ui.widgets')
1429 | .directive('wtMetricsChart', function ($filter, MetricsChartHistory) {
1430 | return {
1431 | restrict: 'A',
1432 | replace: true,
1433 | templateUrl: 'template/widgets/metricsChart/metricsChart.html',
1434 | scope: {
1435 | data: '=?',
1436 | metrics: '=',
1437 | controller: '='
1438 | },
1439 | controller: function ($scope) {
1440 | var filter = $filter('date');
1441 | var yAxisFilter = $filter('number');
1442 |
1443 | $scope.xAxisTickFormatFunction = function () {
1444 | return function (d) {
1445 | return filter(d, 'mm:ss');
1446 | };
1447 | };
1448 |
1449 | $scope.yAxisTickFormatFunction = function () {
1450 | return function (d) {
1451 | return yAxisFilter(d);
1452 | };
1453 | };
1454 |
1455 | $scope.xFunction = function () {
1456 | return function (d) {
1457 | return d.timestamp;
1458 | };
1459 | };
1460 | $scope.yFunction = function () {
1461 | return function (d) {
1462 | return d.value;
1463 | };
1464 | };
1465 |
1466 | $scope.chartCallback = function () { // callback to access nvd3 chart
1467 | //console.log('chartCallback');
1468 | //console.log(arguments);
1469 | //console.log(chart.legend.dispatch.);
1470 | //chart.legend.dispatch.on('legendClick', function(newState) {
1471 | // console.log(newState);
1472 | //});
1473 | };
1474 |
1475 | $scope.maxTimeLimit = 300;
1476 |
1477 | $scope.options = [
1478 | {
1479 | value: 30,
1480 | label: 'last 30 seconds'
1481 | },
1482 | {
1483 | value: 60,
1484 | label: 'last minute'
1485 | },
1486 | {
1487 | value: 120,
1488 | label: 'last two minutes'
1489 | },
1490 | {
1491 | value: $scope.maxTimeLimit,
1492 | label: 'last 5 minutes'
1493 | }
1494 | ];
1495 | $scope.timeFrame = $scope.options[0];
1496 |
1497 |
1498 | var chartHistory = null;
1499 | if ($scope.controller) {
1500 | chartHistory = new MetricsChartHistory($scope, $scope.metrics, $scope.maxTimeLimit, $scope.timeFrame.value);
1501 | $scope.controller.addPoint = function (point) {
1502 | chartHistory.addPoint(point);
1503 | };
1504 | }
1505 |
1506 | $scope.timeFrameChanged = function (newTimeFrame) {
1507 | if (chartHistory) {
1508 | chartHistory.updateChart(Date.now(), newTimeFrame.value);
1509 | }
1510 | };
1511 | },
1512 | link: function postLink(scope) {
1513 | scope.data = [];
1514 | }
1515 | };
1516 | })
1517 | .factory('MetricsChartHistory', function () {
1518 | function MetricsChartHistory(scope, metrics, maxTimeLimit, timeLimit) {
1519 | this.scope = scope;
1520 | this.metrics = metrics;
1521 | this.maxTimeLimit = maxTimeLimit;
1522 | this.timeLimit = timeLimit;
1523 | this.history = [];
1524 |
1525 | this.series = [];
1526 |
1527 | _.each(metrics, function (metric) {
1528 | this.series.push({
1529 | key: metric.key,
1530 | disabled: !metric.visible,
1531 | color: metric.color
1532 | });
1533 | }.bind(this));
1534 | }
1535 |
1536 | angular.extend(MetricsChartHistory.prototype, {
1537 | updateHistory: function (now, point) {
1538 | var historyStartTime = now - this.maxTimeLimit * 1000;
1539 |
1540 | var ind = _.findIndex(this.history, function (historyPoint) {
1541 | return historyPoint.timestamp >= historyStartTime;
1542 | });
1543 | if (ind > 1) {
1544 | this.history = _.rest(this.history, ind - 1);
1545 | }
1546 |
1547 | var historyPoint = {
1548 | timestamp: now,
1549 | data: point
1550 | };
1551 | this.history.push(historyPoint);
1552 | },
1553 |
1554 | updateChart: function (now, timeLimit) {
1555 | this.timeLimit = timeLimit;
1556 |
1557 | var startTime = now - 1000 * timeLimit;
1558 |
1559 | var history = _.filter(this.history, function (historyPoint) { //TODO optimize
1560 | return historyPoint.timestamp >= startTime;
1561 | });
1562 |
1563 | _.each(this.metrics, function (metric, index) {
1564 | var metricKey = metric.key;
1565 |
1566 | var values = _.map(history, function (historyPoint) {
1567 | return {
1568 | timestamp: historyPoint.timestamp,
1569 | value: Math.round(parseInt(historyPoint.data[metricKey], 10))
1570 | };
1571 | });
1572 |
1573 | this.series[index].values = values;
1574 | }.bind(this));
1575 |
1576 | /*
1577 | //TODO this is workaround to have fixed x axis scale when no enough date is available
1578 | chart.push({
1579 | key: 'Left Value',
1580 | values: [
1581 | {timestamp: startTime, value: 0}
1582 | ]
1583 | });
1584 | */
1585 |
1586 | /*
1587 | var max = _.max(history, function (historyPoint) { //TODO optimize
1588 | return historyPoint.stats.tuplesEmittedPSMA; //TODO
1589 | });
1590 |
1591 | chart.push({
1592 | key: 'Upper Value',
1593 | values: [
1594 | {timestamp: now - 30 * 1000, value: Math.round(max.value * 1.2)}
1595 | ]
1596 | });
1597 | */
1598 |
1599 | if (history.length > 1) {
1600 | this.scope.data = _.clone(this.series);
1601 | this.scope.start = Math.min(startTime, _.first(history).timestamp);
1602 | this.scope.end = _.last(history).timestamp;
1603 | }
1604 | },
1605 |
1606 | addPoint: function (point) {
1607 | var now = Date.now();
1608 | this.updateHistory(now, point);
1609 |
1610 | this.updateChart(now, this.timeLimit);
1611 | }
1612 | });
1613 |
1614 | return MetricsChartHistory;
1615 | });
1616 | /*
1617 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
1618 | *
1619 | * Licensed under the Apache License, Version 2.0 (the "License");
1620 | * you may not use this file except in compliance with the License.
1621 | * You may obtain a copy of the License at
1622 | *
1623 | * http://www.apache.org/licenses/LICENSE-2.0
1624 | *
1625 | * Unless required by applicable law or agreed to in writing, software
1626 | * distributed under the License is distributed on an "AS IS" BASIS,
1627 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1628 | * See the License for the specific language governing permissions and
1629 | * limitations under the License.
1630 | */
1631 |
1632 | 'use strict';
1633 |
1634 | angular.module('ui.widgets')
1635 | .directive('wtNvd3LineChart', function ($filter) {
1636 | return {
1637 | restrict: 'A',
1638 | replace: true,
1639 | templateUrl: 'template/widgets/nvd3LineChart/nvd3LineChart.html',
1640 | scope: {
1641 | data: '=data',
1642 | showLegend: '@',
1643 | showTimeRange: '=?',
1644 | timeAxisFormat: '=?'
1645 | },
1646 | controller: function ($scope) {
1647 | var filter = $filter('date');
1648 | var numberFilter = $filter('number');
1649 |
1650 | $scope.xAxisTickFormatFunction = function () {
1651 | return function (d) {
1652 | return filter(d, $scope.timeAxisFormat);
1653 | };
1654 | };
1655 |
1656 | $scope.yAxisTickFormatFunction = function () {
1657 | return function (d) {
1658 | if (d > 999) {
1659 | var value;
1660 | var scale;
1661 | if (d < 999999) {
1662 | value = Math.round(d/1000);
1663 | scale = 'k';
1664 | } else {
1665 | value = Math.round(d/1000000);
1666 | scale = 'm';
1667 | }
1668 | return numberFilter(value) + scale;
1669 | } else {
1670 | return numberFilter(d);
1671 | }
1672 | };
1673 | };
1674 |
1675 | $scope.xFunction = function () {
1676 | return function (d) {
1677 | return d.timestamp;
1678 | };
1679 | };
1680 | $scope.yFunction = function () {
1681 | return function (d) {
1682 | return d.value;
1683 | };
1684 | };
1685 | },
1686 | link: function postLink(scope, element, attrs) {
1687 | if (!_.has(attrs, 'showTimeRange')) {
1688 | scope.showTimeRange = true;
1689 | }
1690 |
1691 | scope.timeAxisFormat = scope.timeAxisFormat || 'HH:mm';
1692 |
1693 | scope.$watch('data', function (data) {
1694 | if (data && data[0] && data[0].values && (data[0].values.length > 1)) {
1695 | var timeseries = _.sortBy(data[0].values, function (item) {
1696 | return item.timestamp;
1697 | });
1698 |
1699 | var start = timeseries[0].timestamp;
1700 | var end = timeseries[timeseries.length - 1].timestamp;
1701 | scope.start = start;
1702 | scope.end = end;
1703 | }
1704 | });
1705 | }
1706 | };
1707 | });
1708 | /*
1709 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
1710 | *
1711 | * Licensed under the Apache License, Version 2.0 (the "License");
1712 | * you may not use this file except in compliance with the License.
1713 | * You may obtain a copy of the License at
1714 | *
1715 | * http://www.apache.org/licenses/LICENSE-2.0
1716 | *
1717 | * Unless required by applicable law or agreed to in writing, software
1718 | * distributed under the License is distributed on an "AS IS" BASIS,
1719 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1720 | * See the License for the specific language governing permissions and
1721 | * limitations under the License.
1722 | */
1723 |
1724 | 'use strict';
1725 |
1726 | angular.module('ui.widgets')
1727 | .directive('wtPieChart', function () {
1728 | return {
1729 | restrict: 'A',
1730 | replace: true,
1731 | templateUrl: 'template/widgets/pieChart/pieChart.html',
1732 | scope: {
1733 | data: '=data'
1734 | },
1735 | controller: function ($scope) {
1736 | $scope.xFunction = function(){
1737 | return function(d) {
1738 | return d.key;
1739 | };
1740 | };
1741 | $scope.yFunction = function(){
1742 | return function(d) {
1743 | return d.y;
1744 | };
1745 | };
1746 |
1747 | $scope.descriptionFunction = function(){
1748 | return function(d){
1749 | return d.key + ' ' + d.y;
1750 | };
1751 | };
1752 | }
1753 | };
1754 | });
1755 | /*
1756 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
1757 | *
1758 | * Licensed under the Apache License, Version 2.0 (the "License");
1759 | * you may not use this file except in compliance with the License.
1760 | * You may obtain a copy of the License at
1761 | *
1762 | * http://www.apache.org/licenses/LICENSE-2.0
1763 | *
1764 | * Unless required by applicable law or agreed to in writing, software
1765 | * distributed under the License is distributed on an "AS IS" BASIS,
1766 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1767 | * See the License for the specific language governing permissions and
1768 | * limitations under the License.
1769 | */
1770 |
1771 | 'use strict';
1772 |
1773 | angular.module('ui.widgets')
1774 | .directive('wtRandom', function ($interval) {
1775 | return {
1776 | restrict: 'A',
1777 | replace: true,
1778 | templateUrl: 'template/widgets/random/random.html',
1779 | link: function postLink(scope) {
1780 | function update() {
1781 | scope.number = Math.floor(Math.random() * 100);
1782 | }
1783 |
1784 | var promise = $interval(update, 500);
1785 |
1786 | scope.$on('$destroy', function () {
1787 | $interval.cancel(promise);
1788 | });
1789 | }
1790 | };
1791 | });
1792 | /*
1793 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
1794 | *
1795 | * Licensed under the Apache License, Version 2.0 (the "License");
1796 | * you may not use this file except in compliance with the License.
1797 | * You may obtain a copy of the License at
1798 | *
1799 | * http://www.apache.org/licenses/LICENSE-2.0
1800 | *
1801 | * Unless required by applicable law or agreed to in writing, software
1802 | * distributed under the License is distributed on an "AS IS" BASIS,
1803 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1804 | * See the License for the specific language governing permissions and
1805 | * limitations under the License.
1806 | */
1807 |
1808 | 'use strict';
1809 |
1810 | angular.module('ui.widgets')
1811 | .directive('wtScopeWatch', function () {
1812 | return {
1813 | restrict: 'A',
1814 | replace: true,
1815 | templateUrl: 'template/widgets/scopeWatch/scopeWatch.html',
1816 | scope: {
1817 | scopeValue: '=value',
1818 | valueClass: '@valueClass'
1819 | }
1820 | };
1821 | });
1822 | /*
1823 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
1824 | *
1825 | * Licensed under the Apache License, Version 2.0 (the "License");
1826 | * you may not use this file except in compliance with the License.
1827 | * You may obtain a copy of the License at
1828 | *
1829 | * http://www.apache.org/licenses/LICENSE-2.0
1830 | *
1831 | * Unless required by applicable law or agreed to in writing, software
1832 | * distributed under the License is distributed on an "AS IS" BASIS,
1833 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1834 | * See the License for the specific language governing permissions and
1835 | * limitations under the License.
1836 | */
1837 |
1838 | 'use strict';
1839 |
1840 | angular.module('ui.widgets')
1841 | .directive('wtSelect', function () {
1842 | return {
1843 | restrict: 'A',
1844 | replace: true,
1845 | templateUrl: 'template/widgets/select/select.html',
1846 | scope: {
1847 | label: '@',
1848 | value: '=',
1849 | options: '='
1850 | },
1851 | controller: function ($scope) {
1852 | $scope.prevValue = function () {
1853 | var index = $scope.options.indexOf($scope.value);
1854 | var nextIndex = (index - 1 + $scope.options.length) % $scope.options.length;
1855 | $scope.value = $scope.options[nextIndex];
1856 | };
1857 |
1858 | $scope.nextValue = function () {
1859 | var index = $scope.options.indexOf($scope.value);
1860 | var nextIndex = (index + 1) % $scope.options.length;
1861 | $scope.value = $scope.options[nextIndex];
1862 | };
1863 | }
1864 | };
1865 | });
1866 | /*
1867 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
1868 | *
1869 | * Licensed under the Apache License, Version 2.0 (the "License");
1870 | * you may not use this file except in compliance with the License.
1871 | * You may obtain a copy of the License at
1872 | *
1873 | * http://www.apache.org/licenses/LICENSE-2.0
1874 | *
1875 | * Unless required by applicable law or agreed to in writing, software
1876 | * distributed under the License is distributed on an "AS IS" BASIS,
1877 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1878 | * See the License for the specific language governing permissions and
1879 | * limitations under the License.
1880 | */
1881 |
1882 | 'use strict';
1883 |
1884 | angular.module('ui.widgets')
1885 | .directive('wtTime', function ($interval) {
1886 | return {
1887 | restrict: 'A',
1888 | replace: true,
1889 | templateUrl: 'template/widgets/time/time.html',
1890 | link: function (scope) {
1891 | function update() {
1892 | scope.time = new Date().toLocaleTimeString();
1893 | }
1894 |
1895 | var promise = $interval(update, 500);
1896 |
1897 | scope.$on('$destroy', function () {
1898 | $interval.cancel(promise);
1899 | });
1900 | }
1901 | };
1902 | });
1903 | /*
1904 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
1905 | *
1906 | * Licensed under the Apache License, Version 2.0 (the "License");
1907 | * you may not use this file except in compliance with the License.
1908 | * You may obtain a copy of the License at
1909 | *
1910 | * http://www.apache.org/licenses/LICENSE-2.0
1911 | *
1912 | * Unless required by applicable law or agreed to in writing, software
1913 | * distributed under the License is distributed on an "AS IS" BASIS,
1914 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1915 | * See the License for the specific language governing permissions and
1916 | * limitations under the License.
1917 | */
1918 |
1919 | 'use strict';
1920 |
1921 | angular.module('ui.widgets')
1922 | .directive('wtTopN', function () {
1923 | return {
1924 | restrict: 'A',
1925 | replace: true,
1926 | templateUrl: 'template/widgets/topN/topN.html',
1927 | scope: {
1928 | data: '='
1929 | },
1930 | controller: function ($scope) {
1931 | $scope.tableOptions = {
1932 | initialSorts: [
1933 | { id: 'value', dir: '-' }
1934 | ]
1935 | };
1936 | $scope.columns = [
1937 | { id: 'name', key: 'name', label: 'Name' },
1938 | { id: 'value', key: 'value', label: 'Value', sort: 'number' }
1939 | ];
1940 | },
1941 | link: function postLink(scope) {
1942 | scope.$watch('data', function (data) {
1943 | if (data) {
1944 | scope.items = data;
1945 | }
1946 | });
1947 | }
1948 | };
1949 | });
1950 | angular.module("ui.widgets").run(["$templateCache", function($templateCache) {
1951 |
1952 | $templateCache.put("template/widgets/barChart/barChart.html",
1953 | "\n" +
1954 | "
\n" +
1955 | " {{start|date:'HH:mm:ss'}} - {{end|date:'HH:mm:ss'}} \n" +
1956 | "
\n" +
1957 | "
\n" +
1966 | " \n" +
1967 | "
"
1968 | );
1969 |
1970 | $templateCache.put("template/widgets/historicalChart/historicalChart.html",
1971 | "\n" +
1972 | "
\n" +
1980 | "
\n" +
1981 | "
"
1982 | );
1983 |
1984 | $templateCache.put("template/widgets/metricsChart/metricsChart.html",
1985 | "\n" +
1986 | "
\n" +
1987 | " {{start|date:'HH:mm:ss'}} - {{end|date:'HH:mm:ss'}} \n" +
1988 | " \n" +
1991 | "
\n" +
1992 | "
\n" +
2006 | " \n" +
2007 | "
"
2008 | );
2009 |
2010 | $templateCache.put("template/widgets/nvd3LineChart/nvd3LineChart.html",
2011 | "\n" +
2012 | "
\n" +
2013 | " {{start|date:'HH:mm:ss'}} - {{end|date:'HH:mm:ss'}} \n" +
2014 | "
\n" +
2015 | "
\n" +
2029 | " \n" +
2030 | "
"
2031 | );
2032 |
2033 | $templateCache.put("template/widgets/pieChart/pieChart.html",
2034 | "\n" +
2035 | "\n" +
2044 | " \n" +
2045 | "
"
2046 | );
2047 |
2048 | $templateCache.put("template/widgets/random/random.html",
2049 | "\n" +
2050 | " Random Number\n" +
2051 | "
{{number}}
\n" +
2052 | "
"
2053 | );
2054 |
2055 | $templateCache.put("template/widgets/scopeWatch/scopeWatch.html",
2056 | "\n" +
2057 | " Value\n" +
2058 | "
{{scopeValue || 'no data'}}
\n" +
2059 | "
"
2060 | );
2061 |
2062 | $templateCache.put("template/widgets/select/select.html",
2063 | "\n" +
2064 | " {{label}} \n" +
2066 | " \n" +
2067 | " \n" +
2068 | " \n" +
2069 | " \n" +
2070 | " \n" +
2071 | " \n" +
2072 | "
"
2073 | );
2074 |
2075 | $templateCache.put("template/widgets/time/time.html",
2076 | "\n" +
2077 | " Time\n" +
2078 | "
{{time}}
\n" +
2079 | "
"
2080 | );
2081 |
2082 | $templateCache.put("template/widgets/topN/topN.html",
2083 | "\n" +
2084 | " \n" +
2088 | " \n" +
2089 | "
"
2090 | );
2091 |
2092 | }]);
2093 |
--------------------------------------------------------------------------------
/docs/AngularJSDashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dtpublic/malhar-angular-widgets/df8f3dacb0f6737334753c6b6df9f00dd16e7d6d/docs/AngularJSDashboard.png
--------------------------------------------------------------------------------
/karma-e2e.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // http://karma-runner.github.io/0.10/config/configuration-file.html
3 |
4 | module.exports = function(config) {
5 | config.set({
6 | // base path, that will be used to resolve files and exclude
7 | basePath: '',
8 |
9 | // testing framework to use (jasmine/mocha/qunit/...)
10 | frameworks: ['ng-scenario'],
11 |
12 | // list of files / patterns to load in the browser
13 | files: [
14 | 'test/e2e/**/*.js'
15 | ],
16 |
17 | // list of files / patterns to exclude
18 | exclude: [],
19 |
20 | // web server port
21 | port: 8080,
22 |
23 | // level of logging
24 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
25 | logLevel: config.LOG_INFO,
26 |
27 |
28 | // enable / disable watching file and executing tests whenever any file changes
29 | autoWatch: false,
30 |
31 |
32 | // Start these browsers, currently available:
33 | // - Chrome
34 | // - ChromeCanary
35 | // - Firefox
36 | // - Opera
37 | // - Safari (only Mac)
38 | // - PhantomJS
39 | // - IE (only Windows)
40 | browsers: ['Chrome'],
41 |
42 |
43 | // Continuous Integration mode
44 | // if true, it capture browsers, run tests and exit
45 | singleRun: false
46 |
47 | // Uncomment the following lines if you are using grunt's server to run the tests
48 | // proxies: {
49 | // '/': 'http://localhost:9000/'
50 | // },
51 | // URL root prevent conflicts with the site root
52 | // urlRoot: '_karma_'
53 | });
54 | };
55 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // http://karma-runner.github.io/0.10/config/configuration-file.html
3 |
4 | module.exports = function(config) {
5 | config.set({
6 | // base path, that will be used to resolve files and exclude
7 | basePath: '',
8 |
9 | // testing framework to use (jasmine/mocha/qunit/...)
10 | frameworks: ['jasmine'],
11 |
12 | // list of files / patterns to load in the browser
13 | files: [
14 | 'bower_components/jquery/jquery.js',
15 | 'bower_components/jquery-ui/jquery-ui.js',
16 | 'bower_components/lodash/dist/lodash.js',
17 | 'bower_components/hamsterjs/hamster.js',
18 | 'bower_components/angular/angular.js',
19 | 'bower_components/angular-mocks/angular-mocks.js',
20 | 'bower_components/malhar-angular-table/dist/mlhr-table.js',
21 | 'bower_components/angular-ui-sortable/sortable.js',
22 | 'bower_components/angular-sanitize/angular-sanitize.js',
23 | 'bower_components/angular-mousewheel/mousewheel.js',
24 | 'bower_components/angularjs-nvd3-directives/dist/angularjs-nvd3-directives.min.js',
25 | 'bower_components/pines-notify/pnotify.core.js',
26 | 'bower_components/angular-pines-notify/src/pnotify.js',
27 | 'bower_components/visibilityjs/lib/visibility.core.js',
28 | 'src/vendor/{,*/}*.js',
29 | 'src/modules.js',
30 | 'src/service/{,*/}*.js',
31 | 'src/widgets/{,*/}*.js',
32 | 'src/models/{,*/}*.js',
33 | 'template/templates.js',
34 | 'test/mock/**/*.js',
35 | 'test/spec/**/*.js'
36 | ],
37 |
38 | // list of files / patterns to exclude
39 | exclude: [],
40 |
41 | // web server port
42 | port: 8080,
43 |
44 | // level of logging
45 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
46 | logLevel: config.LOG_INFO,
47 |
48 |
49 | // enable / disable watching file and executing tests whenever any file changes
50 | autoWatch: false,
51 |
52 | // Start these browsers, currently available:
53 | // - Chrome
54 | // - ChromeCanary
55 | // - Firefox
56 | // - Opera
57 | // - Safari (only Mac)
58 | // - PhantomJS
59 | // - IE (only Windows)
60 | browsers: ['PhantomJS'],
61 |
62 | // Continuous Integration mode
63 | // if true, it capture browsers, run tests and exit
64 | singleRun: false
65 | });
66 | };
67 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-ui-widgets",
3 | "version": "0.2.0",
4 | "author": "https://github.com/DataTorrent/malhar-angular-widgets/graphs/contributors",
5 | "homepage": "https://github.com/DataTorrent/malhar-angular-widgets",
6 | "license": "Apache License, v2.0",
7 | "devDependencies": {
8 | "grunt": "~0.4.1",
9 | "grunt-contrib-copy": "~0.4.1",
10 | "grunt-contrib-concat": "~0.3.0",
11 | "grunt-contrib-jshint": "~0.6.0",
12 | "grunt-contrib-connect": "~0.5.0",
13 | "grunt-contrib-clean": "~0.5.0",
14 | "grunt-contrib-watch": "~0.5.2",
15 | "grunt-angular-templates": "~0.3.0",
16 | "load-grunt-tasks": "~0.1.0",
17 | "time-grunt": "~0.1.0",
18 | "karma-ng-scenario": "~0.1.0",
19 | "grunt-karma": "~0.6.2",
20 | "karma-script-launcher": "~0.1.0",
21 | "karma-chrome-launcher": "~0.1.2",
22 | "karma-firefox-launcher": "~0.1.3",
23 | "karma-html2js-preprocessor": "~0.1.0",
24 | "karma-jasmine": "~0.1.5",
25 | "karma-coffee-preprocessor": "~0.1.2",
26 | "karma-phantomjs-launcher": "~0.1.1",
27 | "karma": "~0.10.9",
28 | "karma-ng-html2js-preprocessor": "~0.1.0"
29 | },
30 | "engines": {
31 | "node": ">=0.8.0"
32 | },
33 | "scripts": {
34 | "test": "grunt test"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/models/random.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | angular.module('ui.models')
20 | .factory('RandomBaseDataModel', function (WidgetDataModel, Visibility) {
21 | function RandomBaseDataModel() {
22 | }
23 |
24 | RandomBaseDataModel.prototype = Object.create(WidgetDataModel.prototype);
25 | RandomBaseDataModel.prototype.constructor = WidgetDataModel;
26 |
27 | angular.extend(RandomBaseDataModel.prototype, {
28 | init: function () {
29 | this.stopUpdates = false;
30 | this.visibilityListener = Visibility.change(function (e, state) {
31 | if (state === 'hidden') {
32 | this.stopUpdates = true;
33 | } else {
34 | this.stopUpdates = false;
35 | }
36 | }.bind(this));
37 | },
38 |
39 | updateScope: function (data) {
40 | if (!this.stopUpdates) {
41 | WidgetDataModel.prototype.updateScope.call(this, data);
42 | }
43 | },
44 |
45 | destroy: function () {
46 | WidgetDataModel.prototype.destroy.call(this);
47 | Visibility.unbind(this.visibilityListener);
48 | }
49 | });
50 |
51 | return RandomBaseDataModel;
52 | })
53 | .factory('RandomPercentageDataModel', function (RandomBaseDataModel, $interval) {
54 | function RandomPercentageDataModel() {
55 | }
56 |
57 | RandomPercentageDataModel.prototype = Object.create(RandomBaseDataModel.prototype);
58 |
59 | RandomPercentageDataModel.prototype.init = function () {
60 | var value = 50;
61 |
62 | this.intervalPromise = $interval(function () {
63 | value += Math.random() * 40 - 20;
64 | value = value < 0 ? 0 : value > 100 ? 100 : value;
65 |
66 | this.updateScope(value);
67 | }.bind(this), 500);
68 | };
69 |
70 | RandomPercentageDataModel.prototype.destroy = function () {
71 | RandomBaseDataModel.prototype.destroy.call(this);
72 | $interval.cancel(this.intervalPromise);
73 | };
74 |
75 | return RandomPercentageDataModel;
76 | })
77 | .factory('RandomTopNDataModel', function (RandomBaseDataModel, $interval) {
78 | function RandomTopNDataModel() {
79 | }
80 |
81 | RandomTopNDataModel.prototype = Object.create(RandomBaseDataModel.prototype);
82 |
83 | RandomTopNDataModel.prototype.init = function () {
84 | this.intervalPromise = $interval(function () {
85 | var topTen = _.map(_.range(0, 10), function (index) {
86 | return {
87 | name: 'item' + index,
88 | value: Math.floor(Math.random() * 100)
89 | };
90 | });
91 | this.updateScope(topTen);
92 | }.bind(this), 500);
93 | };
94 |
95 | RandomTopNDataModel.prototype.destroy = function () {
96 | RandomBaseDataModel.prototype.destroy.call(this);
97 | $interval.cancel(this.intervalPromise);
98 | };
99 |
100 | return RandomTopNDataModel;
101 | })
102 | .factory('RandomBaseTimeSeriesDataModel', function (RandomBaseDataModel, $interval) {
103 | function RandomTimeSeriesDataModel(options) {
104 | this.upperBound = (options && options.upperBound) ? options.upperBound : 100;
105 | this.rate = (options && options.rate) ? options.rate : Math.round(this.upperBound / 2);
106 | }
107 |
108 | RandomTimeSeriesDataModel.prototype = Object.create(RandomBaseDataModel.prototype);
109 | RandomTimeSeriesDataModel.prototype.constructor = RandomBaseDataModel;
110 |
111 | angular.extend(RandomTimeSeriesDataModel.prototype, {
112 | init: function () {
113 | RandomBaseDataModel.prototype.init.call(this);
114 |
115 | var max = 30;
116 | var upperBound = this.upperBound;
117 | var data = [];
118 | var chartValue = Math.round(upperBound / 2);
119 | var rate = this.rate;
120 |
121 | function nextValue() {
122 | chartValue += Math.random() * rate - rate / 2;
123 | chartValue = chartValue < 0 ? 0 : chartValue > upperBound ? upperBound : chartValue;
124 | return Math.round(chartValue);
125 | }
126 |
127 | var now = Date.now();
128 | for (var i = max - 1; i >= 0; i--) {
129 | data.push({
130 | timestamp: now - i * 1000,
131 | value: nextValue()
132 | });
133 | }
134 |
135 | this.updateScope(data);
136 |
137 | this.intervalPromise = $interval(function () {
138 | if (data.length >= max) {
139 | data.shift();
140 | }
141 | data.push({
142 | timestamp: Date.now(),
143 | value: nextValue()
144 | });
145 |
146 | this.updateScope(data);
147 | }.bind(this), 1000);
148 | },
149 |
150 | destroy: function () {
151 | RandomBaseDataModel.prototype.destroy.call(this);
152 | $interval.cancel(this.intervalPromise);
153 | }
154 | });
155 |
156 | return RandomTimeSeriesDataModel;
157 | })
158 | .factory('RandomTimeSeriesDataModel', function (RandomBaseTimeSeriesDataModel) {
159 | function RandomTimeSeriesDataModel(options) {
160 | RandomBaseTimeSeriesDataModel.call(this, options);
161 | }
162 |
163 | RandomTimeSeriesDataModel.prototype = Object.create(RandomBaseTimeSeriesDataModel.prototype);
164 |
165 | angular.extend(RandomTimeSeriesDataModel.prototype, {
166 | updateScope: function (data) {
167 | var chart = {
168 | data: data,
169 | max: 30,
170 | chartOptions: {
171 | vAxis: {}
172 | }
173 | };
174 |
175 | RandomBaseTimeSeriesDataModel.prototype.updateScope.call(this, chart);
176 | }
177 | });
178 |
179 | return RandomTimeSeriesDataModel;
180 | })
181 | .factory('RandomMetricsTimeSeriesDataModel', function (RandomBaseTimeSeriesDataModel) {
182 | function RandomMetricsTimeSeriesDataModel(options) {
183 | RandomBaseTimeSeriesDataModel.call(this, options);
184 | }
185 |
186 | RandomMetricsTimeSeriesDataModel.prototype = Object.create(RandomBaseTimeSeriesDataModel.prototype);
187 |
188 | angular.extend(RandomMetricsTimeSeriesDataModel.prototype, {
189 | updateScope: function (data) {
190 | var chart = [
191 | {
192 | key: 'Stream1',
193 | values: data
194 | },
195 | {
196 | key: 'Stream2',
197 | values: _.map(data, function (item) {
198 | return { timestamp: item.timestamp, value: item.value + 10 };
199 | })
200 | }
201 | ];
202 |
203 | RandomBaseTimeSeriesDataModel.prototype.updateScope.call(this, chart);
204 | }
205 | });
206 |
207 | return RandomMetricsTimeSeriesDataModel;
208 | })
209 | .factory('RandomNVD3TimeSeriesDataModel', function (RandomBaseTimeSeriesDataModel) {
210 | function RandomTimeSeriesDataModel(options) {
211 | RandomBaseTimeSeriesDataModel.call(this, options);
212 | }
213 |
214 | RandomTimeSeriesDataModel.prototype = Object.create(RandomBaseTimeSeriesDataModel.prototype);
215 |
216 | angular.extend(RandomTimeSeriesDataModel.prototype, {
217 | updateScope: function (data) {
218 | var chart = [
219 | {
220 | key: 'Data',
221 | values: data
222 | }
223 | ];
224 |
225 | RandomBaseTimeSeriesDataModel.prototype.updateScope.call(this, chart);
226 | }
227 | });
228 |
229 | return RandomTimeSeriesDataModel;
230 | })
231 | .factory('RandomMinutesDataModel', function (RandomBaseDataModel, $interval) {
232 | function RandomTimeSeriesDataModel(options) {
233 | this.limit = (options && options.limit) ? options.limit : 500;
234 | }
235 |
236 | RandomTimeSeriesDataModel.prototype = Object.create(RandomBaseDataModel.prototype);
237 |
238 | RandomTimeSeriesDataModel.prototype.init = function () {
239 | this.generateChart();
240 | this.intervalPromise = $interval(this.generateChart.bind(this), 2000);
241 | };
242 |
243 | RandomTimeSeriesDataModel.prototype.generateChart = function () {
244 | var minuteCount = 30;
245 | var data = [];
246 | var limit = this.limit;
247 | var chartValue = limit / 2;
248 |
249 | function nextValue() {
250 | chartValue += Math.random() * (limit * 0.4) - (limit * 0.2);
251 | chartValue = chartValue < 0 ? 0 : chartValue > limit ? limit : chartValue;
252 | return chartValue;
253 | }
254 |
255 | var now = Date.now();
256 |
257 | for (var i = minuteCount - 1; i >= 0; i--) {
258 | data.push({
259 | timestamp: now - i * 1000 * 60,
260 | value: nextValue()
261 | });
262 | }
263 |
264 | var widgetData = [
265 | {
266 | key: 'Data',
267 | values: data
268 | }
269 | ];
270 |
271 | this.updateScope(widgetData);
272 | };
273 |
274 | RandomTimeSeriesDataModel.prototype.destroy = function () {
275 | RandomBaseDataModel.prototype.destroy.call(this);
276 | $interval.cancel(this.intervalPromise);
277 | };
278 |
279 | return RandomTimeSeriesDataModel;
280 | });
--------------------------------------------------------------------------------
/src/models/websocket.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | angular.module('ui.models')
20 | .factory('WebSocketDataModel', function (WidgetDataModel, webSocket) {
21 | function WebSocketDataModel() {
22 | }
23 |
24 | WebSocketDataModel.prototype = Object.create(WidgetDataModel.prototype);
25 |
26 | WebSocketDataModel.prototype.init = function () {
27 | this.topic = null;
28 | this.callback = null;
29 | if (this.dataModelOptions && this.dataModelOptions.defaultTopic) {
30 | this.update(this.dataModelOptions.defaultTopic);
31 | }
32 | };
33 |
34 | WebSocketDataModel.prototype.update = function (newTopic) {
35 | var that = this;
36 |
37 | if (this.topic && this.callback) {
38 | webSocket.unsubscribe(this.topic, this.callback);
39 | }
40 |
41 | this.callback = function (message) {
42 | that.updateScope(message);
43 | that.widgetScope.$apply();
44 | };
45 |
46 | this.topic = newTopic;
47 | webSocket.subscribe(this.topic, this.callback, this.widgetScope);
48 | };
49 |
50 | WebSocketDataModel.prototype.destroy = function () {
51 | WidgetDataModel.prototype.destroy.call(this);
52 |
53 | if (this.topic && this.callback) {
54 | webSocket.unsubscribe(this.topic, this.callback);
55 | }
56 | };
57 |
58 | return WebSocketDataModel;
59 | })
60 | .factory('TimeSeriesDataModel', function (WebSocketDataModel) {
61 | function TimeSeriesDataModel() {
62 | }
63 |
64 | TimeSeriesDataModel.prototype = Object.create(WebSocketDataModel.prototype);
65 |
66 | TimeSeriesDataModel.prototype.init = function () {
67 | WebSocketDataModel.prototype.init.call(this);
68 | };
69 |
70 | TimeSeriesDataModel.prototype.update = function (newTopic) {
71 | WebSocketDataModel.prototype.update.call(this, newTopic);
72 | this.items = [];
73 | };
74 |
75 | TimeSeriesDataModel.prototype.updateScope = function (value) {
76 | value = _.isArray(value) ? value[0] : value;
77 |
78 | this.items.push({
79 | timestamp: parseInt(value.timestamp, 10), //TODO
80 | value: parseInt(value.value, 10) //TODO
81 | });
82 |
83 | if (this.items.length > 100) { //TODO
84 | this.items.shift();
85 | }
86 |
87 | var chart = {
88 | data: this.items,
89 | max: 30
90 | };
91 |
92 | WebSocketDataModel.prototype.updateScope.call(this, chart);
93 | this.data = [];
94 | };
95 |
96 | return TimeSeriesDataModel;
97 | })
98 | .factory('PieChartDataModel', function (WebSocketDataModel) {
99 | function PieChartDataModel() {
100 | }
101 |
102 | PieChartDataModel.prototype = Object.create(WebSocketDataModel.prototype);
103 |
104 | PieChartDataModel.prototype.init = function () {
105 | WebSocketDataModel.prototype.init.call(this);
106 | this.data = [];
107 | };
108 |
109 | PieChartDataModel.prototype.update = function (newTopic) {
110 | WebSocketDataModel.prototype.update.call(this, newTopic);
111 | };
112 |
113 | PieChartDataModel.prototype.updateScope = function (value) {
114 | var sum = _.reduce(value, function (memo, item) {
115 | return memo + parseFloat(item.value);
116 | }, 0);
117 |
118 | var sectors = _.map(value, function (item) {
119 | return {
120 | key: item.label,
121 | y: item.value / sum
122 | };
123 | });
124 |
125 | sectors = _.sortBy(sectors, function (item) {
126 | return item.key;
127 | });
128 |
129 | WebSocketDataModel.prototype.updateScope.call(this, sectors);
130 | };
131 |
132 | return PieChartDataModel;
133 | });
134 |
--------------------------------------------------------------------------------
/src/modules.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | angular.module('ui.widgets', ['datatorrent.mlhrTable', 'nvd3ChartDirectives']);
18 | angular.module('ui.websocket', ['ui.visibility', 'ui.notify']);
19 | angular.module('ui.models', ['ui.visibility', 'ui.websocket']);
20 |
--------------------------------------------------------------------------------
/src/service/visibility.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | angular.module('ui.visibility', [])
20 | .factory('Visibility', function ($window) {
21 | return $window.Visibility;
22 | });
--------------------------------------------------------------------------------
/src/service/websocket.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | angular.module('ui.websocket')
20 | .factory('Visibility', function ($window) {
21 | return $window.Visibility;
22 | })
23 | .provider('webSocket', function () {
24 | var visibilityTimeout = 20000;
25 | var maxConnectionAttempts = 5;
26 | var connectionAttemptInterval = 2000;
27 | var webSocketURL;
28 | var explicitConnect;
29 | var closeRequested;
30 |
31 | return {
32 | $get: function ($q, $rootScope, $timeout, notificationService, Visibility, $log, $window) {
33 |
34 | var socket;
35 |
36 | var webSocketService;
37 |
38 | var deferred = $q.defer();
39 |
40 | var webSocketError = false;
41 |
42 | var connectionAttempts = 0;
43 |
44 | var onopen = function () {
45 | $log.info('WebSocket connection has been made. URL: ', webSocketURL);
46 | connectionAttempts = 0;
47 | deferred.resolve();
48 | $rootScope.$apply();
49 | };
50 |
51 | var onclose = function() {
52 | // Reset the deferred
53 | deferred = $q.defer();
54 |
55 | // Check if this close was requested
56 | if (!closeRequested) {
57 |
58 | // Check connectionAttempts count
59 | if (connectionAttempts < maxConnectionAttempts) {
60 | // Try to re-establish connection
61 | var url = this.url;
62 | connectionAttempts++;
63 | $timeout(function() {
64 | $log.info('Attempting to reconnect to websocket');
65 | webSocketService.connect(url);
66 | }, connectionAttemptInterval);
67 | }
68 |
69 | else {
70 | $log.error('Could not re-establish the WebSocket connection.');
71 | notificationService.notify({
72 | type: 'error',
73 | title: 'Could not re-establish WebSocket Connection',
74 | text: 'The dashboard lost contact with your DataTorrent Gateway for over ' +
75 | Math.round((maxConnectionAttempts * connectionAttemptInterval)/1000) +
76 | ' seconds. Double-check your connection and try refreshing the page.'
77 | });
78 | }
79 |
80 | }
81 |
82 | // Otherwise reset some flags
83 | else {
84 | connectionAttempts = 0;
85 | closeRequested = false;
86 | }
87 | };
88 |
89 | // TODO: listeners for error and close, exposed on
90 | // service itself
91 | var onerror = function () {
92 | webSocketError = true;
93 | $log.error('WebSocket encountered an error');
94 | };
95 |
96 | var onmessage = function (event) {
97 | if (stopUpdates) { // stop updates if page is inactive
98 | return;
99 | }
100 |
101 | var message = JSON.parse(event.data);
102 |
103 | var topic = message.topic;
104 |
105 | if (topicMap.hasOwnProperty(topic)) {
106 | if ($window.WS_DEBUG) {
107 | if ($window.WS_DEBUG === true) {
108 | $log.debug('WebSocket ', topic, ' => ', message.data);
109 | }
110 | else {
111 | var search = new RegExp($window.WS_DEBUG + '');
112 | if (search.test(topic)) {
113 | $log.debug('WebSocket ', topic, ' => ', message.data);
114 | }
115 | }
116 | }
117 | topicMap[topic].fire(message.data);
118 | }
119 | };
120 |
121 | var topicMap = {}; // topic -> [callbacks] mapping
122 |
123 | var stopUpdates = false;
124 |
125 |
126 | if (Visibility.isSupported()) {
127 | var timeoutPromise;
128 |
129 | Visibility.change(angular.bind(this, function (e, state) {
130 | if (state === 'hidden') {
131 | timeoutPromise = $timeout(function () {
132 | stopUpdates = true;
133 | timeoutPromise = null;
134 | }, visibilityTimeout);
135 | } else {
136 | stopUpdates = false;
137 |
138 | if (timeoutPromise) {
139 | $timeout.cancel(timeoutPromise);
140 | }
141 |
142 | $log.debug('visible');
143 | }
144 | }));
145 | }
146 |
147 | webSocketService = {
148 | send: function (message) {
149 | var msg = JSON.stringify(message);
150 |
151 | deferred.promise.then(function () {
152 | $log.debug('send ' + msg);
153 | socket.send(msg);
154 | });
155 | },
156 |
157 | subscribe: function (topic, callback, $scope) {
158 | var callbacks = topicMap[topic];
159 |
160 | // If a jQuery.Callbacks object has not been created for this
161 | // topic, one should be created and a "subscribe" message
162 | // should be sent.
163 | if (!callbacks) {
164 |
165 | // send the subscribe message
166 | var message = { type: 'subscribe', topic: topic };
167 | this.send(message);
168 |
169 | // create the Callbacks object
170 | callbacks = jQuery.Callbacks();
171 | topicMap[topic] = callbacks;
172 | }
173 |
174 | // When scope is provided...
175 | if ($scope) {
176 |
177 | // ...it's $digest method should be called
178 | // after the callback has been triggered, so
179 | // we have to wrap the function.
180 | var wrappedCallback = function () {
181 | callback.apply({}, arguments);
182 | $scope.$digest();
183 | };
184 | callbacks.add(wrappedCallback);
185 |
186 | // We should also be listening for the destroy
187 | // event so we can automatically unsubscribe.
188 | $scope.$on('$destroy', angular.bind(this, function () {
189 | this.unsubscribe(topic, wrappedCallback);
190 | }));
191 |
192 | return wrappedCallback;
193 | }
194 | else {
195 | callbacks.add(callback);
196 | return callback;
197 | }
198 | },
199 | // Unsubscribe callback is optional. If callback is omitted, all callbacks are removed.
200 | unsubscribe: function (topic, callback) {
201 | if (topicMap.hasOwnProperty(topic)) {
202 | var callbacks = topicMap[topic];
203 | if (callback) {
204 | callbacks.remove(callback);
205 | }
206 |
207 | // If no callback is provided proceed to delete all existing callbacks
208 | // Or check callbacks.has() which will return false if there are no more handlers
209 | // registered in this callbacks collection.
210 | if (!callback || !callbacks.has()) {
211 |
212 | // Send the unsubscribe message first
213 | var message = { type: 'unsubscribe', topic: topic };
214 | this.send(message);
215 |
216 | // Then remove the callbacks object for this topic
217 | delete topicMap[topic];
218 |
219 | }
220 | }
221 | },
222 |
223 | disconnect: function() {
224 | if (!socket) {
225 | return;
226 | }
227 | closeRequested = true;
228 | socket.close();
229 | },
230 |
231 | connect: function(url) {
232 | if (!url) {
233 | if (webSocketURL) {
234 | url = webSocketURL;
235 | }
236 | else {
237 | throw new TypeError('No WebSocket connection URL specified in connect method');
238 | }
239 | }
240 |
241 | if (socket && socket.readyState === $window.WebSocket.OPEN) {
242 | $log.info('webSocket.connect called, but webSocket connection already established.');
243 | return;
244 | }
245 |
246 | socket = new $window.WebSocket(url);
247 | // deferred = $q.defer();
248 | socket.onopen = onopen;
249 | socket.onclose = onclose;
250 | socket.onerror = onerror;
251 | socket.onmessage = onmessage;
252 |
253 | // resubscribe to topics
254 | // send the subscribe message
255 | for (var topic in topicMap) {
256 | if (topicMap.hasOwnProperty(topic)) {
257 | var message = { type: 'subscribe', topic: topic };
258 | this.send(message);
259 | }
260 | }
261 | }
262 | };
263 |
264 | if (!explicitConnect) {
265 | webSocketService.connect();
266 | }
267 |
268 | return webSocketService;
269 | },
270 |
271 | setVisibilityTimeout: function (timeout) {
272 | visibilityTimeout = timeout;
273 | },
274 |
275 | setWebSocketURL: function (wsURL) {
276 | webSocketURL = wsURL;
277 | },
278 |
279 | setExplicitConnection: function(flag) {
280 | explicitConnect = flag;
281 | },
282 |
283 | setMaxConnectionAttempts: function(max) {
284 | maxConnectionAttempts = max;
285 | },
286 |
287 | setConnectionAttemptInterval: function(interval) {
288 | maxConnectionAttempts = interval;
289 | }
290 | };
291 | });
292 |
--------------------------------------------------------------------------------
/src/vendor/gauge_vendor.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copied from https://github.com/lithiumtech/angular_and_d3
3 | */
4 |
5 | /* jshint ignore:start */
6 |
7 | function Gauge(element, configuration)
8 | {
9 | this.element = element;
10 |
11 | var self = this; // for internal d3 functions
12 |
13 | this.configure = function(configuration)
14 | {
15 | this.config = configuration;
16 |
17 | this.config.size = this.config.size * 0.9;
18 |
19 | this.config.raduis = this.config.size * 0.97 / 2;
20 | this.config.cx = this.config.size / 2;
21 | this.config.cy = this.config.size / 2;
22 |
23 | this.config.min = undefined != configuration.min ? configuration.min : 0;
24 | this.config.max = undefined != configuration.max ? configuration.max : 100;
25 | this.config.range = this.config.max - this.config.min;
26 |
27 | this.config.majorTicks = configuration.majorTicks || 5;
28 | this.config.minorTicks = configuration.minorTicks || 2;
29 |
30 | this.config.greenColor = configuration.greenColor || "#109618";
31 | this.config.yellowColor = configuration.yellowColor || "#FF9900";
32 | this.config.redColor = configuration.redColor || "#DC3912";
33 |
34 | this.config.transitionDuration = configuration.transitionDuration || 500;
35 | }
36 |
37 | this.render = function()
38 | {
39 | this.body = d3.select( this.element )
40 | .append("svg:svg")
41 | .attr("class", "gauge")
42 | .attr("width", this.config.size)
43 | .attr("height", this.config.size);
44 |
45 | this.body.append("svg:circle")
46 | .attr("cx", this.config.cx)
47 | .attr("cy", this.config.cy)
48 | .attr("r", this.config.raduis)
49 | .style("fill", "#ccc")
50 | .style("stroke", "#000")
51 | .style("stroke-width", "0.5px");
52 |
53 | this.body.append("svg:circle")
54 | .attr("cx", this.config.cx)
55 | .attr("cy", this.config.cy)
56 | .attr("r", 0.9 * this.config.raduis)
57 | .style("fill", "#fff")
58 | .style("stroke", "#e0e0e0")
59 | .style("stroke-width", "2px");
60 |
61 | for (var index in this.config.greenZones)
62 | {
63 | this.drawBand(this.config.greenZones[index].from, this.config.greenZones[index].to, self.config.greenColor);
64 | }
65 |
66 | for (var index in this.config.yellowZones)
67 | {
68 | this.drawBand(this.config.yellowZones[index].from, this.config.yellowZones[index].to, self.config.yellowColor);
69 | }
70 |
71 | for (var index in this.config.redZones)
72 | {
73 | this.drawBand(this.config.redZones[index].from, this.config.redZones[index].to, self.config.redColor);
74 | }
75 |
76 | if (undefined != this.config.label)
77 | {
78 | var fontSize = Math.round(this.config.size / 9);
79 | this.body.append("svg:text")
80 | .attr("x", this.config.cx)
81 | .attr("y", this.config.cy / 2 + fontSize / 2)
82 | .attr("dy", fontSize / 2)
83 | .attr("text-anchor", "middle")
84 | .text(this.config.label)
85 | .style("font-size", fontSize + "px")
86 | .style("fill", "#333")
87 | .style("stroke-width", "0px");
88 | }
89 |
90 | var fontSize = Math.round(this.config.size / 16);
91 | var majorDelta = this.config.range / (this.config.majorTicks - 1);
92 | for (var major = this.config.min; major <= this.config.max; major += majorDelta)
93 | {
94 | var minorDelta = majorDelta / this.config.minorTicks;
95 | for (var minor = major + minorDelta; minor < Math.min(major + majorDelta, this.config.max); minor += minorDelta)
96 | {
97 | var point1 = this.valueToPoint(minor, 0.75);
98 | var point2 = this.valueToPoint(minor, 0.85);
99 |
100 | this.body.append("svg:line")
101 | .attr("x1", point1.x)
102 | .attr("y1", point1.y)
103 | .attr("x2", point2.x)
104 | .attr("y2", point2.y)
105 | .style("stroke", "#666")
106 | .style("stroke-width", "1px");
107 | }
108 |
109 | var point1 = this.valueToPoint(major, 0.7);
110 | var point2 = this.valueToPoint(major, 0.85);
111 |
112 | this.body.append("svg:line")
113 | .attr("x1", point1.x)
114 | .attr("y1", point1.y)
115 | .attr("x2", point2.x)
116 | .attr("y2", point2.y)
117 | .style("stroke", "#333")
118 | .style("stroke-width", "2px");
119 |
120 | if (major == this.config.min || major == this.config.max)
121 | {
122 | var point = this.valueToPoint(major, 0.63);
123 |
124 | this.body.append("svg:text")
125 | .attr("x", point.x)
126 | .attr("y", point.y)
127 | .attr("dy", fontSize / 3)
128 | .attr("text-anchor", major == this.config.min ? "start" : "end")
129 | .text(major)
130 | .style("font-size", fontSize + "px")
131 | .style("fill", "#333")
132 | .style("stroke-width", "0px");
133 | }
134 | }
135 |
136 | var pointerContainer = this.body.append("svg:g").attr("class", "pointerContainer");
137 |
138 | var midValue = (this.config.min + this.config.max) / 2;
139 |
140 | var pointerPath = this.buildPointerPath(midValue);
141 |
142 | var pointerLine = d3.svg.line()
143 | .x(function(d) { return d.x })
144 | .y(function(d) { return d.y })
145 | .interpolate("basis");
146 |
147 | pointerContainer.selectAll("path")
148 | .data([pointerPath])
149 | .enter()
150 | .append("svg:path")
151 | .attr("d", pointerLine)
152 | .style("fill", "#dc3912")
153 | .style("stroke", "#c63310")
154 | .style("fill-opacity", 0.7)
155 |
156 | pointerContainer.append("svg:circle")
157 | .attr("cx", this.config.cx)
158 | .attr("cy", this.config.cy)
159 | .attr("r", 0.12 * this.config.raduis)
160 | .style("fill", "#4684EE")
161 | .style("stroke", "#666")
162 | .style("opacity", 1);
163 |
164 | var fontSize = Math.round(this.config.size / 10);
165 | pointerContainer.selectAll("text")
166 | .data([midValue])
167 | .enter()
168 | .append("svg:text")
169 | .attr("x", this.config.cx)
170 | .attr("y", this.config.size - this.config.cy / 4 - fontSize)
171 | .attr("dy", fontSize / 2)
172 | .attr("text-anchor", "middle")
173 | .style("font-size", fontSize + "px")
174 | .style("fill", "#000")
175 | .style("stroke-width", "0px");
176 |
177 | this.redraw(this.config.min, 0);
178 | }
179 |
180 | this.buildPointerPath = function(value)
181 | {
182 | var delta = this.config.range / 13;
183 |
184 | var head = valueToPoint(value, 0.85);
185 | var head1 = valueToPoint(value - delta, 0.12);
186 | var head2 = valueToPoint(value + delta, 0.12);
187 |
188 | var tailValue = value - (this.config.range * (1/(270/360)) / 2);
189 | var tail = valueToPoint(tailValue, 0.28);
190 | var tail1 = valueToPoint(tailValue - delta, 0.12);
191 | var tail2 = valueToPoint(tailValue + delta, 0.12);
192 |
193 | return [head, head1, tail2, tail, tail1, head2, head];
194 |
195 | function valueToPoint(value, factor)
196 | {
197 | var point = self.valueToPoint(value, factor);
198 | point.x -= self.config.cx;
199 | point.y -= self.config.cy;
200 | return point;
201 | }
202 | }
203 |
204 | this.drawBand = function(start, end, color)
205 | {
206 | if (0 >= end - start) return;
207 |
208 | this.body.append("svg:path")
209 | .style("fill", color)
210 | .attr("d", d3.svg.arc()
211 | .startAngle(this.valueToRadians(start))
212 | .endAngle(this.valueToRadians(end))
213 | .innerRadius(0.65 * this.config.raduis)
214 | .outerRadius(0.85 * this.config.raduis))
215 | .attr("transform", function() { return "translate(" + self.config.cx + ", " + self.config.cy + ") rotate(270)" });
216 | }
217 |
218 | this.redraw = function(value, transitionDuration)
219 | {
220 | var pointerContainer = this.body.select(".pointerContainer");
221 |
222 | pointerContainer.selectAll("text").text(Math.round(value));
223 |
224 | var pointer = pointerContainer.selectAll("path");
225 | pointer.transition()
226 | .duration(undefined != transitionDuration ? transitionDuration : this.config.transitionDuration)
227 | //.delay(0)
228 | //.ease("linear")
229 | //.attr("transform", function(d)
230 | .attrTween("transform", function()
231 | {
232 | var pointerValue = value;
233 | if (value > self.config.max) pointerValue = self.config.max + 0.02*self.config.range;
234 | else if (value < self.config.min) pointerValue = self.config.min - 0.02*self.config.range;
235 | var targetRotation = (self.valueToDegrees(pointerValue) - 90);
236 | var currentRotation = self._currentRotation || targetRotation;
237 | self._currentRotation = targetRotation;
238 |
239 | return function(step)
240 | {
241 | var rotation = currentRotation + (targetRotation-currentRotation)*step;
242 | return "translate(" + self.config.cx + ", " + self.config.cy + ") rotate(" + rotation + ")";
243 | }
244 | });
245 | }
246 |
247 | this.valueToDegrees = function(value)
248 | {
249 | // thanks @closealert
250 | //return value / this.config.range * 270 - 45;
251 | return value / this.config.range * 270 - (this.config.min / this.config.range * 270 + 45);
252 | }
253 |
254 | this.valueToRadians = function(value)
255 | {
256 | return this.valueToDegrees(value) * Math.PI / 180;
257 | }
258 |
259 | this.valueToPoint = function(value, factor)
260 | {
261 | return { x: this.config.cx - this.config.raduis * factor * Math.cos(this.valueToRadians(value)),
262 | y: this.config.cy - this.config.raduis * factor * Math.sin(this.valueToRadians(value)) };
263 | }
264 |
265 | // initialization
266 | this.configure(configuration);
267 | }
268 |
269 | /* jshint ignore:end */
--------------------------------------------------------------------------------
/src/vendor/visibly.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * visibly - v0.6 Aug 2011 - Page Visibility API Polyfill
3 | * http://github.com/addyosmani
4 | * Copyright (c) 2011 Addy Osmani
5 | * Dual licensed under the MIT and GPL licenses.
6 | *
7 | * Methods supported:
8 | * visibly.onVisible(callback)
9 | * visibly.onHidden(callback)
10 | * visibly.hidden()
11 | * visibly.visibilityState()
12 | * visibly.visibilitychange(callback(state));
13 | */
14 |
15 | ;(function () {
16 |
17 | window.visibly = {
18 | q: document,
19 | p: undefined,
20 | prefixes: ['webkit', 'ms','o','moz','khtml'],
21 | props: ['VisibilityState', 'visibilitychange', 'Hidden'],
22 | m: ['focus', 'blur'],
23 | visibleCallbacks: [],
24 | hiddenCallbacks: [],
25 | genericCallbacks:[],
26 | _callbacks: [],
27 | cachedPrefix:"",
28 | fn:null,
29 |
30 | onVisible: function (_callback) {
31 | if(typeof _callback == 'function' ){
32 | this.visibleCallbacks.push(_callback);
33 | }
34 | },
35 | onHidden: function (_callback) {
36 | if(typeof _callback == 'function' ){
37 | this.hiddenCallbacks.push(_callback);
38 | }
39 | },
40 | getPrefix:function(){
41 | if(!this.cachedPrefix){
42 | var b;
43 | for(var l=0;b=this.prefixes[l++];){
44 | if(b + this.props[2] in this.q){
45 | this.cachedPrefix = b;
46 | return this.cachedPrefix;
47 | }
48 | }
49 | }
50 | },
51 |
52 | visibilityState:function(){
53 | return this._getProp(0);
54 | },
55 | hidden:function(){
56 | return this._getProp(2);
57 | },
58 | visibilitychange:function(fn){
59 | if(typeof fn == 'function' ){
60 | this.genericCallbacks.push(fn);
61 | }
62 |
63 | var n = this.genericCallbacks.length;
64 | if(n){
65 | if(this.cachedPrefix){
66 | while(n--){
67 | this.genericCallbacks[n].call(this, this.visibilityState());
68 | }
69 | }else{
70 | while(n--){
71 | this.genericCallbacks[n].call(this, arguments[0]);
72 | }
73 | }
74 | }
75 |
76 | },
77 | isSupported: function (index) {
78 | return ((this.cachedPrefix + this.props[2]) in this.q);
79 | },
80 | _getProp:function(index){
81 | return this.q[this.cachedPrefix + this.props[index]];
82 | },
83 | _execute: function (index) {
84 | if (index) {
85 | this._callbacks = (index == 1) ? this.visibleCallbacks : this.hiddenCallbacks;
86 | var n = this._callbacks.length;
87 | while(n--){
88 | this._callbacks[n]();
89 | }
90 | }
91 | },
92 | _visible: function () {
93 | window.visibly._execute(1);
94 | window.visibly.visibilitychange.call(window.visibly, 'visible');
95 | },
96 | _hidden: function () {
97 | window.visibly._execute(2);
98 | window.visibly.visibilitychange.call(window.visibly, 'hidden');
99 | },
100 | _nativeSwitch: function () {
101 | this[this._getProp(2) ? '_hidden' : '_visible']();
102 | },
103 | _listen: function () {
104 | try { /*if no native page visibility support found..*/
105 | if (!(this.isSupported())) {
106 | if (this.q.addEventListener) { /*for browsers without focusin/out support eg. firefox, opera use focus/blur*/
107 | window.addEventListener(this.m[0], this._visible, 1);
108 | window.addEventListener(this.m[1], this._hidden, 1);
109 | } else { /*IE <10s most reliable focus events are onfocusin/onfocusout*/
110 | if (this.q.attachEvent) {
111 | this.q.attachEvent('onfocusin', this._visible);
112 | this.q.attachEvent('onfocusout', this._hidden);
113 | }
114 | }
115 | } else { /*switch support based on prefix detected earlier*/
116 | this.q.addEventListener(this.cachedPrefix + this.props[1], function () {
117 | window.visibly._nativeSwitch.apply(window.visibly, arguments);
118 | }, 1);
119 | }
120 | } catch (e) {}
121 | },
122 | init: function () {
123 | this.getPrefix();
124 | this._listen();
125 | }
126 | };
127 |
128 | this.visibly.init();
129 | })();
130 |
--------------------------------------------------------------------------------
/src/widgets/barChart/barChart.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | angular.module('ui.widgets')
20 | .directive('wtBarChart', function ($filter) {
21 | return {
22 | restrict: 'A',
23 | replace: true,
24 | templateUrl: 'template/widgets/barChart/barChart.html',
25 | scope: {
26 | data: '=data'
27 | },
28 | controller: function ($scope) {
29 | var filter = $filter('date');
30 |
31 | $scope.xAxisTickFormatFunction = function () {
32 | return function(d) {
33 | return filter(d, 'HH:mm');
34 | };
35 | };
36 |
37 | $scope.xFunction = function(){
38 | return function(d) {
39 | return d.timestamp;
40 | };
41 | };
42 | $scope.yFunction = function(){
43 | return function(d) {
44 | return d.value;
45 | };
46 | };
47 | },
48 | link: function postLink(scope) {
49 | scope.$watch('data', function (data) {
50 | if (data && data[0] && data[0].values && (data[0].values.length > 1)) {
51 | var timeseries = _.sortBy(data[0].values, function (item) {
52 | return item.timestamp;
53 | });
54 |
55 | var start = timeseries[0].timestamp;
56 | var end = timeseries[timeseries.length - 1].timestamp;
57 | scope.start = start;
58 | scope.end = end;
59 | }
60 | });
61 | }
62 | };
63 | });
--------------------------------------------------------------------------------
/src/widgets/gauge/gauge.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | angular.module('ui.widgets')
20 | .directive('wtGauge', function () {
21 | return {
22 | replace: true,
23 | scope: {
24 | label: '@',
25 | min: '=',
26 | max: '=',
27 | value: '='
28 | },
29 | link: function (scope, element, attrs) {
30 | var config = {
31 | size: 200,
32 | label: attrs.label,
33 | min: undefined !== scope.min ? scope.min : 0,
34 | max: undefined !== scope.max ? scope.max : 100,
35 | minorTicks: 5
36 | };
37 |
38 | var range = config.max - config.min;
39 | config.yellowZones = [
40 | { from: config.min + range * 0.75, to: config.min + range * 0.9 }
41 | ];
42 | config.redZones = [
43 | { from: config.min + range * 0.9, to: config.max }
44 | ];
45 |
46 | scope.gauge = new Gauge(element[0], config);
47 | scope.gauge.render();
48 |
49 | function update(value) {
50 | var percentage;
51 | if (_.isString(value)) {
52 | percentage = parseFloat(value);
53 | } else if (_.isNumber(value)) {
54 | percentage = value;
55 | }
56 |
57 | if (!_.isUndefined(percentage)) {
58 | scope.gauge.redraw(percentage);
59 | }
60 | }
61 |
62 | update(0);
63 |
64 | scope.$watch('value', function (value) {
65 | if (scope.gauge) {
66 | update(value);
67 | }
68 | });
69 | }
70 | };
71 | });
--------------------------------------------------------------------------------
/src/widgets/historicalChart/historicalChart.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | angular.module('ui.widgets')
20 | .directive('wtHistoricalChart', function () {
21 | return {
22 | restrict: 'A',
23 | replace: true,
24 | templateUrl: 'template/widgets/historicalChart/historicalChart.html',
25 | scope: {
26 | chart: '='
27 | },
28 | controller: function ($scope) {
29 | $scope.mode = 'MINUTES';
30 |
31 | $scope.changeMode = function (mode) {
32 | $scope.mode = mode;
33 | $scope.$emit('modeChanged', mode);
34 | };
35 | }
36 | };
37 | });
--------------------------------------------------------------------------------
/src/widgets/lineChart/lineChart.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | angular.module('ui.widgets')
20 | .directive('wtLineChart', function () {
21 | return {
22 | template: '
',
23 | scope: {
24 | chart: '='
25 | },
26 | replace: true,
27 | link: function postLink(scope, element) {
28 | var lineChart = new google.visualization.LineChart(element[0]);
29 |
30 | function draw(chart) {
31 | var data = chart.data;
32 |
33 | var table = new google.visualization.DataTable();
34 | table.addColumn('datetime');
35 | table.addColumn('number');
36 | table.addRows(data.length);
37 |
38 | var view = new google.visualization.DataView(table);
39 |
40 | for (var i = 0; i < data.length; i++) {
41 | var item = data[i];
42 | table.setCell(i, 0, new Date(item.timestamp));
43 | var value = parseFloat(item.value);
44 | table.setCell(i, 1, value);
45 | }
46 |
47 | var chartOptions = {
48 | legend: 'none',
49 | vAxis: { minValue: 0, maxValue: 100 }
50 | //chartArea: { top: 20, left: 30, height: 240 }
51 | };
52 |
53 | if (chart.max) {
54 | var lastTimestamp;
55 |
56 | if (data.length) {
57 | var last = data[data.length - 1];
58 | lastTimestamp = last.timestamp;
59 | } else {
60 | lastTimestamp = Date.now();
61 | }
62 |
63 | var max = new Date(lastTimestamp);
64 | var min = new Date(lastTimestamp - (chart.max - 1) * 1000);
65 |
66 | angular.extend(chartOptions, {
67 | hAxis: { viewWindow: { min: min, max: max }}
68 | });
69 | }
70 |
71 | if (chart.chartOptions) {
72 | angular.extend(chartOptions, chart.chartOptions);
73 | }
74 |
75 | lineChart.draw(view, chartOptions);
76 | }
77 |
78 | scope.$watch('chart', function (chart) {
79 | if (!chart) {
80 | chart = {
81 | data: [],
82 | max: 30
83 | };
84 | }
85 |
86 | if (chart.data) {
87 | draw(chart);
88 | }
89 | });
90 | }
91 | };
92 | });
--------------------------------------------------------------------------------
/src/widgets/metricsChart/metricsChart.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | angular.module('ui.widgets')
20 | .directive('wtMetricsChart', function ($filter, MetricsChartHistory) {
21 | return {
22 | restrict: 'A',
23 | replace: true,
24 | templateUrl: 'template/widgets/metricsChart/metricsChart.html',
25 | scope: {
26 | data: '=?',
27 | metrics: '=',
28 | controller: '='
29 | },
30 | controller: function ($scope) {
31 | var filter = $filter('date');
32 | var yAxisFilter = $filter('number');
33 |
34 | $scope.xAxisTickFormatFunction = function () {
35 | return function (d) {
36 | return filter(d, 'mm:ss');
37 | };
38 | };
39 |
40 | $scope.yAxisTickFormatFunction = function () {
41 | return function (d) {
42 | return yAxisFilter(d);
43 | };
44 | };
45 |
46 | $scope.xFunction = function () {
47 | return function (d) {
48 | return d.timestamp;
49 | };
50 | };
51 | $scope.yFunction = function () {
52 | return function (d) {
53 | return d.value;
54 | };
55 | };
56 |
57 | $scope.chartCallback = function () { // callback to access nvd3 chart
58 | //console.log('chartCallback');
59 | //console.log(arguments);
60 | //console.log(chart.legend.dispatch.);
61 | //chart.legend.dispatch.on('legendClick', function(newState) {
62 | // console.log(newState);
63 | //});
64 | };
65 |
66 | $scope.maxTimeLimit = 300;
67 |
68 | $scope.options = [
69 | {
70 | value: 30,
71 | label: 'last 30 seconds'
72 | },
73 | {
74 | value: 60,
75 | label: 'last minute'
76 | },
77 | {
78 | value: 120,
79 | label: 'last two minutes'
80 | },
81 | {
82 | value: $scope.maxTimeLimit,
83 | label: 'last 5 minutes'
84 | }
85 | ];
86 | $scope.timeFrame = $scope.options[0];
87 |
88 |
89 | var chartHistory = null;
90 | if ($scope.controller) {
91 | chartHistory = new MetricsChartHistory($scope, $scope.metrics, $scope.maxTimeLimit, $scope.timeFrame.value);
92 | $scope.controller.addPoint = function (point) {
93 | chartHistory.addPoint(point);
94 | };
95 | }
96 |
97 | $scope.timeFrameChanged = function (newTimeFrame) {
98 | if (chartHistory) {
99 | chartHistory.updateChart(Date.now(), newTimeFrame.value);
100 | }
101 | };
102 | },
103 | link: function postLink(scope) {
104 | scope.data = [];
105 | }
106 | };
107 | })
108 | .factory('MetricsChartHistory', function () {
109 | function MetricsChartHistory(scope, metrics, maxTimeLimit, timeLimit) {
110 | this.scope = scope;
111 | this.metrics = metrics;
112 | this.maxTimeLimit = maxTimeLimit;
113 | this.timeLimit = timeLimit;
114 | this.history = [];
115 |
116 | this.series = [];
117 |
118 | _.each(metrics, function (metric) {
119 | this.series.push({
120 | key: metric.key,
121 | disabled: !metric.visible,
122 | color: metric.color
123 | });
124 | }.bind(this));
125 | }
126 |
127 | angular.extend(MetricsChartHistory.prototype, {
128 | updateHistory: function (now, point) {
129 | var historyStartTime = now - this.maxTimeLimit * 1000;
130 |
131 | var ind = _.findIndex(this.history, function (historyPoint) {
132 | return historyPoint.timestamp >= historyStartTime;
133 | });
134 | if (ind > 1) {
135 | this.history = _.rest(this.history, ind - 1);
136 | }
137 |
138 | var historyPoint = {
139 | timestamp: now,
140 | data: point
141 | };
142 | this.history.push(historyPoint);
143 | },
144 |
145 | updateChart: function (now, timeLimit) {
146 | this.timeLimit = timeLimit;
147 |
148 | var startTime = now - 1000 * timeLimit;
149 |
150 | var history = _.filter(this.history, function (historyPoint) { //TODO optimize
151 | return historyPoint.timestamp >= startTime;
152 | });
153 |
154 | _.each(this.metrics, function (metric, index) {
155 | var metricKey = metric.key;
156 |
157 | var values = _.map(history, function (historyPoint) {
158 | return {
159 | timestamp: historyPoint.timestamp,
160 | value: Math.round(parseInt(historyPoint.data[metricKey], 10))
161 | };
162 | });
163 |
164 | this.series[index].values = values;
165 | }.bind(this));
166 |
167 | /*
168 | //TODO this is workaround to have fixed x axis scale when no enough date is available
169 | chart.push({
170 | key: 'Left Value',
171 | values: [
172 | {timestamp: startTime, value: 0}
173 | ]
174 | });
175 | */
176 |
177 | /*
178 | var max = _.max(history, function (historyPoint) { //TODO optimize
179 | return historyPoint.stats.tuplesEmittedPSMA; //TODO
180 | });
181 |
182 | chart.push({
183 | key: 'Upper Value',
184 | values: [
185 | {timestamp: now - 30 * 1000, value: Math.round(max.value * 1.2)}
186 | ]
187 | });
188 | */
189 |
190 | if (history.length > 1) {
191 | this.scope.data = _.clone(this.series);
192 | this.scope.start = Math.min(startTime, _.first(history).timestamp);
193 | this.scope.end = _.last(history).timestamp;
194 | }
195 | },
196 |
197 | addPoint: function (point) {
198 | var now = Date.now();
199 | this.updateHistory(now, point);
200 |
201 | this.updateChart(now, this.timeLimit);
202 | }
203 | });
204 |
205 | return MetricsChartHistory;
206 | });
--------------------------------------------------------------------------------
/src/widgets/nvd3LineChart/nvd3LineChart.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | angular.module('ui.widgets')
20 | .directive('wtNvd3LineChart', function ($filter) {
21 | return {
22 | restrict: 'A',
23 | replace: true,
24 | templateUrl: 'template/widgets/nvd3LineChart/nvd3LineChart.html',
25 | scope: {
26 | data: '=data',
27 | showLegend: '@',
28 | showTimeRange: '=?',
29 | timeAxisFormat: '=?'
30 | },
31 | controller: function ($scope) {
32 | var filter = $filter('date');
33 | var numberFilter = $filter('number');
34 |
35 | $scope.xAxisTickFormatFunction = function () {
36 | return function (d) {
37 | return filter(d, $scope.timeAxisFormat);
38 | };
39 | };
40 |
41 | $scope.yAxisTickFormatFunction = function () {
42 | return function (d) {
43 | if (d > 999) {
44 | var value;
45 | var scale;
46 | if (d < 999999) {
47 | value = Math.round(d/1000);
48 | scale = 'k';
49 | } else {
50 | value = Math.round(d/1000000);
51 | scale = 'm';
52 | }
53 | return numberFilter(value) + scale;
54 | } else {
55 | return numberFilter(d);
56 | }
57 | };
58 | };
59 |
60 | $scope.xFunction = function () {
61 | return function (d) {
62 | return d.timestamp;
63 | };
64 | };
65 | $scope.yFunction = function () {
66 | return function (d) {
67 | return d.value;
68 | };
69 | };
70 | },
71 | link: function postLink(scope, element, attrs) {
72 | if (!_.has(attrs, 'showTimeRange')) {
73 | scope.showTimeRange = true;
74 | }
75 |
76 | scope.timeAxisFormat = scope.timeAxisFormat || 'HH:mm';
77 |
78 | scope.$watch('data', function (data) {
79 | if (data && data[0] && data[0].values && (data[0].values.length > 1)) {
80 | var timeseries = _.sortBy(data[0].values, function (item) {
81 | return item.timestamp;
82 | });
83 |
84 | var start = timeseries[0].timestamp;
85 | var end = timeseries[timeseries.length - 1].timestamp;
86 | scope.start = start;
87 | scope.end = end;
88 | }
89 | });
90 | }
91 | };
92 | });
--------------------------------------------------------------------------------
/src/widgets/pieChart/pieChart.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | angular.module('ui.widgets')
20 | .directive('wtPieChart', function () {
21 | return {
22 | restrict: 'A',
23 | replace: true,
24 | templateUrl: 'template/widgets/pieChart/pieChart.html',
25 | scope: {
26 | data: '=data'
27 | },
28 | controller: function ($scope) {
29 | $scope.xFunction = function(){
30 | return function(d) {
31 | return d.key;
32 | };
33 | };
34 | $scope.yFunction = function(){
35 | return function(d) {
36 | return d.y;
37 | };
38 | };
39 |
40 | $scope.descriptionFunction = function(){
41 | return function(d){
42 | return d.key + ' ' + d.y;
43 | };
44 | };
45 | }
46 | };
47 | });
--------------------------------------------------------------------------------
/src/widgets/random/random.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | angular.module('ui.widgets')
20 | .directive('wtRandom', function ($interval) {
21 | return {
22 | restrict: 'A',
23 | replace: true,
24 | templateUrl: 'template/widgets/random/random.html',
25 | link: function postLink(scope) {
26 | function update() {
27 | scope.number = Math.floor(Math.random() * 100);
28 | }
29 |
30 | var promise = $interval(update, 500);
31 |
32 | scope.$on('$destroy', function () {
33 | $interval.cancel(promise);
34 | });
35 | }
36 | };
37 | });
--------------------------------------------------------------------------------
/src/widgets/scopeWatch/scopeWatch.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | angular.module('ui.widgets')
20 | .directive('wtScopeWatch', function () {
21 | return {
22 | restrict: 'A',
23 | replace: true,
24 | templateUrl: 'template/widgets/scopeWatch/scopeWatch.html',
25 | scope: {
26 | scopeValue: '=value',
27 | valueClass: '@valueClass'
28 | }
29 | };
30 | });
--------------------------------------------------------------------------------
/src/widgets/select/select.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | angular.module('ui.widgets')
20 | .directive('wtSelect', function () {
21 | return {
22 | restrict: 'A',
23 | replace: true,
24 | templateUrl: 'template/widgets/select/select.html',
25 | scope: {
26 | label: '@',
27 | value: '=',
28 | options: '='
29 | },
30 | controller: function ($scope) {
31 | $scope.prevValue = function () {
32 | var index = $scope.options.indexOf($scope.value);
33 | var nextIndex = (index - 1 + $scope.options.length) % $scope.options.length;
34 | $scope.value = $scope.options[nextIndex];
35 | };
36 |
37 | $scope.nextValue = function () {
38 | var index = $scope.options.indexOf($scope.value);
39 | var nextIndex = (index + 1) % $scope.options.length;
40 | $scope.value = $scope.options[nextIndex];
41 | };
42 | }
43 | };
44 | });
--------------------------------------------------------------------------------
/src/widgets/time/time.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | angular.module('ui.widgets')
20 | .directive('wtTime', function ($interval) {
21 | return {
22 | restrict: 'A',
23 | replace: true,
24 | templateUrl: 'template/widgets/time/time.html',
25 | link: function (scope) {
26 | function update() {
27 | scope.time = new Date().toLocaleTimeString();
28 | }
29 |
30 | var promise = $interval(update, 500);
31 |
32 | scope.$on('$destroy', function () {
33 | $interval.cancel(promise);
34 | });
35 | }
36 | };
37 | });
--------------------------------------------------------------------------------
/src/widgets/topN/topN.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | angular.module('ui.widgets')
20 | .directive('wtTopN', function () {
21 | return {
22 | restrict: 'A',
23 | replace: true,
24 | templateUrl: 'template/widgets/topN/topN.html',
25 | scope: {
26 | data: '='
27 | },
28 | controller: function ($scope) {
29 | $scope.tableOptions = {
30 | initialSorts: [
31 | { id: 'value', dir: '-' }
32 | ]
33 | };
34 | $scope.columns = [
35 | { id: 'name', key: 'name', label: 'Name' },
36 | { id: 'value', key: 'value', label: 'Value', sort: 'number' }
37 | ];
38 | },
39 | link: function postLink(scope) {
40 | scope.$watch('data', function (data) {
41 | if (data) {
42 | scope.items = data;
43 | }
44 | });
45 | }
46 | };
47 | });
--------------------------------------------------------------------------------
/template/widgets/barChart/barChart.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{start|date:'HH:mm:ss'}} - {{end|date:'HH:mm:ss'}}
4 |
5 |
14 |
15 |
--------------------------------------------------------------------------------
/template/widgets/historicalChart/historicalChart.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/template/widgets/metricsChart/metricsChart.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{start|date:'HH:mm:ss'}} - {{end|date:'HH:mm:ss'}}
4 |
7 |
8 |
22 |
23 |
--------------------------------------------------------------------------------
/template/widgets/nvd3LineChart/nvd3LineChart.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{start|date:'HH:mm:ss'}} - {{end|date:'HH:mm:ss'}}
4 |
5 |
19 |
20 |
--------------------------------------------------------------------------------
/template/widgets/pieChart/pieChart.html:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
--------------------------------------------------------------------------------
/template/widgets/random/random.html:
--------------------------------------------------------------------------------
1 |
2 | Random Number
3 |
{{number}}
4 |
--------------------------------------------------------------------------------
/template/widgets/scopeWatch/scopeWatch.html:
--------------------------------------------------------------------------------
1 |
2 | Value
3 |
{{scopeValue || 'no data'}}
4 |
--------------------------------------------------------------------------------
/template/widgets/select/select.html:
--------------------------------------------------------------------------------
1 |
2 | {{label}}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/template/widgets/time/time.html:
--------------------------------------------------------------------------------
1 |
2 | Time
3 |
{{time}}
4 |
--------------------------------------------------------------------------------
/template/widgets/topN/topN.html:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/test/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "browser": true,
4 | "esnext": true,
5 | "bitwise": true,
6 | "camelcase": true,
7 | "curly": true,
8 | "eqeqeq": true,
9 | "immed": true,
10 | "indent": 2,
11 | "latedef": true,
12 | "newcap": true,
13 | "noarg": true,
14 | "quotmark": "single",
15 | "regexp": true,
16 | "undef": true,
17 | "unused": true,
18 | "strict": true,
19 | "trailing": true,
20 | "smarttabs": true,
21 | "globals": {
22 | "after": false,
23 | "afterEach": false,
24 | "angular": false,
25 | "before": false,
26 | "beforeEach": false,
27 | "browser": false,
28 | "describe": false,
29 | "expect": false,
30 | "inject": false,
31 | "it": false,
32 | "spyOn": false
33 | }
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/test/runner.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | End2end Test Runner
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/spec/webSocket_test.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2013 DataTorrent, Inc. ALL Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | 'use strict';
17 |
18 | describe('Service: webSocket', function () {
19 |
20 | var webSocket, webSocketObject, notificationService, $rootScope, $timeout, WebSocket, Visibility, visibilityIsSupported, $window, wsUrl;
21 |
22 | describe('when explicitConnection is disabled', function() {
23 |
24 | describe('when url is provided by calling provider.setWebSocketURL', function() {
25 |
26 | beforeEach(module('ui.websocket', function($provide, webSocketProvider) {
27 |
28 | WebSocket = function(url, protocol) {
29 | webSocketObject = this;
30 | wsUrl = url;
31 | };
32 |
33 | Visibility = {
34 | change: jasmine.createSpy(),
35 | isSupported: function() {
36 | return visibilityIsSupported;
37 | }
38 | };
39 |
40 | visibilityIsSupported = true;
41 |
42 | $provide.value('$window', $window = {
43 | Visibility: Visibility,
44 | WebSocket: WebSocket
45 | });
46 |
47 | spyOn($window, 'WebSocket').andCallThrough();
48 |
49 | webSocketProvider.setWebSocketURL('ws://testing.com');
50 |
51 | }));
52 |
53 | beforeEach(inject(function (_webSocket_, _notificationService_, _$rootScope_) {
54 | webSocket = _webSocket_;
55 | notificationService = _notificationService_;
56 | $rootScope = _$rootScope_;
57 | }));
58 |
59 | it('should instantiate a WebSocket immediately with the url set with provider.setWebSocketURL', function() {
60 | expect($window.WebSocket).toHaveBeenCalledWith('ws://testing.com');
61 | });
62 |
63 | });
64 |
65 | describe('when the url is not provided by calling provider.setWebSocketURL', function() {
66 |
67 | beforeEach(module('ui.websocket', function($provide, webSocketProvider) {
68 |
69 | WebSocket = function(url, protocol) {
70 | webSocketObject = this;
71 | wsUrl = url;
72 | };
73 |
74 | Visibility = {
75 | change: jasmine.createSpy(),
76 | isSupported: function() {
77 | return visibilityIsSupported;
78 | }
79 | };
80 |
81 | visibilityIsSupported = true;
82 |
83 | $provide.value('$window', $window = {
84 | Visibility: Visibility,
85 | WebSocket: WebSocket
86 | });
87 |
88 | spyOn($window, 'WebSocket').andCallThrough();
89 |
90 | }));
91 |
92 | it('should throw if a URL was not set with provider.setWebSocketURL', function() {
93 | expect(function() {
94 | inject(function(webSocket) {
95 | // should not get here
96 | });
97 | }).toThrow();
98 | });
99 |
100 | });
101 |
102 | });
103 |
104 | describe('when explicitConnection is enabled', function() {
105 |
106 | describe('when url is provided by calling provider.setWebSocketURL', function() {
107 |
108 | beforeEach(module('ui.websocket', function($provide, webSocketProvider) {
109 |
110 | WebSocket = function(url, protocol) {
111 | webSocketObject = this;
112 | wsUrl = url;
113 | };
114 |
115 | Visibility = {
116 | change: jasmine.createSpy(),
117 | isSupported: function() {
118 | return visibilityIsSupported;
119 | }
120 | };
121 |
122 | visibilityIsSupported = true;
123 |
124 | $provide.value('$window', $window = {
125 | Visibility: Visibility,
126 | WebSocket: WebSocket
127 | });
128 |
129 | spyOn($window, 'WebSocket').andCallThrough();
130 |
131 | webSocketProvider.setExplicitConnection(true);
132 |
133 | webSocketProvider.setWebSocketURL('ws://testing.com');
134 |
135 | }));
136 |
137 | beforeEach(inject(function (_webSocket_, _notificationService_, _$rootScope_) {
138 | webSocket = _webSocket_;
139 | notificationService = _notificationService_;
140 | $rootScope = _$rootScope_;
141 | }));
142 |
143 | it('should not call WebSocket immediately', function() {
144 | expect($window.WebSocket).not.toHaveBeenCalled();
145 | });
146 |
147 | it('should instantiate a WebSocket with the url set with provider.setWebSocketURL if no url arg is passed to it', function() {
148 | webSocket.connect();
149 | expect($window.WebSocket).toHaveBeenCalledWith('ws://testing.com');
150 | });
151 |
152 | });
153 |
154 | describe('when the url is not provided by calling provider.setWebSocketURL', function() {
155 |
156 | beforeEach(module('ui.websocket', function($provide, webSocketProvider) {
157 |
158 | WebSocket = function(url, protocol) {
159 | webSocketObject = this;
160 | wsUrl = url;
161 | };
162 |
163 | Visibility = {
164 | change: jasmine.createSpy(),
165 | isSupported: function() {
166 | return visibilityIsSupported;
167 | }
168 | };
169 |
170 | visibilityIsSupported = true;
171 |
172 | $provide.value('$window', $window = {
173 | Visibility: Visibility,
174 | WebSocket: WebSocket
175 | });
176 |
177 | spyOn($window, 'WebSocket').andCallThrough();
178 |
179 | webSocketProvider.setExplicitConnection(true);
180 |
181 | }));
182 |
183 | beforeEach(inject(function (_webSocket_, _notificationService_, _$rootScope_) {
184 | webSocket = _webSocket_;
185 | notificationService = _notificationService_;
186 | $rootScope = _$rootScope_;
187 | }));
188 |
189 | it('should not call WebSocket immediately', function() {
190 | expect($window.WebSocket).not.toHaveBeenCalled();
191 | });
192 |
193 | it('should instantiate a WebSocket with the provided url', function() {
194 | webSocket.connect('ws://my-other-url.com');
195 | expect($window.WebSocket).toHaveBeenCalledWith('ws://my-other-url.com');
196 | });
197 |
198 | it('should throw if no url is passed and no url was set with provider.setWebSocketURL', function() {
199 | expect(function() {
200 | webSocket.connect();
201 | }).toThrow();
202 | });
203 |
204 | });
205 |
206 | });
207 |
208 | describe('the send method', function() {
209 |
210 | var wsUrl;
211 |
212 | beforeEach(module('ui.websocket', function($provide, webSocketProvider) {
213 |
214 | WebSocket = function(url, protocol) {
215 | webSocketObject = this;
216 | wsUrl = url;
217 | this.send = jasmine.createSpy();
218 | };
219 |
220 | Visibility = {
221 | change: jasmine.createSpy(),
222 | isSupported: function() {
223 | return visibilityIsSupported;
224 | }
225 | };
226 |
227 | visibilityIsSupported = true;
228 |
229 | $provide.value('$window', $window = {
230 | Visibility: Visibility,
231 | WebSocket: WebSocket
232 | });
233 |
234 | spyOn($window, 'WebSocket').andCallThrough();
235 |
236 | webSocketProvider.setWebSocketURL('ws://testing.com');
237 |
238 | }));
239 |
240 | beforeEach(inject(function (_webSocket_, _notificationService_, _$rootScope_) {
241 | webSocket = _webSocket_;
242 | notificationService = _notificationService_;
243 | $rootScope = _$rootScope_;
244 | }));
245 |
246 | it('should send message when WebSocket connection is opened', inject(function () {
247 | expect(webSocketObject.onopen).toBeDefined();
248 |
249 | webSocket.send({});
250 |
251 | expect(webSocketObject.send).not.toHaveBeenCalled(); // no connection yet
252 |
253 | webSocketObject.onopen();
254 |
255 | expect(webSocketObject.send).toHaveBeenCalled();
256 | }));
257 |
258 | it('should notify subscribers', function () {
259 | expect(webSocketObject.onmessage).toBeDefined();
260 |
261 | var listener1 = jasmine.createSpy();
262 | var listener2 = jasmine.createSpy();
263 |
264 | webSocket.subscribe('test', listener1);
265 | webSocket.subscribe('test', listener2);
266 |
267 | var message1 = { topic: 'test', data: { value: 100 } };
268 | webSocketObject.onmessage({ data: JSON.stringify(message1) });
269 |
270 | expect(listener1).toHaveBeenCalledWith({ value: 100 });
271 | expect(listener2).toHaveBeenCalledWith({ value: 100 });
272 |
273 | var message2 = { topic: 'test', data: { value: 50 } };
274 | webSocketObject.onmessage({ data: JSON.stringify(message2) });
275 |
276 | expect(listener1).toHaveBeenCalledWith({ value: 50 });
277 | expect(listener2).toHaveBeenCalledWith({ value: 50 });
278 | });
279 |
280 | it('should send a subscribe message', function() {
281 | var listener1 = jasmine.createSpy();
282 | spyOn(webSocket, 'send');
283 | webSocket.subscribe('testing', listener1);
284 | expect(webSocket.send).toHaveBeenCalledWith({ type: 'subscribe', topic: 'testing' });
285 | });
286 |
287 | it('should unsubscribe single callback', function () {
288 | expect(webSocketObject.onmessage).toBeDefined();
289 |
290 | var listener1 = jasmine.createSpy();
291 | var listener2 = jasmine.createSpy();
292 |
293 | webSocket.subscribe('test', listener1);
294 | webSocket.subscribe('test', listener2);
295 |
296 | var message = { topic: 'test', data: {} };
297 | var event = { data: JSON.stringify(message) };
298 | webSocketObject.onmessage(event);
299 |
300 | expect(listener1).toHaveBeenCalled();
301 | expect(listener2).toHaveBeenCalled();
302 |
303 | webSocket.unsubscribe('test', listener1);
304 |
305 | webSocketObject.onmessage(event);
306 |
307 | expect(listener1.callCount).toEqual(1);
308 | expect(listener2.callCount).toEqual(2);
309 | });
310 |
311 | it('should unsubscribe all listeners when second argument is omitted', function () {
312 | expect(webSocketObject.onmessage).toBeDefined();
313 |
314 | var listener1 = jasmine.createSpy();
315 | var listener2 = jasmine.createSpy();
316 |
317 | webSocket.subscribe('test', listener1);
318 | webSocket.subscribe('test', listener2);
319 |
320 | var message = { topic: 'test', data: {} };
321 | var event = { data: JSON.stringify(message) };
322 | webSocketObject.onmessage(event);
323 |
324 | expect(listener1).toHaveBeenCalled();
325 | expect(listener2).toHaveBeenCalled();
326 |
327 | webSocket.unsubscribe('test');
328 |
329 | webSocketObject.onmessage(event);
330 |
331 | expect(listener1.callCount).toEqual(1);
332 | expect(listener2.callCount).toEqual(1);
333 | });
334 |
335 |
336 | it('should call unsubscribe on scope destroy', function () {
337 | var listener = jasmine.createSpy();
338 | var scope = $rootScope.$new();
339 |
340 | spyOn(webSocket, 'unsubscribe');
341 |
342 | webSocket.subscribe('test', listener, scope);
343 |
344 | expect(webSocket.unsubscribe).not.toHaveBeenCalled();
345 |
346 | scope.$destroy();
347 |
348 | expect(webSocket.unsubscribe).toHaveBeenCalled();
349 |
350 | });
351 |
352 | it('should send an unsubscribe message when there are no more listeners for a given topic', function() {
353 |
354 | var listener1 = jasmine.createSpy();
355 | spyOn(webSocket, 'send');
356 | webSocket.subscribe('testing', listener1);
357 | expect(webSocket.send).toHaveBeenCalledWith({ type: 'subscribe', topic: 'testing' });
358 | webSocket.unsubscribe('testing', listener1);
359 | expect(webSocket.send).toHaveBeenCalledWith({ type: 'unsubscribe', topic: 'testing' });
360 | });
361 |
362 | it('should send a subscribe message again if a topic has previously been unsubscribed to', function() {
363 | var listener1 = jasmine.createSpy();
364 | spyOn(webSocket, 'send');
365 | webSocket.subscribe('testing', listener1);
366 | expect(webSocket.send).toHaveBeenCalledWith({ type: 'subscribe', topic: 'testing' });
367 | webSocket.unsubscribe('testing', listener1);
368 | expect(webSocket.send).toHaveBeenCalledWith({ type: 'unsubscribe', topic: 'testing' });
369 | webSocket.subscribe('testing', listener1);
370 | expect(webSocket.send.calls[2].args[0]).toEqual({ type: 'subscribe', topic: 'testing' });
371 | });
372 |
373 | });
374 |
375 | describe('the connect method', function() {
376 |
377 | beforeEach(module('ui.websocket', function($provide, webSocketProvider) {
378 | WebSocket = function(url, protocol) {
379 | webSocketObject = this;
380 | wsUrl = url;
381 | this.send = jasmine.createSpy();
382 | this.close = function() {
383 | this.onclose();
384 | };
385 | };
386 | WebSocket.OPEN = 1;
387 |
388 | Visibility = {
389 | change: jasmine.createSpy(),
390 | isSupported: function() {
391 | return visibilityIsSupported;
392 | }
393 | };
394 |
395 | visibilityIsSupported = true;
396 |
397 | $provide.value('$window', $window = {
398 | Visibility: Visibility,
399 | WebSocket: WebSocket
400 | });
401 | webSocketProvider.setExplicitConnection(true);
402 | webSocketProvider.setWebSocketURL('ws://testing.com');
403 |
404 | }));
405 |
406 | beforeEach(inject(function (_webSocket_, _notificationService_, _$rootScope_, _$timeout_) {
407 | webSocket = _webSocket_;
408 | notificationService = _notificationService_;
409 | $rootScope = _$rootScope_;
410 | $timeout = _$timeout_;
411 | webSocket.connect();
412 |
413 | webSocketObject.onopen();
414 |
415 | spyOn(webSocketObject, 'close').andCallThrough();
416 | // spyOn(webSocket, 'connect').andCallThrough();
417 | }));
418 |
419 | it('should not throw', function() {
420 | expect(function() {
421 | webSocket.connect();
422 | }).not.toThrow();
423 | });
424 |
425 | it('should do nothing if an open WebSocket instance is already there', function() {
426 | var initObj = webSocketObject;
427 | initObj.readyState = WebSocket.OPEN;
428 | webSocket.connect();
429 | expect(initObj === webSocketObject).toEqual(true);
430 | });
431 |
432 | });
433 |
434 | describe('the disconnect method', function() {
435 |
436 | beforeEach(module('ui.websocket', function($provide, webSocketProvider) {
437 |
438 | WebSocket = function(url, protocol) {
439 | webSocketObject = this;
440 | wsUrl = url;
441 | this.send = jasmine.createSpy();
442 | this.close = function() {
443 | this.onclose();
444 | };
445 | };
446 |
447 | Visibility = {
448 | change: jasmine.createSpy(),
449 | isSupported: function() {
450 | return visibilityIsSupported;
451 | }
452 | };
453 |
454 | visibilityIsSupported = true;
455 |
456 | $provide.value('$window', $window = {
457 | Visibility: Visibility,
458 | WebSocket: WebSocket
459 | });
460 |
461 | spyOn($window, 'WebSocket').andCallThrough();
462 | webSocketProvider.setExplicitConnection(true);
463 | webSocketProvider.setWebSocketURL('ws://testing.com');
464 |
465 | }));
466 |
467 | describe('when the webSocket has been connected', function() {
468 |
469 | beforeEach(inject(function (_webSocket_, _notificationService_, _$rootScope_, _$timeout_) {
470 | webSocket = _webSocket_;
471 | notificationService = _notificationService_;
472 | $rootScope = _$rootScope_;
473 | $timeout = _$timeout_;
474 | webSocket.connect();
475 |
476 | webSocketObject.onopen();
477 |
478 | spyOn(webSocketObject, 'close').andCallThrough();
479 | spyOn(webSocket, 'connect');
480 | }));
481 |
482 | it('should call the close method of the WebSocket', function() {
483 | webSocket.disconnect();
484 | expect(webSocketObject.close).toHaveBeenCalled();
485 | });
486 |
487 | it('should not try to re-establish connection after connectionAttemptInterval', function() {
488 | webSocket.disconnect();
489 | $timeout.verifyNoPendingTasks();
490 | });
491 |
492 | });
493 |
494 | describe('when the webSocket has not connected', function() {
495 |
496 | beforeEach(inject(function (_webSocket_, _notificationService_, _$rootScope_, _$timeout_) {
497 | webSocket = _webSocket_;
498 | notificationService = _notificationService_;
499 | $rootScope = _$rootScope_;
500 | $timeout = _$timeout_;
501 | spyOn(webSocket, 'connect');
502 | }));
503 |
504 | it('should not throw', function() {
505 | expect(function() {
506 | webSocket.disconnect();
507 | }).not.toThrow();
508 | });
509 |
510 | });
511 |
512 | });
513 |
514 | describe('when the connection gets unexpectedly closed', function() {
515 |
516 | beforeEach(module('ui.websocket', function($provide, webSocketProvider) {
517 |
518 | WebSocket = function(url, protocol) {
519 | webSocketObject = this;
520 | wsUrl = url;
521 | this.send = jasmine.createSpy();
522 | this.close = function() {
523 | this.onclose();
524 | };
525 | };
526 |
527 | Visibility = {
528 | change: jasmine.createSpy(),
529 | isSupported: function() {
530 | return visibilityIsSupported;
531 | }
532 | };
533 |
534 | visibilityIsSupported = true;
535 |
536 | $provide.value('$window', $window = {
537 | Visibility: Visibility,
538 | WebSocket: WebSocket
539 | });
540 |
541 | spyOn($window, 'WebSocket').andCallThrough();
542 |
543 | webSocketProvider.setWebSocketURL('ws://testing.com');
544 | webSocketProvider.setConnectionAttemptInterval(1000);
545 | webSocketProvider.setMaxConnectionAttempts(3);
546 |
547 | }));
548 |
549 | beforeEach(inject(function (_webSocket_, _notificationService_, _$rootScope_, _$timeout_) {
550 | webSocket = _webSocket_;
551 | notificationService = _notificationService_;
552 | $rootScope = _$rootScope_;
553 | $timeout = _$timeout_;
554 |
555 | webSocketObject.onopen();
556 |
557 | spyOn(webSocketObject, 'close').andCallThrough();
558 | spyOn(webSocket, 'connect').andCallThrough();
559 | }));
560 |
561 | it('should try to re-establish connection', function() {
562 | webSocketObject.onclose();
563 | $timeout.flush();
564 | expect(webSocket.connect).toHaveBeenCalled();
565 | });
566 |
567 | it('should try maxConnectionAttempts times', function() {
568 | webSocketObject.onclose();
569 | $timeout.flush();
570 | webSocketObject.onclose();
571 | $timeout.flush();
572 | webSocketObject.onclose();
573 | $timeout.flush();
574 | webSocketObject.onclose();
575 | $timeout.verifyNoPendingTasks();
576 | expect(webSocket.connect.calls.length).toEqual(3);
577 | });
578 |
579 | it('should start queuing up send messages again, then send those messages once connection has been re-established', function() {
580 |
581 | });
582 |
583 | });
584 |
585 | });
586 |
--------------------------------------------------------------------------------
/test/spec/widgets/main.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | describe('main test', function () {
20 | var $compile, $rootScope;
21 |
22 | beforeEach(module('ui.widgets'));
23 | beforeEach(inject(function(_$compile_, _$rootScope_) {
24 | $compile = _$compile_;
25 | $rootScope = _$rootScope_;
26 | $rootScope.myData = [];
27 | }));
28 |
29 | it('should have topN directive', function() {
30 | var element = angular.element('
');
31 | $compile(element)($rootScope);
32 | $rootScope.$digest();
33 | expect(element.hasClass('top-n')).toBe(true);
34 | });
35 | });
--------------------------------------------------------------------------------