93 |
94 |
106 |
109 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/script/controller.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Google 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 | * @fileoverview Controls app flow.
17 | */
18 |
19 | /**
20 | * The Controller class controls the app flow. It ensures that OAuth
21 | * is complete and the project ID is set before displaying the charts.
22 | * @constructor
23 | * @param {Object} api A Monitoring API object.
24 | * @param {Object} chartDisplay A ChartDisplay object.
25 | */
26 | var Controller = function(api, chartDisplay) {
27 | /**
28 | * Interval at which to update the charts in milliseconds.
29 | * @type {number}
30 | */
31 | this.chartInterval = 20000;
32 |
33 | /**
34 | * MonitoringApi object.
35 | * @type {Object}
36 | * @private
37 | */
38 | this.api_ = api;
39 |
40 | /**
41 | * ChartDisplay object.
42 | * @type {Object}
43 | * @private
44 | */
45 | this.chartDisplay_ = chartDisplay;
46 |
47 | /**
48 | * Interval which updates charts with new data.
49 | * @type {Object}
50 | * @private
51 | */
52 | this.interval_ = null;
53 | };
54 |
55 | /**
56 | * Start OAuth 2.0 flow and initialize events on HTML elements. Initiate the
57 | * update interval.
58 | */
59 | Controller.prototype.init = function() {
60 | var self = this;
61 |
62 | // Initialize OAuth 2.0 flow.
63 | window.setTimeout(this.api_.auth(this.checkProjectId_()), 1);
64 |
65 | // Initialize the buttons.
66 | $('#project-button').click(this.setProjectId_());
67 | $('#project-id').click(this.resetProjectId_());
68 |
69 | // Initialize the slider and slider text.
70 | $('#timespan-value').text(this.chartDisplay_.timespanValues[
71 | this.chartDisplay_.defaultTimespanIndex]);
72 | $('#slider').slider({
73 | min: 0,
74 | max: this.chartDisplay_.timespanValues.length - 1,
75 | value: this.chartDisplay_.defaultTimespanIndex,
76 | change: function(event, ui) {
77 | var sliderSelection = $(this).slider('value');
78 | var timespan = self.chartDisplay_.timespanValues[sliderSelection];
79 | $('#timespan-value').text(timespan);
80 | self.chartDisplay_.rangeUpdater(timespan);
81 |
82 | // Reset the interval so that it doesn't update right after the
83 | // new range is set.
84 | window.clearInterval(self.interval_);
85 | self.interval_ = window.setInterval(
86 | self.chartDisplay_.intervalUpdater(), self.chartInterval);
87 | }
88 | });
89 |
90 | // Start an interval to update the charts every X milliseconds.
91 | this.interval_ = window.setInterval(
92 | this.chartDisplay_.intervalUpdater(), this.chartInterval);
93 | };
94 |
95 | /**
96 | * Check whether the project ID has already been set in local storage. If
97 | * it has, show the project ID and charts on the page. If not, display
98 | * the project form.
99 | * @return {Function} A function to test if project ID is set.
100 | * @private
101 | */
102 | Controller.prototype.checkProjectId_ = function() {
103 | var self = this;
104 |
105 | return function() {
106 | if (typeof(Storage) !== 'undefined') {
107 | var projectId = localStorage.getItem('project-id');
108 | if (projectId) {
109 | self.api_.projectId = projectId;
110 | self.displayCharts_()();
111 | return;
112 | }
113 | }
114 |
115 | $('#project-form').css('display', 'inline');
116 | };
117 | };
118 |
119 | /**
120 | * Store the project ID in local storage and set the api project ID. This
121 | * method is called when project form button is clicked.
122 | * @return {Function} A function to store project ID.
123 | * @private
124 | */
125 | Controller.prototype.setProjectId_ = function() {
126 | var self = this;
127 |
128 | return function() {
129 | // Get the value of the project ID from the form.
130 | var projectId = $('#project-id-field').val();
131 |
132 | if (projectId) {
133 | // If storage exists, store the project ID locally so the user
134 | // doesn't have to keep entering it every time they visit the page.
135 | if (typeof(Storage) !== 'undefined') {
136 | localStorage.setItem('project-id', projectId);
137 | }
138 |
139 | self.api_.projectId = projectId;
140 | self.displayCharts_()();
141 |
142 | } else {
143 | alert('Project ID required!');
144 | }
145 | }
146 | };
147 |
148 | /**
149 | * Allow the user to reset the project ID by showing the project ID form.
150 | * This method is called when the user clicks on the project ID.
151 | * @return {Function} A function to display the project ID form.
152 | * @private
153 | */
154 | Controller.prototype.resetProjectId_ = function() {
155 | var self = this;
156 | return function() {
157 | $('#project-form').css('display', 'inline');
158 | $('#project-id-field').val(self.api_.projectId);
159 | $('#project-display').css('display', 'none');
160 | };
161 | };
162 |
163 | /**
164 | * Display the project ID on the page, hide the project form, and show charts.
165 | * @return {Function} A function to display the charts.
166 | * @private
167 | */
168 | Controller.prototype.displayCharts_ = function() {
169 | var self = this;
170 | return function() {
171 | $('#project-form').css('display', 'none');
172 | $('#time-selector').css('display', 'inline');
173 | $('#project-display').css('display', 'inline');
174 | $('#project-id').text(self.api_.projectId);
175 | self.api_.getMetrics(self.chartDisplay_.displayDefaultCharts());
176 | };
177 | };
178 |
--------------------------------------------------------------------------------
/script/monitoring.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Google 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 | * @fileoverview Monitoring API demo.
17 | */
18 |
19 | /**
20 | * The MonitoringApi class performs authorization and retrieves
21 | * data from the Monitoring API.
22 | * @constructor
23 | */
24 | var MonitoringApi = function() {
25 | /**
26 | * User's project ID. Set via the UI.
27 | * @type {string}
28 | */
29 | this.projectId = null;
30 |
31 | /**
32 | * API name.
33 | * @type {String}
34 | * @private
35 | */
36 | this.apiName_ = 'cloudmonitoring';
37 |
38 | /**
39 | * API version.
40 | * @type {String}
41 | * @private
42 | */
43 | this.apiVersion_ = 'v2beta1';
44 |
45 | /**
46 | * Google API Key.
47 | * @type {String}
48 | * @private
49 | */
50 | this.apiKey_ = '';
51 |
52 | /**
53 | * Google Client ID.
54 | * @type {String}
55 | * @private
56 | */
57 | this.clientId_ = '';
58 |
59 | /**
60 | * Google API scope.
61 | * @type {String}
62 | * @private
63 | */
64 | this.scopes_ = 'https://www.googleapis.com/auth/monitoring.readonly';
65 | };
66 |
67 | /**
68 | * Initialize the HTML page and authorization settings.
69 | * @param {Function} authComplete Function to call when authorization is done.
70 | */
71 | MonitoringApi.prototype.auth = function(authComplete) {
72 | gapi.client.setApiKey(this.apiKey_);
73 | gapi.auth.authorize({
74 | client_id: this.clientId_,
75 | scope: this.scopes_,
76 | immediate: true}, this.handleAuthResult(authComplete));
77 | };
78 |
79 | /**
80 | * Handle the response from Google's authorization server.
81 | * @param {Function} authComplete Function to call when authorization is done.
82 | * @return {Function} A function to handle response from Google's authorization
83 | * server.
84 | */
85 | MonitoringApi.prototype.handleAuthResult = function(authComplete) {
86 | var self = this;
87 |
88 | return function(authResult) {
89 | if (authResult && authResult.name != 'TypeError') {
90 | $('#authorize-button').css('visibility', 'hidden');
91 | authComplete();
92 | } else {
93 | $('#authorize-button').css('visibility', '');
94 | $('#authorize-button').click(function(event) {
95 | gapi.auth.authorize({
96 | client_id: self.clientId_,
97 | scope: self.scopes_,
98 | immediate: false}, self.handleAuthResult(authComplete));
99 | return false;
100 | });
101 | }
102 | };
103 | };
104 |
105 | /**
106 | * Make a call to the Monitoring API timeseries.list endpoint. Pass
107 | * the returned data to the provided callback function.
108 | * @param {Object} query Query parameters for the call to the API. For example:
109 | * {
110 | * timespan: '2d',
111 | * labels: ['label==value',...]
112 | * }
113 | * @param {function} callback Method to call when API returns.
114 | */
115 | MonitoringApi.prototype.getData = function(query, callback) {
116 | var self = this;
117 | var timeseries = [];
118 |
119 | // Make a copy of the query in case the pageToken needs to be added.
120 | // We don't want the pageToken added to the query object.
121 | var localQuery = $.extend({}, query);
122 | localQuery.youngest = new Date().toISOString();
123 |
124 | var makeCall = function() {
125 | gapi.client.load(self.apiName_, self.apiVersion_, function() {
126 | var request = gapi.client.cloudmonitoring.timeseries.list(localQuery);
127 | request.execute(function(response) {
128 | if (response.timeseries) {
129 | $.merge(timeseries, response.timeseries);
130 | if (response.nextPageToken) {
131 | $.extend(localQuery, {'pageToken': response.nextPageToken});
132 | makeCall();
133 | } else {
134 | callback(timeseries);
135 | }
136 | } else {
137 | // If there's no timeseries data, there was a problem. Return the
138 | // response to display in the error message.
139 | callback(timeseries, response);
140 | }
141 | });
142 | });
143 | };
144 | makeCall();
145 | };
146 |
147 | /**
148 | * Make a call to the Monitoring API metricDescriptors.list endpoint.
149 | * @param {function} callback Method to call when API returns.
150 | */
151 | MonitoringApi.prototype.getMetrics = function(callback) {
152 | var self = this;
153 | gapi.client.load(this.apiName_, this.apiVersion_, function() {
154 | var request = gapi.client.cloudmonitoring.metricDescriptors.list({
155 | 'project': self.projectId
156 | });
157 | request.execute(function(response) {
158 | callback(response.metrics);
159 | });
160 | });
161 | };
162 |
163 | /**
164 | * Make a call to the Monitoring API timeseriesDescriptors.list endpoint.
165 | * @param {string} metric String metric name (ex:
166 | * compute.googleapis.com/instance/disk/read_latencies)
167 | * @param {function} callback Method to call when API returns.
168 | */
169 | MonitoringApi.prototype.getDescriptors = function(metric, callback) {
170 | var self = this;
171 | gapi.client.load(this.apiName_, this.apiVersion_, function() {
172 | var request = gapi.client.cloudmonitoring.timeseriesDescriptors.list({
173 | 'metric': metric,
174 | 'project': self.projectId
175 | });
176 | request.execute(function(response) {
177 | callback(response.timeseries);
178 | });
179 | });
180 | };
181 |
--------------------------------------------------------------------------------
/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 {yyyy} {name of copyright owner}
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.
--------------------------------------------------------------------------------
/script/chart.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Google 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 | * @fileoverview Graphs data from the Monitoring API using Rickshaw.
17 | */
18 |
19 | /**
20 | * The Chart class wraps the Rickshaw class.
21 | * @constructor
22 | * @param {Element} chartElement The HTML element in which to add the chart.
23 | * @param {Element} legendElement The HTML element in which to add the legend.
24 | * @param {Element} errorElement The HTML element in which to display an error.
25 | * @param {Object} api An instance of the MonitoringApi class.
26 | * @param {Object} query The query specific to this chart.
27 | * @param {Function} formatter Function to format the data.
28 | */
29 | var Chart = function(
30 | chartElement, legendElement, errorElement, api, query, formatter) {
31 |
32 | /**
33 | * Element in which to display the chart.
34 | * @type {Element}
35 | */
36 | this.chartElement = chartElement;
37 |
38 | /**
39 | * Element in which to display the legend.
40 | * @type {Element}
41 | */
42 | this.legendElement = legendElement;
43 |
44 | /**
45 | * Element in which to display an error message.
46 | * @type {Element}
47 | */
48 | this.errorElement = errorElement;
49 |
50 | /**
51 | * Data returned by the Monitoring API and formatted for display on the chart.
52 | * @type {Object}
53 | */
54 | this.data = null;
55 |
56 | /**
57 | * Specific API query for this chart.
58 | * @type {Object}
59 | */
60 | this.query = query;
61 |
62 | /**
63 | * A function to format the data.
64 | * @type {Function}
65 | */
66 | this.formatter = formatter;
67 |
68 | /**
69 | * Rickshaw chart.
70 | * @type {Rickshaw.Graph}
71 | * @private
72 | */
73 | this.chart_ = null;
74 |
75 | /**
76 | * Monitoring API object.
77 | * @type {Object}
78 | * @private
79 | */
80 | this.api_ = api;
81 |
82 | /**
83 | * Copy of a query for rollback if there's an error.
84 | * @type {Object}
85 | * @private
86 | */
87 | this.oldQuery_ = query;
88 |
89 | /**
90 | * Chart height.
91 | * @type {number}
92 | * @private
93 | */
94 | this.chartHeight_ = 400;
95 |
96 | /**
97 | * Chart width.
98 | * @type {number}
99 | * @private
100 | */
101 | this.chartWidth_ = 600;
102 |
103 | /**
104 | * Number of ticks on the x-axis.
105 | * @type {number}
106 | * @private
107 | */
108 | this.numberTicks_ = 4;
109 |
110 | this.update();
111 | };
112 |
113 | /**
114 | * Update the chart with the provided query.
115 | * @param {Object} query The new query (optional).
116 | */
117 | Chart.prototype.update = function(query) {
118 | // Make a copy of the current query in case there's an error.
119 | this.oldQuery_ = $.extend({}, this.query);
120 |
121 | // Extend the existing query with the new query. The new query
122 | // overwrites existing query parameters.
123 | this.query = $.extend({}, this.query, query);
124 |
125 | this.update_();
126 | };
127 |
128 | /**
129 | * Reset the chart by removing all query labels.
130 | */
131 | Chart.prototype.reset = function() {
132 | // Remove labels from query.
133 | delete this.query.labels;
134 | this.update_();
135 | };
136 |
137 | /**
138 | * Run the API call to get new data with which to update the chart.
139 | * @private
140 | */
141 | Chart.prototype.update_ = function() {
142 | var self = this;
143 |
144 | // Call the API to get the new data for the chart.
145 | this.api_.getData(this.query, function(data, response) {
146 | // If there's no data, display an error message and revert to the old query.
147 | if (response) {
148 | var errorText = ['Query returned no results.'];
149 | errorText.push('Query parameters:');
150 | errorText.push(JSON.stringify(self.query));
151 | errorText.push(JSON.stringify(response));
152 | self.error_(errorText.join(' '));
153 | self.query = self.oldQuery_;
154 | return;
155 | }
156 |
157 | // Format the data for display in the chart.
158 | self.data = self.formatter(data);
159 |
160 | // Create the chart if it doesn't exist. This is done the first time.
161 | if (!self.chart_) {
162 | self.create_(self.data);
163 |
164 | } else {
165 | // Boolean value set to true if a series is added or removed.
166 | var updateLegend = false;
167 |
168 | // Replace chart data series with new data.
169 | var removeIndices = [];
170 | for (var series in self.chart_.series) {
171 | var exists = false;
172 | for (var data in self.data) {
173 | if (self.chart_.series[series].name == self.data[data].name) {
174 | self.chart_.series[series].data = self.data[data].data;
175 | exists = true;
176 | } else if (series == 'active') {
177 | // Rickshaw adds this field to the series data, so keep it.
178 | exists = true;
179 | }
180 | }
181 | // If this series doesn't exist in the new data, keep note for later
182 | // removal.
183 | if (!exists) {
184 | removeIndices.push(series);
185 | updateLegend = true;
186 | }
187 | }
188 |
189 | // Remove any series that don't exist in the new data.
190 | for (var index in removeIndices) {
191 | self.chart_.series.splice(removeIndices[index]);
192 | }
193 |
194 | // Find new data that doesn't exist in the series and add it.
195 | for (var data in self.data) {
196 | var exists = false;
197 | for (var series in self.chart_.series) {
198 | if (self.chart_.series[series].name == self.data[data].name) {
199 | exists = true;
200 | }
201 | }
202 | if (!exists) {
203 | self.chart_.series.push(self.data[data]);
204 | updateLegend = true;
205 | }
206 | }
207 |
208 | // If there was any change to the number of series, then update the
209 | // legend by recreating it.
210 | if (updateLegend) {
211 | self.createLegend_();
212 | }
213 |
214 | // Update the chart.
215 | self.chart_.update();
216 | }
217 | });
218 | };
219 |
220 | /**
221 | * Create the Rickshaw chart and supporting elements.
222 | * @param {Object} data The data to display on the chart.
223 | * @private
224 | */
225 | Chart.prototype.create_ = function(data) {
226 | var self = this;
227 |
228 | // Create and display the actual Rickshaw chart.
229 | this.chart_ = new Rickshaw.Graph({
230 | element: this.chartElement,
231 | renderer: 'line',
232 | width: this.chartWidth_,
233 | height: this.chartHeight_,
234 | series: data,
235 | min: 'auto'
236 | });
237 | this.chart_.render();
238 |
239 | // Create a time fixture for the X axis.
240 | var time = this.createTimeFixture_();
241 |
242 | // Add the X axis to the chart.
243 | var xAxis = new Rickshaw.Graph.Axis.Time({
244 | graph: this.chart_,
245 | timeFixture: time
246 | });
247 | // Rewrite the tickOffsets function to display only a specific number of
248 | // ticks on the X axis.
249 | xAxis.tickOffsets = function() {
250 | var domain = this.graph.x.domain();
251 | var unit = this.fixedTimeUnit || this.appropriateTimeUnit();
252 | var step = Math.ceil((domain[1] - domain[0]) / self.numberTicks_);
253 | var offsets = [];
254 | for (var i = 0; i < self.numberTicks_; i++) {
255 | var tickValue = domain[0] + step * i;
256 | offsets.push({value: tickValue, unit: unit});
257 | }
258 | return offsets;
259 | };
260 | xAxis.render();
261 |
262 | // Add the Y axis to the chart.
263 | var yAxis = new Rickshaw.Graph.Axis.Y({
264 | graph: this.chart_,
265 | tickFormat: Rickshaw.Fixtures.Number.formatKMBT
266 | });
267 | yAxis.render();
268 |
269 | // Add hover state to the chart.
270 | var hoverDetail = new Rickshaw.Graph.HoverDetail({
271 | graph: this.chart_,
272 | xFormatter: function(date) {
273 | return self.stringifyDate_(date);
274 | }
275 | });
276 |
277 | // Add the legend to the chart.
278 | this.createLegend_();
279 | };
280 |
281 | /**
282 | * Create a Rickshaw.Fixtures.Time object for the X axis to display the
283 | * date as a string.
284 | * @return {Rickshaw.Fixtures.Time} A Rickshaw.Fixtures.Time object.
285 | * @private
286 | */
287 | Chart.prototype.createTimeFixture_ = function() {
288 | var self = this;
289 |
290 | var time = new Rickshaw.Fixtures.Time();
291 | for (var unit in time.units) {
292 | // Update the formatters to display the date as a string.
293 | time.units[unit].formatter = function(date) {
294 | return self.stringifyDate_(date);
295 | };
296 | }
297 | return time;
298 | };
299 |
300 | /**
301 | * Display a legend next to the graph.
302 | * @private
303 | */
304 | Chart.prototype.createLegend_ = function() {
305 | $(this.legendElement).empty();
306 |
307 | // Add a legend to the chart.
308 | var legend = new Rickshaw.Graph.Legend({
309 | graph: this.chart_,
310 | element: this.legendElement
311 | });
312 |
313 | // Update the render method in the legend to color-code series
314 | // according to the distribution range value rather than the series
315 | // name. This is done via the legend value within the series object.
316 | legend.render = function() {
317 | var self = this;
318 |
319 | $(this.list).empty();
320 | this.lines = [];
321 |
322 | var series = this.graph.series.map(function(s) {
323 | return s;
324 | });
325 |
326 | if (!this.naturalOrder) {
327 | series = series.reverse();
328 | }
329 |
330 | var labels = [];
331 | series.forEach(function(s) {
332 | if (s.legend) {
333 | if (labels.indexOf(s.legend) == -1) {
334 | self.addLine(s);
335 | labels.push(s.legend);
336 | }
337 | } else {
338 | self.addLine(s);
339 | }
340 | });
341 | };
342 |
343 | // Update the addLine method in the legend object to display the
344 | // series legend value as the text rather than the series name.
345 | legend.addLine = function(series) {
346 | var line = document.createElement('li');
347 | line.className = 'line';
348 | if (series.disabled) {
349 | line.className += ' disabled';
350 | }
351 | if (series.className) {
352 | d3.select(line).classed(series.className, true);
353 | }
354 |
355 | var swatch = document.createElement('div');
356 | swatch.className = 'swatch';
357 | swatch.style.backgroundColor = series.color;
358 | line.appendChild(swatch);
359 |
360 | var label = document.createElement('span');
361 | label.className = 'label';
362 | if (series.legend) {
363 | label.innerHTML = series.legend;
364 | } else {
365 | label.innerHTML = series.name;
366 | }
367 | line.appendChild(label);
368 |
369 | this.list.appendChild(line);
370 |
371 | line.series = series;
372 | if (series.noLegend) {
373 | line.style.display = 'none';
374 | }
375 |
376 | var _line = {element: line, series: series};
377 |
378 | if (this.shelving) {
379 | this.shelving.addAnchor(_line);
380 | this.shelving.updateBehaviour();
381 | }
382 | if (this.highlighter) {
383 | this.highlighter.addHighlightEvents(_line);
384 | }
385 |
386 | this.lines.push(_line);
387 | return line;
388 | };
389 | legend.render();
390 | };
391 |
392 | /**
393 | * Format the date as YYYY-mm-dd HH:MM:SS.
394 | * @param {number} date A date in milliseconds.
395 | * @return {string} A stringified date object.
396 | * @private
397 | */
398 | Chart.prototype.stringifyDate_ = function(date) {
399 | var date = new Date(date);
400 | var stringDate = [];
401 | stringDate.push(date.getFullYear());
402 | stringDate.push('-');
403 | stringDate.push(date.getMonth() + 1);
404 | stringDate.push('-');
405 | stringDate.push(date.getDate());
406 | stringDate.push(' ');
407 | stringDate.push(date.getHours());
408 | stringDate.push(':');
409 | if (date.getMinutes() < 10) {
410 | stringDate.push('0');
411 | }
412 | stringDate.push(date.getMinutes());
413 | stringDate.push(':');
414 | if (date.getSeconds() < 10) {
415 | stringDate.push('0');
416 | }
417 | stringDate.push(date.getSeconds());
418 | return stringDate.join('');
419 | };
420 |
421 | /**
422 | * Display an error message for the chart.
423 | * @param {string} errorText An error message to display.
424 | * @private
425 | */
426 | Chart.prototype.error_ = function(errorText) {
427 | var self = this;
428 |
429 | $(this.errorElement).empty();
430 | var error = document.createElement('div');
431 | $(error).addClass('alert alert-danger alert-dismissable');
432 | $(error).text(errorText);
433 | var close = document.createElement('button');
434 | $(close).attr('type', 'button');
435 | $(close).addClass('close');
436 | $(close).attr('data-dismiss', 'alert');
437 | $(close).attr('aria-hidden', 'true');
438 | $(close).text('x');
439 | $(error).append(close);
440 | $(this.errorElement).append(error);
441 | window.setTimeout(function() {
442 | $(error).fadeTo(200, 0).slideUp(700, function() {
443 | $(this).remove();
444 | });
445 | }, 7000);
446 | };
447 |
--------------------------------------------------------------------------------
/script/chart-display.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Google 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 | * @fileoverview Controls the display of the charts on the page.
17 | */
18 |
19 | /**
20 | * The ChartDisplay controls the display of the charts.
21 | * @constructor
22 | * @param {Object} api The Monitoring API object.
23 | */
24 | var ChartDisplay = function(api) {
25 | /**
26 | * Values for the range selector.
27 | * @type {Array.}
28 | */
29 | this.timespanValues = ['5m', '10m', '20m', '30m', '40m', '50m', '1h', '2h',
30 | '3h', '4h', '5h', '6h', '7h', '8h', '9h', '10h', '11h',
31 | '12h', '1d', '2d', '3d', '4d', '5d', '6d', '1w',
32 | '2w', '3w', '30d'];
33 |
34 | /**
35 | * The default timespan to select on page load.
36 | * @type {number}
37 | */
38 | this.defaultTimespanIndex = 6;
39 |
40 | /**
41 | * The Monitoring API object.
42 | * @type {Object}
43 | * @private
44 | */
45 | this.api_ = api;
46 |
47 | /**
48 | * A list of objects representing the visible charts, their data, and
49 | * their queries.
50 | * @type {Array.