Commit b8231b29 by Daniel Lee

stackdriver: ux for config page, docs updated

parent 65cbcc06
...@@ -12,7 +12,7 @@ weight = 11 ...@@ -12,7 +12,7 @@ weight = 11
# Using Google Stackdriver in Grafana # Using Google Stackdriver in Grafana
Grafana ships with built in support for Google Stackdriver. You just have to add it as a datasource and you will be ready to build dashboards for your Stackdriver metrics. Grafana ships with built-in support for Google Stackdriver. You just have to add it as a datasource and you will be ready to build dashboards for your Stackdriver metrics.
## Adding the data source to Grafana ## Adding the data source to Grafana
...@@ -56,11 +56,14 @@ Click on the links above and click the `Enable` button: ...@@ -56,11 +56,14 @@ Click on the links above and click the `Enable` button:
3. On the `Create service account key` page, choose key type `JSON`. Then in the `Service Account` dropdown, choose the `New service account` option: 3. On the `Create service account key` page, choose key type `JSON`. Then in the `Service Account` dropdown, choose the `New service account` option:
![Create service account key](/img/docs/v54/stackdriver_create_service_account_key.png) ![Create service account key](/img/docs/v54/stackdriver_create_service_account_key.png)
4. Some new fields will appear. Fill in a name for the service account in the `Service account name` field and then choose the Monitoring Viewer role from the `Role` dropdown: 4. Some new fields will appear. Fill in a name for the service account in the `Service account name` field and then choose the `Monitoring Viewer` role from the `Role` dropdown:
![Choose role](/img/docs/v54/stackdriver_service_account_choose_role.png) ![Choose role](/img/docs/v54/stackdriver_service_account_choose_role.png)
5. Click the Create button. A Json Web Token (JWT) file will be created and downloaded to your computer. Store this file in a secure place as it allows access to your Stackdriver data. 5. Click the Create button. A Json Web Token (JWT) file will be created and downloaded to your computer. Store this file in a secure place as it allows access to your Stackdriver data.
6. Upload it to Grafana on the datasource Configuration page. 6. Upload it to Grafana on the datasource Configuration page. You can either upload the file or paste in the contents of the file.
![Choose role](/img/docs/v54/stackdriver_grafana_upload_key.png)
7. The file contents will be encrypted and saved in the Grafana database. Don't forget to save after uploading the file!
![Choose role](/img/docs/v54/stackdriver_grafana_key_uploaded.png)
## Metric Query Editor ## Metric Query Editor
......
...@@ -5,25 +5,20 @@ export class StackdriverConfigCtrl { ...@@ -5,25 +5,20 @@ export class StackdriverConfigCtrl {
jsonText: string; jsonText: string;
validationErrors: string[] = []; validationErrors: string[] = [];
inputDataValid: boolean; inputDataValid: boolean;
defaultProject: string;
projectsError: string;
projects: any[];
loadingProjects: boolean;
/** @ngInject */ /** @ngInject */
constructor(private $scope, datasourceSrv) { constructor(datasourceSrv) {
this.datasourceSrv = datasourceSrv; this.datasourceSrv = datasourceSrv;
this.current.jsonData = this.current.jsonData || {}; this.current.jsonData = this.current.jsonData || {};
this.current.secureJsonData = this.current.secureJsonData || {}; this.current.secureJsonData = this.current.secureJsonData || {};
this.current.secureJsonFields = this.current.secureJsonFields || {}; this.current.secureJsonFields = this.current.secureJsonFields || {};
this.defaultProject = this.current.jsonData.defaultProject;
this.projects = [];
} }
save(jwt) { save(jwt) {
this.current.secureJsonData.privateKey = jwt.private_key; this.current.secureJsonData.privateKey = jwt.private_key;
this.current.jsonData.tokenUri = jwt.token_uri; this.current.jsonData.tokenUri = jwt.token_uri;
this.current.jsonData.clientEmail = jwt.client_email; this.current.jsonData.clientEmail = jwt.client_email;
this.current.jsonData.defaultProject = jwt.project_id;
} }
validateJwt(jwt) { validateJwt(jwt) {
...@@ -43,16 +38,15 @@ export class StackdriverConfigCtrl { ...@@ -43,16 +38,15 @@ export class StackdriverConfigCtrl {
if (this.validationErrors.length === 0) { if (this.validationErrors.length === 0) {
this.inputDataValid = true; this.inputDataValid = true;
return true; return true;
} else {
return false;
} }
return false;
} }
onUpload(json) { onUpload(json) {
this.jsonText = ''; this.jsonText = '';
if (this.validateJwt(json)) { if (this.validateJwt(json)) {
this.save(json); this.save(json);
this.displayProjects();
} }
} }
...@@ -61,7 +55,6 @@ export class StackdriverConfigCtrl { ...@@ -61,7 +55,6 @@ export class StackdriverConfigCtrl {
const json = JSON.parse(e.originalEvent.clipboardData.getData('text/plain') || this.jsonText); const json = JSON.parse(e.originalEvent.clipboardData.getData('text/plain') || this.jsonText);
if (this.validateJwt(json)) { if (this.validateJwt(json)) {
this.save(json); this.save(json);
this.displayProjects();
} }
} catch (error) { } catch (error) {
this.resetValidationMessages(); this.resetValidationMessages();
...@@ -73,44 +66,9 @@ export class StackdriverConfigCtrl { ...@@ -73,44 +66,9 @@ export class StackdriverConfigCtrl {
this.validationErrors = []; this.validationErrors = [];
this.inputDataValid = false; this.inputDataValid = false;
this.jsonText = ''; this.jsonText = '';
this.loadingProjects = false;
this.projectsError = '';
this.current.jsonData = {}; this.current.jsonData = {};
this.current.secureJsonData = {}; this.current.secureJsonData = {};
this.current.secureJsonFields = {}; this.current.secureJsonFields = {};
} }
async displayProjects() {
if (this.projects.length === 0) {
try {
this.loadingProjects = true;
const ds = await this.datasourceSrv.loadDatasource(this.current.name);
this.projects = await ds.getProjects();
this.$scope.$apply(() => {
if (this.projects.length > 0) {
this.current.jsonData.defaultProject = this.current.jsonData.defaultProject || this.projects[0].id;
}
});
} catch (error) {
let message = 'Projects cannot be fetched: ';
message += error.statusText ? error.statusText + ': ' : '';
if (error && error.data && error.data.error && error.data.error.message) {
if (error.data.error.code === 403) {
message += `
A list of projects could not be fetched from the Google Cloud Resource Manager API.
You might need to enable it first:
https://console.developers.google.com/apis/library/cloudresourcemanager.googleapis.com`;
} else {
message += error.data.error.code + '. ' + error.data.error.message;
}
} else {
message += 'Cannot connect to Stackdriver API';
}
this.$scope.$apply(() => (this.projectsError = message));
} finally {
this.$scope.$apply(() => (this.loadingProjects = false));
}
}
}
} }
<div ng-if="!ctrl.current.jsonData.clientEmail && !ctrl.inputDataValid"> <div class="gf-form-group">
<div class="gf-form-group" ng-if="!ctrl.inputDataValid"> <div class="grafana-info-box">
<div class="gf-form"> <h5>GCP Service Account</h5>
<form> <p>
<dash-upload on-upload="ctrl.onUpload(dash)"></dash-upload> To authenticate with the Stackdriver API, you need to create a Google Cloud Platform (GCP) Service Account for
</form> the Project you want to show data for. A Grafana datasource integrates with one GCP Project. If you want to
</div> visualize data from multiple GCP Projects then you need to create one datasource per GCP Project.
</div> </p>
<div class="gf-form-group"> <p>
<h5 class="section-heading" ng-if="!ctrl.inputDataValid">Or paste JSON</h5> The <strong>Monitoring Viewer</strong> role provides all the permissions that Grafana needs.
<div class="gf-form" ng-if="!ctrl.inputDataValid"> </p>
<textarea rows="10" data-share-panel-url="" class="gf-form-input" ng-model="ctrl.jsonText" ng-paste="ctrl.onPasteJwt($event)"></textarea> <p>
</div> The following APIs need to be enabled on GCP for the datasource to work:
<div ng-repeat="valError in ctrl.validationErrors" class="text-error p-l-1"> <ul>
<i class="fa fa-warning"></i> <li><a class="external-link" target="_blank" href="https://console.cloud.google.com/apis/library/monitoring.googleapis.com">Monitoring
{{valError}} API</a></li>
</div> <li><a class="external-link" target="_blank" href="https://console.cloud.google.com/apis/library/cloudresourcemanager.googleapis.com">Resource
</div> Manager API</a></li>
</ul>
</p>
<p>Detailed instructions on how to create a Service Account can be found <a class="external-link" target="_blank"
href="http://docs.grafana.org/datasources/stackdriver/">in
the documentation.</a></p>
</div>
</div>
<div class="gf-form-group">
<div class="gf-form">
<h3>Service Account Authentication</h3>
<info-popover mode="header">Upload your Service Account key file or paste in the contents of the file. The file
contents will be encrypted and saved in the Grafana database.</info-popover>
</div>
<div ng-if="!ctrl.current.jsonData.clientEmail && !ctrl.inputDataValid">
<div class="gf-form-group" ng-if="!ctrl.inputDataValid">
<div class="gf-form">
<form>
<dash-upload on-upload="ctrl.onUpload(dash)" btn-text="Upload Service Account key file"></dash-upload>
</form>
</div>
</div>
<div class="gf-form-group">
<h5 class="section-heading" ng-if="!ctrl.inputDataValid">Or paste Service Account key JSON</h5>
<div class="gf-form" ng-if="!ctrl.inputDataValid">
<textarea rows="10" data-share-panel-url="" class="gf-form-input" ng-model="ctrl.jsonText" ng-paste="ctrl.onPasteJwt($event)"></textarea>
</div>
<div ng-repeat="valError in ctrl.validationErrors" class="text-error p-l-1">
<i class="fa fa-warning"></i>
{{valError}}
</div>
</div>
</div>
</div> </div>
<div class="gf-form-group" ng-if="ctrl.inputDataValid || ctrl.current.jsonData.clientEmail"> <div class="gf-form-group" ng-if="ctrl.inputDataValid || ctrl.current.jsonData.clientEmail">
<div class="gf-form-inline"> <h6>Uploaded Key Details</h6>
<div class="gf-form">
<span class="gf-form-label width-9">Token URI</span>
<input class="gf-form-input width-30" disabled type="text" ng-model='ctrl.current.jsonData.tokenUri' />
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label width-9">Client Email</span>
<input class="gf-form-input width-30" disabled type="text" ng-model="ctrl.current.jsonData.clientEmail" />
</div>
</div>
<div class="gf-form" ng-if="ctrl.current.secureJsonFields.privateKey">
<span class="gf-form-label width-9">Private Key</span>
<input type="text" class="gf-form-input max-width-12" disabled="disabled" value="configured">
</div>
<div class="gf-form">
<span class="gf-form-label width-9">Project</span>
<input class="gf-form-input width-40" disabled type="text" ng-model="ctrl.current.jsonData.defaultProject" />
</div>
<div class="gf-form">
<span class="gf-form-label width-9">Client Email</span>
<input class="gf-form-input width-40" disabled type="text" ng-model="ctrl.current.jsonData.clientEmail" />
</div>
<div class="gf-form">
<span class="gf-form-label width-9">Token URI</span>
<input class="gf-form-input width-40" disabled type="text" ng-model='ctrl.current.jsonData.tokenUri' />
</div>
<div class="gf-form" ng-if="ctrl.current.secureJsonFields.privateKey">
<span class="gf-form-label width-9">Private Key</span>
<input type="text" class="gf-form-input max-width-12" disabled="disabled" value="configured">
</div>
<div class="gf-form width-18">
<a class="btn btn-secondary gf-form-btn" href="#" ng-click="ctrl.resetValidationMessages()">Reset Service
Account Key </a>
<info-popover mode="right-normal">
Reset to clear the uploaded key and upload a new file.
</info-popover>
</div>
</div>
<div class="gf-form"> <p class="gf-form-label" ng-hide="ctrl.current.secureJsonFields.privateKey"><i class="fa fa-save"></i> Do not forget to save your changes after uploading a file.</p>
<a class="btn btn-secondary gf-form-btn" href="#" ng-click="ctrl.resetValidationMessages()">Reset form</a>
</div>
<br />
<div class="gf-form">
<span class="gf-form-label width-10">Default Project</span>
<div class="gf-form-select-wrapper max-width-23">
<select class="gf-form-input" ng-model="ctrl.current.jsonData.defaultProject" ng-options="p.id as p.name for p in ctrl.projects"
ng-change="ctrl.userChangedDefaultProject()"></select>
</div>
<div ng-if="ctrl.loadingProjects">
<i class="fa fa-spinner fa-spin"></i>
<em>Fetching projects...&hellip;</em>
</div>
</div>
<div ng-if="ctrl.projectsError" class="text-error p-l-1">
<i class="fa fa-warning"></i>
{{ctrl.projectsError}}
</div>
</div>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment