├── images └── search.png ├── css └── style.css ├── README.md ├── index.html ├── script ├── controller.js ├── monitoring.js ├── chart.js └── chart-display.js └── LICENSE /images/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleCloudPlatform/monitoring-javascript-dashboard/HEAD/images/search.png -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 60px; 3 | } 4 | 5 | label { 6 | display: inline; 7 | } 8 | 9 | input { 10 | width: 150px; 11 | margin: 0 5px; 12 | } 13 | 14 | #project-id:hover { 15 | background-color: yellow; 16 | } 17 | 18 | #project-info { 19 | margin-bottom: 20px; 20 | } 21 | 22 | #slider { 23 | width: 400px; 24 | margin-top: 5px; 25 | } 26 | 27 | .chartContainer { 28 | clear: both; 29 | margin: 40px 0 25px 0; 30 | height: 500px; 31 | width: 1350px; 32 | } 33 | 34 | .error { 35 | width: 1350px; 36 | } 37 | 38 | .chart { 39 | float: left; 40 | margin-right: 15px; 41 | } 42 | 43 | .legend { 44 | float: left; 45 | } 46 | 47 | .search { 48 | background-image: url('../images/search.png'); 49 | width: 12px; 50 | height: 12px; 51 | float: left; 52 | margin-left: 15px; 53 | } 54 | 55 | .chartLabelForm { 56 | display: none; 57 | margin-left: 15px; 58 | float: left; 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Monitoring JavaScript Dashboard 2 | =============================== 3 | 4 | About 5 | ----- 6 | 7 | This sample demonstrates the use of the Cloud Monitoring 8 | API using the Google APIs JavaScript Client Library. For more 9 | information about the Monitoring API, please see the documentation: 10 | 11 | https://developers.google.com/cloud/eap/cloud-monitoring/ 12 | 13 | Instructions 14 | ------------ 15 | 16 | 1. Update the API Key and Client ID in the monitoring.js file with 17 | your own OAuth 2.0 credentials. For more information, see 18 | the Google APIs JavaScript Client Library documentation: 19 | 20 | https://developers.google.com/api-client-library/javascript/features/authentication 21 | 22 | 2. Download the dependencies and install in the scripts/ 23 | and css/ directories, or update the URLs in the index.html file to point 24 | to hosted versions of the dependencies. 25 | 26 | Dependencies 27 | ------------ 28 | 29 | - [jQuery 1.11.0][1] 30 | - [jQuery UI 1.10.4][2] 31 | - [Bootstrap 3.1.1][3] 32 | - [d3 3.4.3][4] 33 | - [Rickshaw][5] 34 | 35 | [1]: http://jquery.com/ 36 | [2]: https://jqueryui.com/ 37 | [3]: http://getbootstrap.com/ 38 | [4]: http://d3js.org/ 39 | [5]: http://code.shutterstock.com/rickshaw/ 40 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 14 | 17 | 20 | 23 | 24 | 27 | 30 | 33 | 36 | 39 | 42 | 45 | 48 | 51 | 52 | 53 | 54 | 55 | 67 | 68 | 69 |
70 | 73 | 74 |
75 | 80 | 84 |
85 | 86 | 90 | 91 |
92 |
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.} 51 | * @private 52 | */ 53 | this.charts_ = []; 54 | 55 | /** 56 | * List of default charts to display. 57 | * @type {Array.} 58 | * @private 59 | */ 60 | this.defaultCharts_ = [ 61 | 'compute.googleapis.com/instance/uptime', 62 | 'compute.googleapis.com/instance/disk/read_ops_count', 63 | 'compute.googleapis.com/instance/disk/write_ops_count', 64 | 'compute.googleapis.com/instance/disk/read_latencies', 65 | 'compute.googleapis.com/instance/disk/write_latencies', 66 | 'compute.googleapis.com/instance/network/received_bytes_count', 67 | 'compute.googleapis.com/instance/network/sent_bytes_count', 68 | 'compute.googleapis.com/instance/cpu/usage_time', 69 | 'compute.googleapis.com/firewall/dropped_packets_count' 70 | ]; 71 | }; 72 | 73 | /** 74 | * Add the chart based on the provided metric. 75 | * @param {Object} metric A single metric object returned from the 76 | * metricDescriptors.list API endpoint. 77 | */ 78 | ChartDisplay.prototype.display = function(metric) { 79 | 80 | var chartNumber = this.charts_.length; 81 | 82 | // Create a container to hold all chart elements. 83 | var chartContainer = this.createChartContainer_(chartNumber); 84 | 85 | // Create a title for the chart. 86 | var chartTitle = this.createChartTitle_(metric.description); 87 | $(chartContainer).append(chartTitle); 88 | 89 | // Create an error element in which to display errors. 90 | var errorElement = this.createErrorElement_(chartNumber); 91 | $(chartContainer).append(errorElement); 92 | 93 | // Create an element in which to display the chart itself. 94 | var chartElement = this.createChartElement_(chartNumber); 95 | $(chartContainer).append(chartElement); 96 | 97 | // Create the chart legend. 98 | var chartLegend = this.createChartLegend_(chartNumber, chart); 99 | $(chartContainer).append(chartLegend); 100 | 101 | // Create a query object to query the API. 102 | var query = { 103 | metric: metric.name, 104 | project: this.api_.projectId, 105 | timespan: this.timespanValues[this.defaultTimespanIndex] 106 | }; 107 | 108 | // Create a data formatter based on the type of metric. 109 | var formatter = null; 110 | if (metric.typeDescriptor.valueType == 'distribution') { 111 | formatter = this.formatDataDistribution_(); 112 | } else if (metric.typeDescriptor.valueType == 'double') { 113 | formatter = this.formatDataSimple_('doubleValue'); 114 | } else { 115 | formatter = this.formatDataSimple_('int64Value'); 116 | } 117 | 118 | // Create the actual chart. 119 | var chart = new Chart( 120 | chartElement, chartLegend, errorElement, this.api_, query, formatter); 121 | 122 | // Create the label form. 123 | var chartLabelForm = this.createChartLabelForm_(chartNumber, metric); 124 | 125 | // Create the search icon. 126 | var searchIcon = this.createSearchIcon_(chartLabelForm); 127 | $(chartContainer).append(searchIcon); 128 | $(chartContainer).append(chartLabelForm); 129 | 130 | // Add the container to the charts HTML element. 131 | $('#charts').append(chartContainer); 132 | 133 | // Add the chart object to the list of charts. 134 | this.charts_.push(chart); 135 | }; 136 | 137 | /** 138 | * Displays default charts. Called by the controller once all necessary 139 | * initialization steps have been completed. 140 | * @return {Function} A function to display the default charts on the page. 141 | */ 142 | ChartDisplay.prototype.displayDefaultCharts = function() { 143 | var self = this; 144 | 145 | return function(metrics) { 146 | $('#charts').empty(); 147 | for (var metric in metrics) { 148 | if (self.defaultCharts_.indexOf(metrics[metric].name) > -1) { 149 | self.display(metrics[metric]); 150 | } 151 | } 152 | } 153 | }; 154 | 155 | /** 156 | * Update all the charts at a given interval. The interval is set by the 157 | * controller. 158 | * @return {Function} A function to update the charts. 159 | */ 160 | ChartDisplay.prototype.intervalUpdater = function() { 161 | var self = this; 162 | return function() { 163 | for (var chart in self.charts_) { 164 | self.charts_[chart].update(); 165 | } 166 | } 167 | }; 168 | 169 | /** 170 | * Update the timespan of the query. This method is called when the value 171 | * of the range selector changes. 172 | * @param {string} timespan The new timespan for the query. 173 | */ 174 | ChartDisplay.prototype.rangeUpdater = function(timespan) { 175 | for (var chart in this.charts_) { 176 | this.charts_[chart].update({'timespan': timespan}); 177 | } 178 | }; 179 | 180 | /** 181 | * Update the chart with the given labels in the chart form. 182 | * @param {number} chartNumber The chart number to update. 183 | * @return {Function} A function to update the charts with the new query. 184 | */ 185 | ChartDisplay.prototype.labelUpdater = function(chartNumber) { 186 | var self = this; 187 | 188 | return function() { 189 | // Get query parameters from label form and add them to the query object. 190 | var query = {}; 191 | query['labels'] = []; 192 | $('*[name="' + chartNumber + '"]').each(function() { 193 | if ($(this).val()) { 194 | var label = $(this).data('label') + '==' + $(this).val(); 195 | query['labels'].push(label); 196 | } 197 | }); 198 | self.charts_[chartNumber].update(query); 199 | }; 200 | }; 201 | 202 | /** 203 | * Reset the chart by removing all labels from the query. 204 | * @param {number} chartNumber The chart number to update. 205 | * @return {Function} A function to reset the charts. 206 | */ 207 | ChartDisplay.prototype.reset = function(chartNumber) { 208 | var self = this; 209 | 210 | return function() { 211 | $('*[name="' + chartNumber + '"]').each(function() { 212 | $(this).val(''); 213 | }); 214 | self.charts_[chartNumber].reset(); 215 | }; 216 | }; 217 | 218 | /** 219 | * Create the container for the chart and corresponding elements. 220 | * @param {number} chartNumber The number of the chart. 221 | * @return {Element} An HTML Element. 222 | * @private 223 | */ 224 | ChartDisplay.prototype.createChartContainer_ = function(chartNumber) { 225 | // Add the chart container. 226 | var chartContainer = document.createElement('div'); 227 | $(chartContainer).attr('id', 'chartContainer' + chartNumber); 228 | $(chartContainer).addClass('chartContainer'); 229 | return chartContainer; 230 | }; 231 | 232 | /** 233 | * Create the title for the chart. 234 | * @param {string} title The title of the chart. 235 | * @return {Element} An HTML Element. 236 | * @private 237 | */ 238 | ChartDisplay.prototype.createChartTitle_ = function(title) { 239 | // Add the chart title. 240 | var chartTitle = document.createElement('h4'); 241 | $(chartTitle).text(title); 242 | return chartTitle; 243 | }; 244 | 245 | /** 246 | * Create the error element for the chart. 247 | * @return {Element} An HTML Element. 248 | * @private 249 | */ 250 | ChartDisplay.prototype.createErrorElement_ = function() { 251 | // Add the chart title. 252 | var error = document.createElement('div'); 253 | $(error).addClass('error'); 254 | return error; 255 | }; 256 | 257 | /** 258 | * Create the HTML element for the actual chart. 259 | * @param {number} chartNumber The number of the chart. 260 | * @return {Element} An HTML Element. 261 | * @private 262 | */ 263 | ChartDisplay.prototype.createChartElement_ = function(chartNumber) { 264 | // Add the chart. 265 | var chart = document.createElement('div'); 266 | $(chart).attr('id', 'chart' + chartNumber); 267 | $(chart).addClass('chart'); 268 | return chart; 269 | }; 270 | 271 | /** 272 | * Create the HTML Element for the legend. 273 | * @param {number} chartNumber The number of the chart. 274 | * @return {Element} An HTML Element. 275 | * @private 276 | */ 277 | ChartDisplay.prototype.createChartLegend_ = function(chartNumber) { 278 | // Add the chart legend. 279 | var chartLegend = document.createElement('div'); 280 | $(chartLegend).attr('id', 'legend' + chartNumber); 281 | $(chartLegend).addClass('legend'); 282 | return chartLegend; 283 | }; 284 | 285 | /** 286 | * Create the label form. 287 | * @param {number} chartNumber The number of the chart. 288 | * @param {Object} metric Metric object returned from the API. 289 | * @return {Element} An HTML Element. 290 | * @private 291 | */ 292 | ChartDisplay.prototype.createChartLabelForm_ = function(chartNumber, metric) { 293 | var self = this; 294 | 295 | // Add the label form. 296 | var formContainer = document.createElement('form'); 297 | $(formContainer).addClass('chartLabelForm'); 298 | 299 | // Display the labels specific to the metric by getting them from the API. 300 | this.api_.getDescriptors(metric.name, function(descriptors) { 301 | 302 | // Create a dictionary mapping label name to all possible values 303 | // using the metricDescriptors.list API endpoint. 304 | var descriptorLists = {}; 305 | for (var descriptor in descriptors) { 306 | for (var label in descriptors[descriptor].labels) { 307 | if (!descriptorLists[label]) { 308 | descriptorLists[label] = []; 309 | } 310 | var labelValue = descriptors[descriptor].labels[label]; 311 | if (descriptorLists[label].indexOf(labelValue) == -1) { 312 | descriptorLists[label].push(labelValue); 313 | } 314 | } 315 | } 316 | 317 | // For each label, add an input with drop-down selector using all possible 318 | // label values. 319 | for (var label in metric.labels) { 320 | var labelName = metric.labels[label].key; 321 | self.addLabelInput_( 322 | formContainer, 323 | chartNumber, 324 | labelName, 325 | descriptorLists[labelName]); 326 | } 327 | 328 | // Add Go and Reset buttons to the form. 329 | var go = document.createElement('input'); 330 | $(go).attr('type', 'button'); 331 | $(go).val('Go'); 332 | $(go).click(self.labelUpdater(chartNumber)); 333 | $(formContainer).append(go); 334 | var reset = document.createElement('input'); 335 | $(reset).attr('type', 'button'); 336 | $(reset).val('Reset'); 337 | $(reset).click(self.reset(chartNumber)); 338 | $(formContainer).append(reset); 339 | }); 340 | 341 | return formContainer; 342 | }; 343 | 344 | /** 345 | * Create the search icon. 346 | * @param {Element} chartLabelForm The HTML element in which the chart label 347 | * form is displayed. 348 | * @return {Element} An HTML Element. 349 | * @private 350 | */ 351 | ChartDisplay.prototype.createSearchIcon_ = function(chartLabelForm) { 352 | // Add the search icon. 353 | var search = document.createElement('div'); 354 | $(search).addClass('search'); 355 | 356 | // When the search icon is clicked, hide or display the label form. 357 | $(search).click(function() { 358 | if ($(chartLabelForm).css('display') == 'none') { 359 | $(chartLabelForm).fadeIn(500); 360 | } else { 361 | $(chartLabelForm).fadeOut(500); 362 | } 363 | }); 364 | 365 | return search; 366 | }; 367 | 368 | /** 369 | * Add a form label and input for a given API label. 370 | * @param {Element} formContainer The container for the form elements. 371 | * @param {number} chartNumber The chart number. 372 | * @param {string} label The text to display in the form label. 373 | * @param {Array.} descriptors A list of values for the select menu. 374 | * @private 375 | */ 376 | ChartDisplay.prototype.addLabelInput_ = function( 377 | formContainer, chartNumber, label, descriptors) { 378 | var formLabel = document.createElement('label'); 379 | $(formLabel).text(label + ': '); 380 | $(formContainer).append(formLabel); 381 | 382 | var input = null; 383 | if (descriptors) { 384 | input = document.createElement('select'); 385 | var option = document.createElement('option'); 386 | $(option).attr('value', ''); 387 | $(option).text('--Select--'); 388 | $(input).append(option); 389 | for (var descriptor in descriptors) { 390 | var option = document.createElement('option'); 391 | $(option).attr('value', descriptors[descriptor]); 392 | $(option).text(descriptors[descriptor]); 393 | $(input).append(option); 394 | } 395 | } else { 396 | input = document.createElement('input'); 397 | $(input).attr('type', 'text'); 398 | } 399 | $(input).attr('name', chartNumber); 400 | $(input).data('label', label); 401 | $(formContainer).append(input); 402 | 403 | var lineBreak = document.createElement('br'); 404 | $(formContainer).append(lineBreak); 405 | }; 406 | 407 | /** 408 | * Format the data for display in the chart. 409 | * @return {Function} A function for formatting the data. 410 | * @private 411 | */ 412 | ChartDisplay.prototype.formatDataSimple_ = function(valueKey) { 413 | return function(data) { 414 | var formattedData = []; 415 | var palette = new Rickshaw.Color.Palette({scheme: 'munin'}); 416 | 417 | // Create a list of objects (aka, series) for display in the chart. 418 | // List is formatted as follows: 419 | // [{ 420 | // name: , 421 | // data: , 422 | // color: 423 | // }, ...] 424 | for (var timeseries in data) { 425 | var formattedSeries = {}; 426 | 427 | // Add a name to the series. Name is displayed in the legend. 428 | var resourceType = data[timeseries].timeseriesDesc.labels[ 429 | 'cloud.googleapis.com/resource_type']; 430 | if (resourceType == 'instance') { 431 | formattedSeries.name = data[timeseries].timeseriesDesc.labels[ 432 | 'compute.googleapis.com/instance_name']; 433 | } else { 434 | formattedSeries.name = data[timeseries].timeseriesDesc.labels[ 435 | 'cloud.googleapis.com/resource_id']; 436 | } 437 | 438 | // Add the data to the series, formatted as a list of objects with syntax: 439 | // [{x: